image.dart 14.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// 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 'package:flutter/animation.dart';

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

12
export 'package:flutter/painting.dart' show
13
  BoxFit,
14 15
  ImageRepeat;

16
/// An image in the render tree.
17 18
///
/// The render image attempts to find a size for itself that fits in the given
19
/// constraints and preserves the image's intrinsic aspect ratio.
20 21 22
///
/// The image is painted using [paintImage], which describes the meanings of the
/// various fields on this class in more detail.
23
class RenderImage extends RenderBox {
24
  /// Creates a render box that displays an image.
Ian Hickson's avatar
Ian Hickson committed
25
  ///
26
  /// The [scale], [alignment], [repeat], [matchTextDirection] and [filterQuality] arguments
Ian Hickson's avatar
Ian Hickson committed
27 28
  /// must not be null. The [textDirection] argument must not be null if
  /// [alignment] will need resolving or if [matchTextDirection] is true.
29
  RenderImage({
30
    ui.Image? image,
31
    this.debugImageLabel,
32 33
    double? width,
    double? height,
34
    double scale = 1.0,
35
    Color? color,
36
    Animation<double>? opacity,
37 38
    BlendMode? colorBlendMode,
    BoxFit? fit,
39 40
    AlignmentGeometry alignment = Alignment.center,
    ImageRepeat repeat = ImageRepeat.noRepeat,
41
    Rect? centerSlice,
42
    bool matchTextDirection = false,
43
    TextDirection? textDirection,
44
    bool invertColors = false,
45
    bool isAntiAlias = false,
46
    FilterQuality filterQuality = FilterQuality.low,
Ian Hickson's avatar
Ian Hickson committed
47 48 49
  }) : assert(scale != null),
       assert(repeat != null),
       assert(alignment != null),
50
       assert(filterQuality != null),
Ian Hickson's avatar
Ian Hickson committed
51
       assert(matchTextDirection != null),
52
       assert(isAntiAlias != null),
Ian Hickson's avatar
Ian Hickson committed
53 54 55 56 57
       _image = image,
       _width = width,
       _height = height,
       _scale = scale,
       _color = color,
58
       _opacity = opacity,
Ian Hickson's avatar
Ian Hickson committed
59 60 61 62 63 64
       _colorBlendMode = colorBlendMode,
       _fit = fit,
       _alignment = alignment,
       _repeat = repeat,
       _centerSlice = centerSlice,
       _matchTextDirection = matchTextDirection,
65
       _invertColors = invertColors,
66
       _textDirection = textDirection,
67
       _isAntiAlias = isAntiAlias,
68
       _filterQuality = filterQuality {
Adam Barth's avatar
Adam Barth committed
69 70
    _updateColorFilter();
  }
71

72 73
  Alignment? _resolvedAlignment;
  bool? _flipHorizontally;
Ian Hickson's avatar
Ian Hickson committed
74 75 76 77 78 79 80 81 82 83 84 85 86 87

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

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

88
  /// The image to display.
89 90 91
  ui.Image? get image => _image;
  ui.Image? _image;
  set image(ui.Image? value) {
92
    if (value == _image) {
93
      return;
94 95 96 97 98 99 100 101
    }
    // If we get a clone of our image, it's the same underlying native data -
    // dispose of the new clone and return early.
    if (value != null && _image != null && value.isCloneOf(_image!)) {
      value.dispose();
      return;
    }
    _image?.dispose();
102 103 104 105 106 107
    _image = value;
    markNeedsPaint();
    if (_width == null || _height == null)
      markNeedsLayout();
  }

108
  /// A string used to identify the source of the image.
109
  String? debugImageLabel;
110

111
  /// If non-null, requires the image to have this width.
112 113 114
  ///
  /// If null, the image will pick a size that best preserves its intrinsic
  /// aspect ratio.
115 116 117
  double? get width => _width;
  double? _width;
  set width(double? value) {
118 119 120 121 122 123
    if (value == _width)
      return;
    _width = value;
    markNeedsLayout();
  }

124 125 126 127
  /// 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.
128 129 130
  double? get height => _height;
  double? _height;
  set height(double? value) {
131 132 133 134 135 136
    if (value == _height)
      return;
    _height = value;
    markNeedsLayout();
  }

137
  /// Specifies the image's scale.
138 139 140 141
  ///
  /// Used when determining the best display size for the image.
  double get scale => _scale;
  double _scale;
142
  set scale(double value) {
143
    assert(value != null);
144 145 146 147 148 149
    if (value == _scale)
      return;
    _scale = value;
    markNeedsLayout();
  }

150
  ColorFilter? _colorFilter;
Adam Barth's avatar
Adam Barth committed
151 152 153 154 155

