image.dart 12.3 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
export 'package:flutter/painting.dart' show
11
  BoxFit,
12 13
  ImageRepeat;

14
/// An image in the render tree.
15 16
///
/// The render image attempts to find a size for itself that fits in the given
17
/// constraints and preserves the image's intrinsic 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.
Ian Hickson's avatar
Ian Hickson committed
23
  ///
24
  /// The [scale], [alignment], [repeat], [matchTextDirection] and [filterQuality] arguments
Ian Hickson's avatar
Ian Hickson committed
25 26
  /// must not be null. The [textDirection] argument must not be null if
  /// [alignment] will need resolving or if [matchTextDirection] is true.
27
  RenderImage({
28
    ui.Image image,
29 30
    double width,
    double height,
31
    double scale = 1.0,
Adam Barth's avatar
Adam Barth committed
32
    Color color,
33
    BlendMode colorBlendMode,
34
    BoxFit fit,
35 36
    AlignmentGeometry alignment = Alignment.center,
    ImageRepeat repeat = ImageRepeat.noRepeat,
Ian Hickson's avatar
Ian Hickson committed
37
    Rect centerSlice,
38
    bool matchTextDirection = false,
Ian Hickson's avatar
Ian Hickson committed
39
    TextDirection textDirection,
40
    bool invertColors = false,
41
    FilterQuality filterQuality = FilterQuality.low,
Ian Hickson's avatar
Ian Hickson committed
42 43 44
  }) : assert(scale != null),
       assert(repeat != null),
       assert(alignment != null),
45
       assert(filterQuality != null),
Ian Hickson's avatar
Ian Hickson committed
46 47 48 49 50 51 52 53 54 55 56 57
       assert(matchTextDirection != null),
       _image = image,
       _width = width,
       _height = height,
       _scale = scale,
       _color = color,
       _colorBlendMode = colorBlendMode,
       _fit = fit,
       _alignment = alignment,
       _repeat = repeat,
       _centerSlice = centerSlice,
       _matchTextDirection = matchTextDirection,
58
       _invertColors = invertColors,
59 60
       _textDirection = textDirection,
       _filterQuality = filterQuality {
Adam Barth's avatar
Adam Barth committed
61 62
    _updateColorFilter();
  }
63

64
  Alignment _resolvedAlignment;
Ian Hickson's avatar
Ian Hickson committed
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
  bool _flipHorizontally;

  void _resolve() {
    if (_resolvedAlignment != null)
      return;
    _resolvedAlignment = alignment.resolve(textDirection);
    _flipHorizontally = matchTextDirection && textDirection == TextDirection.rtl;
  }

  void _markNeedResolution() {
    _resolvedAlignment = null;
    _flipHorizontally = null;
    markNeedsPaint();
  }

80
  /// The image to display.
81
  ui.Image get image => _image;
82
  ui.Image _image;
83
  set image(ui.Image value) {
84 85 86 87 88 89 90 91
    if (value == _image)
      return;
    _image = value;
    markNeedsPaint();
    if (_width == null || _height == null)
      markNeedsLayout();
  }

92
  /// If non-null, requires the image to have this width.
93 94 95
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
96
  double get width => _width;
97
  double _width;
98
  set width(double value) {
99 100 101 102 103 104
    if (value == _width)
      return;
    _width = value;
    markNeedsLayout();
  }

105 106 107 108
  /// 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.
109
  double get height => _height;
110
  double _height;
111
  set height(double value) {
112 113 114 115 116 117
    if (value == _height)
      return;
    _height = value;
    markNeedsLayout();
  }

118
  /// Specifies the image's scale.
119 120 121 122
  ///
  /// Used when determining the best display size for the image.
  double get scale => _scale;
  double _scale;
123
  set scale(double value) {
124
    assert(value != null);
125 126 127 128 129 130
    if (value == _scale)
      return;
    _scale = value;
    markNeedsLayout();
  }

Adam Barth's avatar
Adam Barth committed
131
  ColorFilter _colorFilter;
Adam Barth's avatar
Adam Barth committed
132 133 134 135 136

  void _updateColorFilter() {
    if (_color == null)
      _colorFilter = null;
    else
137
      _colorFilter = ColorFilter.mode(_color, _colorBlendMode ?? BlendMode.srcIn);
Adam Barth's avatar
Adam Barth committed
138 139
  }

140
  /// If non-null, this color is blended with each image pixel using [colorBlendMode].
Adam Barth's avatar
Adam Barth committed
141 142
  Color get color => _color;
  Color _color;
