image.dart 7.73 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:ui' as ui show Image;
6 7 8

import 'box.dart';
import 'object.dart';
9

10 11 12 13
export 'package:flutter/painting.dart' show
  ImageFit,
  ImageRepeat;

14
/// An image in the render tree.
15 16 17
///
/// The render image attempts to find a size for itself that fits in the given
/// constraints and preserves the image's intrinisc aspect ratio.
18 19 20
///
/// The image is painted using [paintImage], which describes the meanings of the
/// various fields on this class in more detail.
21
class RenderImage extends RenderBox {
22
  /// Creates a render box that displays an image.
23
  RenderImage({
24
    ui.Image image,
25 26
    double width,
    double height,
27
    double scale: 1.0,
Adam Barth's avatar
Adam Barth committed
28
    Color color,
29
    ImageFit fit,
30
    FractionalOffset alignment,
Adam Barth's avatar
Adam Barth committed
31
    ImageRepeat repeat: ImageRepeat.noRepeat,
32
    Rect centerSlice
33 34 35
  }) : _image = image,
      _width = width,
      _height = height,
36
      _scale = scale,
Adam Barth's avatar
Adam Barth committed
37
      _color = color,
38
      _fit = fit,
39
      _alignment = alignment,
40
      _repeat = repeat,
Adam Barth's avatar
Adam Barth committed
41 42 43
      _centerSlice = centerSlice {
    _updateColorFilter();
  }
44

45
  /// The image to display.
46
  ui.Image get image => _image;
47
  ui.Image _image;
48
  set image (ui.Image value) {
49 50 51 52 53 54 55 56
    if (value == _image)
      return;
    _image = value;
    markNeedsPaint();
    if (_width == null || _height == null)
      markNeedsLayout();
  }

57
  /// If non-null, requires the image to have this width.
58 59 60
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
61
  double get width => _width;
62
  double _width;
63
  set width (double value) {
64 65 66 67 68 69
    if (value == _width)
      return;
    _width = value;
    markNeedsLayout();
  }

70 71 72 73
  /// If non-null, require the image to have this height.
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
74
  double get height => _height;
75
  double _height;
76
  set height (double value) {
77 78 79 80 81 82
    if (value == _height)
      return;
    _height = value;
    markNeedsLayout();
  }

83
  /// Specifies the image's scale.
84 85 86 87
  ///
  /// Used when determining the best display size for the image.
  double get scale => _scale;
  double _scale;
88
  set scale (double value) {
89
    assert(value != null);
90 91 92 93 94 95
    if (value == _scale)
      return;
    _scale = value;
    markNeedsLayout();
  }

Adam Barth's avatar
Adam Barth committed
96
  ColorFilter _colorFilter;
Adam Barth's avatar
Adam Barth committed
97 98 99 100 101 102 103 104 105 106 107 108

  // Should we make the transfer mode configurable?
  void _updateColorFilter() {
    if (_color == null)
      _colorFilter = null;
    else
      _colorFilter = new ColorFilter.mode(_color, TransferMode.srcIn);
  }

  /// If non-null, apply this color filter to the image before painting.
  Color get color => _color;
  Color _color;
109
  set color (Color value) {
Adam Barth's avatar
Adam Barth committed
110
    if (value == _color)
111
      return;
Adam Barth's avatar
Adam Barth committed
112 113
    _color = value;
    _updateColorFilter();
114 115 116
    markNeedsPaint();
  }

117
  /// How to inscribe the image into the place allocated during layout.
118 119 120
  ///
  /// The default varies based on the other fields. See the discussion at
  /// [paintImage].
121
  ImageFit get fit => _fit;
122
  ImageFit _fit;
123
  set fit (ImageFit value) {
124 125 126 127 128 129
    if (value == _fit)
      return;
    _fit = value;
    markNeedsPaint();
  }

130 131 132
  /// How to align the image within its bounds.
  FractionalOffset get alignment => _alignment;
  FractionalOffset _alignment;
133
  set alignment (FractionalOffset value) {
134 135 136 137 138 139
    if (value == _alignment)
      return;
    _alignment = value;
    markNeedsPaint();
  }

140
  /// How to repeat this image if it doesn't fill its layout bounds.
141
  ImageRepeat get repeat => _repeat;
142
  ImageRepeat _repeat;
143
  set repeat (ImageRepeat value) {
144 145 146 147 148 149
    if (value == _repeat)
      return;
    _repeat = value;
    markNeedsPaint();
  }

150 151 152 153 154 155 156 157 158
  /// The center slice for a nine-patch image.
  ///
  /// The region of the image inside the center slice will be stretched both
  /// horizontally and vertically to fit the image into its destination. The
  /// region of the image above and below the center slice will be stretched
  /// only horizontally and the region of the image to the left and right of
  /// the center slice will be stretched only vertically.
  Rect get centerSlice => _centerSlice;
  Rect _centerSlice;
159
  set centerSlice (Rect value) {
160 161 162 163 164 165 166
    if (value == _centerSlice)
      return;
    _centerSlice = value;
    markNeedsPaint();
  }