  void _updateColorFilter() {
    if (_color == null)
      _colorFilter = null;
    else
156
      _colorFilter = ColorFilter.mode(_color!, _colorBlendMode ?? BlendMode.srcIn);
Adam Barth's avatar
Adam Barth committed
157 158
  }

159
  /// If non-null, this color is blended with each image pixel using [colorBlendMode].
160 161 162
  Color? get color => _color;
  Color? _color;
  set color(Color? value) {
Adam Barth's avatar
Adam Barth committed
163
    if (value == _color)
164
      return;
Adam Barth's avatar
Adam Barth committed
165 166
    _color = value;
    _updateColorFilter();
167 168 169
    markNeedsPaint();
  }

170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
  /// If non-null, the value from the [Animation] is multiplied with the opacity
  /// of each image pixel before painting onto the canvas.
  Animation<double>? get opacity => _opacity;
  Animation<double>? _opacity;
  set opacity(Animation<double>? value) {
    if (value == _opacity)
      return;

    if (attached)
      _opacity?.removeListener(markNeedsPaint);
    _opacity = value;
    if (attached)
      value?.addListener(markNeedsPaint);
  }

185 186 187 188 189 190 191 192
  /// 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);
193
    if (value == _filterQuality)
194 195 196 197 198 199
      return;
    _filterQuality = value;
    markNeedsPaint();
  }


200 201 202 203 204 205 206 207
  /// 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.
208 209 210
  BlendMode? get colorBlendMode => _colorBlendMode;
  BlendMode? _colorBlendMode;
  set colorBlendMode(BlendMode? value) {
211 212
    if (value == _colorBlendMode)
      return;
213
    _colorBlendMode = value;
214 215 216 217
    _updateColorFilter();
    markNeedsPaint();
  }

Adam Barth's avatar
Adam Barth committed
218
  /// How to inscribe the image into the space allocated during layout.
219 220 221
  ///
  /// The default varies based on the other fields. See the discussion at
  /// [paintImage].
222 223 224
  BoxFit? get fit => _fit;
  BoxFit? _fit;
  set fit(BoxFit? value) {
225 226 227 228 229 230
    if (value == _fit)
      return;
    _fit = value;
    markNeedsPaint();
  }

231
  /// How to align the image within its bounds.
Ian Hickson's avatar
Ian Hickson committed
232 233 234
  ///
  /// If this is set to a text-direction-dependent value, [textDirection] must
  /// not be null.
235 236 237
  AlignmentGeometry get alignment => _alignment;
  AlignmentGeometry _alignment;
  set alignment(AlignmentGeometry value) {
Ian Hickson's avatar
Ian Hickson committed
238
    assert(value != null);
239 240 241
    if (value == _alignment)
      return;
    _alignment = value;
Ian Hickson's avatar
Ian Hickson committed
242
    _markNeedResolution();
243 244
  }

245
  /// How to repeat this image if it doesn't fill its layout bounds.
246
  ImageRepeat get repeat => _repeat;
247
  ImageRepeat _repeat;
248
  set repeat(ImageRepeat value) {
Ian Hickson's avatar
Ian Hickson committed
249
    assert(value != null);
250 251 252 253 254 255
    if (value == _repeat)
      return;
    _repeat = value;
    markNeedsPaint();
  }

256 257 258 259 260 261 262
  /// 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.
263 264 265
  Rect? get centerSlice => _centerSlice;
  Rect? _centerSlice;
  set centerSlice(Rect? value) {
266 267 268 269 270 271
    if (value == _centerSlice)
      return;
    _centerSlice = value;
    markNeedsPaint();
  }

272 273 274 275 276 277 278 279 280 281 282 283 284 285
  /// 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
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
  /// 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.
315 316 317
  TextDirection? get textDirection => _textDirection;
  TextDirection? _textDirection;
  set textDirection(TextDirection? value) {
Ian Hickson's avatar
Ian Hickson committed
318 319 320 321 322 323
    if (_textDirection == value)
      return;
    _textDirection = value;
    _markNeedResolution();
  }

324 325 326 327 328 329 330 331 332 333 334 335 336 337
  /// Whether to paint the image with anti-aliasing.
  ///
  /// Anti-aliasing alleviates the sawtooth artifact when the image is rotated.
  bool get isAntiAlias => _isAntiAlias;
  bool _isAntiAlias;
  set isAntiAlias(bool value) {
    if (_isAntiAlias == value) {
      return;
    }
    assert(value != null);
    _isAntiAlias = value;
    markNeedsPaint();
  }