143
  set color(Color value) {
Adam Barth's avatar
Adam Barth committed
144
    if (value == _color)
145
      return;
Adam Barth's avatar
Adam Barth committed
146 147
    _color = value;
    _updateColorFilter();
148 149 150
    markNeedsPaint();
  }

151 152 153 154 155 156 157 158
  /// Used to set the filterQuality of the image
  /// Use the [FilterQuality.low] quality setting to scale the image, which corresponds to
  /// bilinear interpolation, rather than the default [FilterQuality.none] which corresponds
  /// to nearest-neighbor.
  FilterQuality get filterQuality => _filterQuality;
  FilterQuality _filterQuality;
  set filterQuality(FilterQuality value) {
    assert(value != null);
159
    if (value == _filterQuality)
160 161 162 163 164 165
      return;
    _filterQuality = value;
    markNeedsPaint();
  }


166 167 168 169 170 171 172 173 174 175 176 177 178
  /// Used to combine [color] with this image.
  ///
  /// The default is [BlendMode.srcIn]. In terms of the blend mode, [color] is
  /// the source and this image is the destination.
  ///
  /// See also:
  ///
  ///  * [BlendMode], which includes an illustration of the effect of each blend mode.
  BlendMode get colorBlendMode => _colorBlendMode;
  BlendMode _colorBlendMode;
  set colorBlendMode(BlendMode value) {
    if (value == _colorBlendMode)
      return;
179
    _colorBlendMode = value;
180 181 182 183
    _updateColorFilter();
    markNeedsPaint();
  }

Adam Barth's avatar
Adam Barth committed
184
  /// How to inscribe the image into the space allocated during layout.
185 186 187
  ///
  /// The default varies based on the other fields. See the discussion at
  /// [paintImage].
188 189 190
  BoxFit get fit => _fit;
  BoxFit _fit;
  set fit(BoxFit value) {
191 192 193 194 195 196
    if (value == _fit)
      return;
    _fit = value;
    markNeedsPaint();
  }

197
  /// How to align the image within its bounds.
Ian Hickson's avatar
Ian Hickson committed
198 199 200
  ///
  /// If this is set to a text-direction-dependent value, [textDirection] must
  /// not be null.
201 202 203
  AlignmentGeometry get alignment => _alignment;
  AlignmentGeometry _alignment;
  set alignment(AlignmentGeometry value) {
Ian Hickson's avatar
Ian Hickson committed
204
    assert(value != null);
205 206 207
    if (value == _alignment)
      return;
    _alignment = value;
Ian Hickson's avatar
Ian Hickson committed
208
    _markNeedResolution();
209 210
  }

211
  /// How to repeat this image if it doesn't fill its layout bounds.
212
  ImageRepeat get repeat => _repeat;
213
  ImageRepeat _repeat;
214
  set repeat(ImageRepeat value) {
Ian Hickson's avatar
Ian Hickson committed
215
    assert(value != null);
216 217 218 219 220 221
    if (value == _repeat)
      return;
    _repeat = value;
    markNeedsPaint();
  }

222 223 224 225 226 227 228 229 230
  /// 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;
231
  set centerSlice(Rect value) {
232 233 234 235 236 237
    if (value == _centerSlice)
      return;
    _centerSlice = value;
    markNeedsPaint();
  }

238 239 240 241 242 243 244 245 246 247 248 249 250 251
  /// Whether to invert the colors of the image.
  ///
  /// inverting the colors of an image applies a new color filter to the paint.
  /// If there is another specified color filter, the invert will be applied
  /// after it. This is primarily used for implementing smart invert on iOS.
  bool get invertColors => _invertColors;
  bool _invertColors;
  set invertColors(bool value) {
    if (value == _invertColors)
      return;
    _invertColors = value;
    markNeedsPaint();
  }

Ian Hickson's avatar
Ian Hickson committed
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
  /// Whether to paint the image in the direction of the [TextDirection].
  ///
  /// If this is true, then in [TextDirection.ltr] contexts, the image will be
  /// drawn with its origin in the top left (the "normal" painting direction for
  /// images); and in [TextDirection.rtl] contexts, the image will be drawn with
  /// a scaling factor of -1 in the horizontal direction so that the origin is
  /// in the top right.
  ///
  /// This is occasionally used with images in right-to-left environments, for
  /// images that were designed for left-to-right locales. Be careful, when
  /// using this, to not flip images with integral shadows, text, or other
  /// effects that will look incorrect when flipped.
  ///
  /// If this is set to true, [textDirection] must not be null.
  bool get matchTextDirection => _matchTextDirection;
  bool _matchTextDirection;
  set matchTextDirection(bool value) {
    assert(value != null);
    if (value == _matchTextDirection)
      return;
    _matchTextDirection = value;
    _markNeedResolution();
  }

  /// The text direction with which to resolve [alignment].
  ///
  /// This may be changed to null, but only after the [alignment] and
  /// [matchTextDirection] properties have been changed to values that do not
  /// depend on the direction.
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection value) {
    if (_textDirection == value)
      return;
    _textDirection = value;
    _markNeedResolution();
  }