  /// Find a size for the render image within the given constraints.
167 168 169 170 171 172
  ///
  ///  - The dimensions of the RenderImage must fit within the constraints.
  ///  - The aspect ratio of the RenderImage matches the instrinsic aspect
  ///    ratio of the image.
  ///  - The RenderImage's dimension are maximal subject to being smaller than
  ///    the intrinsic size of the image.
173
  Size _sizeForConstraints(BoxConstraints constraints) {
174
    // Folds the given |width| and |height| into |constraints| so they can all
175 176 177 178
    // be treated uniformly.
    constraints = new BoxConstraints.tightFor(
      width: _width,
      height: _height
179
    ).enforce(constraints);
180 181 182 183

    if (constraints.isTight || _image == null)
      return constraints.smallest;

184 185
    double width = _image.width.toDouble() / _scale;
    double height = _image.height.toDouble() / _scale;
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
    assert(width > 0.0);
    assert(height > 0.0);
    double aspectRatio = width / height;

    if (width > constraints.maxWidth) {
      width = constraints.maxWidth;
      height = width / aspectRatio;
    }

    if (height > constraints.maxHeight) {
      height = constraints.maxHeight;
      width = height * aspectRatio;
    }

    if (width < constraints.minWidth) {
      width = constraints.minWidth;
      height = width / aspectRatio;
    }

    if (height < constraints.minHeight) {
      height = constraints.minHeight;
      width = height * aspectRatio;
    }

    return constraints.constrain(new Size(width, height));
  }

213
  @override
214
  double computeMinIntrinsicWidth(double height) {
215
    assert(height >= 0.0);
216
    if (_width == null && _height == null)
217 218
      return 0.0;
    return _sizeForConstraints(new BoxConstraints.tightForFinite(height: height)).width;
219 220
  }

221
  @override
222
  double computeMaxIntrinsicWidth(double height) {
223
    assert(height >= 0.0);
224
    return _sizeForConstraints(new BoxConstraints.tightForFinite(height: height)).width;
225 226
  }

227
  @override
228
  double computeMinIntrinsicHeight(double width) {
229
    assert(width >= 0.0);
230
    if (_width == null && _height == null)
231 232
      return 0.0;
    return _sizeForConstraints(new BoxConstraints.tightForFinite(width: width)).height;
233 234
  }

235
  @override
236
  double computeMaxIntrinsicHeight(double width) {
237
    assert(width >= 0.0);
238
    return _sizeForConstraints(new BoxConstraints.tightForFinite(width: width)).height;
239 240
  }

241
  @override
Adam Barth's avatar
Adam Barth committed
242 243
  bool hitTestSelf(Point position) => true;

244
  @override
245 246 247 248
  void performLayout() {
    size = _sizeForConstraints(constraints);
  }

249
  @override
250 251 252 253 254 255 256 257 258
  void paint(PaintingContext context, Offset offset) {
    if (_image == null)
      return;
    paintImage(
      canvas: context.canvas,
      rect: offset & size,
      image: _image,
      colorFilter: _colorFilter,
      fit: _fit,
259
      alignment: _alignment,
260
      centerSlice: _centerSlice,
261 262 263 264
      repeat: _repeat
    );
  }

265
  @override
266 267 268
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('image: $image');
269
    if (width != null)
270
      description.add('width: $width');
271
    if (height != null)
272
      description.add('height: $height');
273
    if (scale != 1.0)
274
      description.add('scale: $scale');
275
    if (color != null)
276
      description.add('color: $color');
277
    if (fit != null)
278
      description.add('fit: $fit');
279
    if (alignment != null)
280
      description.add('alignment: $alignment');
281
    if (repeat != ImageRepeat.noRepeat)
282
      description.add('repeat: $repeat');
283
    if (centerSlice != null)
284
      description.add('centerSlice: $centerSlice');
285
  }
286
}