338
  /// Find a size for the render image within the given constraints.
339 340
  ///
  ///  - The dimensions of the RenderImage must fit within the constraints.
341
  ///  - The aspect ratio of the RenderImage matches the intrinsic aspect
342 343 344
  ///    ratio of the image.
  ///  - The RenderImage's dimension are maximal subject to being smaller than
  ///    the intrinsic size of the image.
345
  Size _sizeForConstraints(BoxConstraints constraints) {
346
    // Folds the given |width| and |height| into |constraints| so they can all
347
    // be treated uniformly.
348
    constraints = BoxConstraints.tightFor(
349
      width: _width,
350
      height: _height,
351
    ).enforce(constraints);
352

Adam Barth's avatar
Adam Barth committed
353
    if (_image == null)
354 355
      return constraints.smallest;

356
    return constraints.constrainSizeAndAttemptToPreserveAspectRatio(Size(
357 358
      _image!.width.toDouble() / _scale,
      _image!.height.toDouble() / _scale,
Adam Barth's avatar
Adam Barth committed
359
    ));
360 361
  }

362
  @override
363
  double computeMinIntrinsicWidth(double height) {
364
    assert(height >= 0.0);
365
    if (_width == null && _height == null)
366
      return 0.0;
367
    return _sizeForConstraints(BoxConstraints.tightForFinite(height: height)).width;
368 369
  }

370
  @override
371
  double computeMaxIntrinsicWidth(double height) {
372
    assert(height >= 0.0);
373
    return _sizeForConstraints(BoxConstraints.tightForFinite(height: height)).width;
374 375
  }

376
  @override
377
  double computeMinIntrinsicHeight(double width) {
378
    assert(width >= 0.0);
379
    if (_width == null && _height == null)
380
      return 0.0;
381
    return _sizeForConstraints(BoxConstraints.tightForFinite(width: width)).height;
382 383
  }

384
  @override
385
  double computeMaxIntrinsicHeight(double width) {
386
    assert(width >= 0.0);
387
    return _sizeForConstraints(BoxConstraints.tightForFinite(width: width)).height;
388 389
  }

390
  @override
391
  bool hitTestSelf(Offset position) => true;
Adam Barth's avatar
Adam Barth committed
392

393 394 395 396 397
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    return _sizeForConstraints(constraints);
  }

398
  @override
399 400 401 402
  void performLayout() {
    size = _sizeForConstraints(constraints);
  }

403 404 405 406 407 408 409 410 411 412 413 414
  @override
  void attach(covariant PipelineOwner owner) {
    super.attach(owner);
    _opacity?.addListener(markNeedsPaint);
  }

  @override
  void detach() {
    _opacity?.removeListener(markNeedsPaint);
    super.detach();
  }

415
  @override
416 417 418
  void paint(PaintingContext context, Offset offset) {
    if (_image == null)
      return;
Ian Hickson's avatar
Ian Hickson committed
419 420 421
    _resolve();
    assert(_resolvedAlignment != null);
    assert(_flipHorizontally != null);
422 423 424
    paintImage(
      canvas: context.canvas,
      rect: offset & size,
425
      image: _image!,
426
      debugImageLabel: debugImageLabel,
427
      scale: _scale,
428
      opacity: _opacity?.value ?? 1.0,
429 430
      colorFilter: _colorFilter,
      fit: _fit,
431
      alignment: _resolvedAlignment!,
432
      centerSlice: _centerSlice,
Ian Hickson's avatar
Ian Hickson committed
433
      repeat: _repeat,
434
      flipHorizontally: _flipHorizontally!,
435
      invertColors: invertColors,
436
      filterQuality: _filterQuality,
437
      isAntiAlias: _isAntiAlias,
438 439 440
    );
  }

441 442 443 444 445 446 447
  @override
  void dispose() {
    _image?.dispose();
    _image = null;
    super.dispose();
  }

448
  @override
449 450
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
451 452 453 454
    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));
455
    properties.add(ColorProperty('color', color, defaultValue: null));
456
    properties.add(DiagnosticsProperty<Animation<double>?>('opacity', opacity, defaultValue: null));
457 458 459 460 461 462 463
    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));
464
    properties.add(DiagnosticsProperty<bool>('invertColors', invertColors));
465
    properties.add(EnumProperty<FilterQuality>('filterQuality', filterQuality));
466
  }
467
}