290
  /// Find a size for the render image within the given constraints.
291 292
  ///
  ///  - The dimensions of the RenderImage must fit within the constraints.
293
  ///  - The aspect ratio of the RenderImage matches the intrinsic aspect
294 295 296
  ///    ratio of the image.
  ///  - The RenderImage's dimension are maximal subject to being smaller than
  ///    the intrinsic size of the image.
297
  Size _sizeForConstraints(BoxConstraints constraints) {
298
    // Folds the given |width| and |height| into |constraints| so they can all
299
    // be treated uniformly.
300
    constraints = BoxConstraints.tightFor(
301
      width: _width,
302
      height: _height,
303
    ).enforce(constraints);
304

Adam Barth's avatar
Adam Barth committed
305
    if (_image == null)
306 307
      return constraints.smallest;

308
    return constraints.constrainSizeAndAttemptToPreserveAspectRatio(Size(
Adam Barth's avatar
Adam Barth committed
309
      _image.width.toDouble() / _scale,
310
      _image.height.toDouble() / _scale,
Adam Barth's avatar
Adam Barth committed
311
    ));
312 313
  }

314
  @override
315
  double computeMinIntrinsicWidth(double height) {
316
    assert(height >= 0.0);
317
    if (_width == null && _height == null)
318
      return 0.0;
319
    return _sizeForConstraints(BoxConstraints.tightForFinite(height: height)).width;
320 321
  }

322
  @override
323
  double computeMaxIntrinsicWidth(double height) {
324
    assert(height >= 0.0);
325
    return _sizeForConstraints(BoxConstraints.tightForFinite(height: height)).width;
326 327
  }

328
  @override
329
  double computeMinIntrinsicHeight(double width) {
330
    assert(width >= 0.0);
331
    if (_width == null && _height == null)
332
      return 0.0;
333
    return _sizeForConstraints(BoxConstraints.tightForFinite(width: width)).height;
334 335
  }

336
  @override
337
  double computeMaxIntrinsicHeight(double width) {
338
    assert(width >= 0.0);
339
    return _sizeForConstraints(BoxConstraints.tightForFinite(width: width)).height;
340 341
  }

342
  @override
343
  bool hitTestSelf(Offset position) => true;
Adam Barth's avatar
Adam Barth committed
344

345
  @override
346 347 348 349
  void performLayout() {
    size = _sizeForConstraints(constraints);
  }

350
  @override
351 352 353
  void paint(PaintingContext context, Offset offset) {
    if (_image == null)
      return;
Ian Hickson's avatar
Ian Hickson committed
354 355 356
    _resolve();
    assert(_resolvedAlignment != null);
    assert(_flipHorizontally != null);
357 358 359 360
    paintImage(
      canvas: context.canvas,
      rect: offset & size,
      image: _image,
361
      scale: _scale,
362 363
      colorFilter: _colorFilter,
      fit: _fit,
Ian Hickson's avatar
Ian Hickson committed
364
      alignment: _resolvedAlignment,
365
      centerSlice: _centerSlice,
Ian Hickson's avatar
Ian Hickson committed
366 367
      repeat: _repeat,
      flipHorizontally: _flipHorizontally,
368
      invertColors: invertColors,
369
      filterQuality: _filterQuality,
370 371 372
    );
  }

373
  @override
374 375
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
376 377 378 379
    properties.add(DiagnosticsProperty<ui.Image>('image', image));
    properties.add(DoubleProperty('width', width, defaultValue: null));
    properties.add(DoubleProperty('height', height, defaultValue: null));
    properties.add(DoubleProperty('scale', scale, defaultValue: 1.0));
380
    properties.add(ColorProperty('color', color, defaultValue: null));
381 382 383 384 385 386 387
    properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
    properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null));
    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
    properties.add(EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
    properties.add(DiagnosticsProperty<Rect>('centerSlice', centerSlice, defaultValue: null));
    properties.add(FlagProperty('matchTextDirection', value: matchTextDirection, ifTrue: 'match text direction'));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
388
    properties.add(DiagnosticsProperty<bool>('invertColors', invertColors));
389
    properties.add(EnumProperty<FilterQuality>('filterQuality', filterQuality));
390
  }
391
}