image.dart 11 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 25 26
  ///
  /// The [scale], [alignment], [repeat], and [matchTextDirection] arguments
  /// 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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
    TextDirection textDirection,
  }) : assert(scale != null),
       assert(repeat != null),
       assert(alignment != null),
       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,
       _textDirection = textDirection {
Adam Barth's avatar
Adam Barth committed
56 57
    _updateColorFilter();
  }
58

59
  Alignment _resolvedAlignment;
Ian Hickson's avatar
Ian Hickson committed
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
  bool _flipHorizontally;

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

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

75
  /// The image to display.
76
  ui.Image get image => _image;
77
  ui.Image _image;
78
  set image(ui.Image value) {
79 80 81 82 83 84 85 86
    if (value == _image)
      return;
    _image = value;
    markNeedsPaint();
    if (_width == null || _height == null)
      markNeedsLayout();
  }

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

100 101 102 103
  /// 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.
104
  double get height => _height;
105
  double _height;
106
  set height(double value) {
107 108 109 110 111 112
    if (value == _height)
      return;
    _height = value;
    markNeedsLayout();
  }

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

Adam Barth's avatar
Adam Barth committed
126
  ColorFilter _colorFilter;
Adam Barth's avatar
Adam Barth committed
127 128 129 130 131

  void _updateColorFilter() {
    if (_color == null)
      _colorFilter = null;
    else
132
      _colorFilter = new ColorFilter.mode(_color, _colorBlendMode ?? BlendMode.srcIn);
Adam Barth's avatar
Adam Barth committed
133 134
  }

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

146 147 148 149 150 151 152 153 154 155 156 157 158
  /// 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;
159
    _colorBlendMode = value;
160 161 162 163
    _updateColorFilter();
    markNeedsPaint();
  }

Adam Barth's avatar
Adam Barth committed
164
  /// How to inscribe the image into the space allocated during layout.
165 166 167
  ///
  /// The default varies based on the other fields. See the discussion at
  /// [paintImage].
168 169 170
  BoxFit get fit => _fit;
  BoxFit _fit;
  set fit(BoxFit value) {
171 172 173 174 175 176
    if (value == _fit)
      return;
    _fit = value;
    markNeedsPaint();
  }

177
  /// How to align the image within its bounds.
Ian Hickson's avatar
Ian Hickson committed
178 179 180
  ///
  /// If this is set to a text-direction-dependent value, [textDirection] must
  /// not be null.
181 182 183
  AlignmentGeometry get alignment => _alignment;
  AlignmentGeometry _alignment;
  set alignment(AlignmentGeometry value) {
Ian Hickson's avatar
Ian Hickson committed
184
    assert(value != null);
185 186 187
    if (value == _alignment)
      return;
    _alignment = value;
Ian Hickson's avatar
Ian Hickson committed
188
    _markNeedResolution();
189 190
  }

191
  /// How to repeat this image if it doesn't fill its layout bounds.
192
  ImageRepeat get repeat => _repeat;
193
  ImageRepeat _repeat;
194
  set repeat(ImageRepeat value) {
Ian Hickson's avatar
Ian Hickson committed
195
    assert(value != null);
196 197 198 199 200 201
    if (value == _repeat)
      return;
    _repeat = value;
    markNeedsPaint();
  }

202 203 204 205 206 207 208 209 210
  /// 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;
211
  set centerSlice(Rect value) {
212 213 214 215 216 217
    if (value == _centerSlice)
      return;
    _centerSlice = value;
    markNeedsPaint();
  }

Ian Hickson's avatar
Ian Hickson committed
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
  /// 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();
  }

256
  /// Find a size for the render image within the given constraints.
257 258
  ///
  ///  - The dimensions of the RenderImage must fit within the constraints.
259
  ///  - The aspect ratio of the RenderImage matches the intrinsic aspect
260 261 262
  ///    ratio of the image.
  ///  - The RenderImage's dimension are maximal subject to being smaller than
  ///    the intrinsic size of the image.
263
  Size _sizeForConstraints(BoxConstraints constraints) {
264
    // Folds the given |width| and |height| into |constraints| so they can all
265 266 267 268
    // be treated uniformly.
    constraints = new BoxConstraints.tightFor(
      width: _width,
      height: _height
269
    ).enforce(constraints);
270

Adam Barth's avatar
Adam Barth committed
271
    if (_image == null)
272 273
      return constraints.smallest;

Adam Barth's avatar
Adam Barth committed
274 275 276 277
    return constraints.constrainSizeAndAttemptToPreserveAspectRatio(new Size(
      _image.width.toDouble() / _scale,
      _image.height.toDouble() / _scale
    ));
278 279
  }

280
  @override
281
  double computeMinIntrinsicWidth(double height) {
282
    assert(height >= 0.0);
283
    if (_width == null && _height == null)
284 285
      return 0.0;
    return _sizeForConstraints(new BoxConstraints.tightForFinite(height: height)).width;
286 287
  }

288
  @override
289
  double computeMaxIntrinsicWidth(double height) {
290
    assert(height >= 0.0);
291
    return _sizeForConstraints(new BoxConstraints.tightForFinite(height: height)).width;
292 293
  }

294
  @override
295
  double computeMinIntrinsicHeight(double width) {
296
    assert(width >= 0.0);
297
    if (_width == null && _height == null)
298 299
      return 0.0;
    return _sizeForConstraints(new BoxConstraints.tightForFinite(width: width)).height;
300 301
  }

302
  @override
303
  double computeMaxIntrinsicHeight(double width) {
304
    assert(width >= 0.0);
305
    return _sizeForConstraints(new BoxConstraints.tightForFinite(width: width)).height;
306 307
  }

308
  @override
309
  bool hitTestSelf(Offset position) => true;
Adam Barth's avatar
Adam Barth committed
310

311
  @override
312 313 314 315
  void performLayout() {
    size = _sizeForConstraints(constraints);
  }

316
  @override
317 318 319
  void paint(PaintingContext context, Offset offset) {
    if (_image == null)
      return;
Ian Hickson's avatar
Ian Hickson committed
320 321 322
    _resolve();
    assert(_resolvedAlignment != null);
    assert(_flipHorizontally != null);
323 324 325 326 327 328
    paintImage(
      canvas: context.canvas,
      rect: offset & size,
      image: _image,
      colorFilter: _colorFilter,
      fit: _fit,
Ian Hickson's avatar
Ian Hickson committed
329
      alignment: _resolvedAlignment,
330
      centerSlice: _centerSlice,
Ian Hickson's avatar
Ian Hickson committed
331 332
      repeat: _repeat,
      flipHorizontally: _flipHorizontally,
333 334 335
    );
  }

336
  @override
337 338 339 340 341 342 343 344 345 346 347 348 349 350
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<ui.Image>('image', image));
    properties.add(new DoubleProperty('width', width, defaultValue: null));
    properties.add(new DoubleProperty('height', height, defaultValue: null));
    properties.add(new DoubleProperty('scale', scale, defaultValue: 1.0));
    properties.add(new DiagnosticsProperty<Color>('color', color, defaultValue: null));
    properties.add(new EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
    properties.add(new EnumProperty<BoxFit>('fit', fit, defaultValue: null));
    properties.add(new DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
    properties.add(new EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
    properties.add(new DiagnosticsProperty<Rect>('centerSlice', centerSlice, defaultValue: null));
    properties.add(new FlagProperty('matchTextDirection', value: matchTextDirection, ifTrue: 'match text direction'));
    properties.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
351
  }
352
}