image.dart 7.53 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
class RenderImage extends RenderBox {
  RenderImage({
20
    ui.Image image,
21 22
    double width,
    double height,
23
    double scale: 1.0,
Adam Barth's avatar
Adam Barth committed
24
    Color color,
25
    ImageFit fit,
26
    FractionalOffset alignment,
Adam Barth's avatar
Adam Barth committed
27
    ImageRepeat repeat: ImageRepeat.noRepeat,
28
    Rect centerSlice
29 30 31
  }) : _image = image,
      _width = width,
      _height = height,
32
      _scale = scale,
Adam Barth's avatar
Adam Barth committed
33
      _color = color,
34
      _fit = fit,
35
      _alignment = alignment,
36
      _repeat = repeat,
Adam Barth's avatar
Adam Barth committed
37 38 39
      _centerSlice = centerSlice {
    _updateColorFilter();
  }
40

41
  /// The image to display.
42
  ui.Image get image => _image;
43
  ui.Image _image;
44
  void set image (ui.Image value) {
45 46 47 48 49 50 51 52
    if (value == _image)
      return;
    _image = value;
    markNeedsPaint();
    if (_width == null || _height == null)
      markNeedsLayout();
  }

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

66 67 68 69
  /// 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.
70
  double get height => _height;
71
  double _height;
72 73 74 75 76 77 78
  void set height (double value) {
    if (value == _height)
      return;
    _height = value;
    markNeedsLayout();
  }

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

Adam Barth's avatar
Adam Barth committed
92
  ColorFilter _colorFilter;
Adam Barth's avatar
Adam Barth committed
93 94 95 96 97 98 99 100 101 102 103 104 105 106

  // 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;
  void set color (Color value) {
    if (value == _color)
107
      return;
Adam Barth's avatar
Adam Barth committed
108 109
    _color = value;
    _updateColorFilter();
110 111 112
    markNeedsPaint();
  }

113
  /// How to inscribe the image into the place allocated during layout.
114
  ImageFit get fit => _fit;
115
  ImageFit _fit;
116 117 118 119 120 121 122
  void set fit (ImageFit value) {
    if (value == _fit)
      return;
    _fit = value;
    markNeedsPaint();
  }

123 124 125 126 127 128 129 130 131 132
  /// How to align the image within its bounds.
  FractionalOffset get alignment => _alignment;
  FractionalOffset _alignment;
  void set alignment (FractionalOffset value) {
    if (value == _alignment)
      return;
    _alignment = value;
    markNeedsPaint();
  }

133
  /// How to repeat this image if it doesn't fill its layout bounds.
134
  ImageRepeat get repeat => _repeat;
135
  ImageRepeat _repeat;
136 137 138 139 140 141 142
  void set repeat (ImageRepeat value) {
    if (value == _repeat)
      return;
    _repeat = value;
    markNeedsPaint();
  }

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
  /// 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;
  void set centerSlice (Rect value) {
    if (value == _centerSlice)
      return;
    _centerSlice = value;
    markNeedsPaint();
  }

  /// Find a size for the render image within the given constraints.
160 161 162 163 164 165
  ///
  ///  - 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.
166 167 168 169 170 171
  Size _sizeForConstraints(BoxConstraints constraints) {
    // Folds the given |width| and |height| into |cosntraints| so they can all
    // be treated uniformly.
    constraints = new BoxConstraints.tightFor(
      width: _width,
      height: _height
172
    ).enforce(constraints);
173 174 175 176

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

177 178
    double width = _image.width.toDouble() / _scale;
    double height = _image.height.toDouble() / _scale;
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
    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));
  }

206
  @override
207
  double getMinIntrinsicWidth(BoxConstraints constraints) {
Hixie's avatar
Hixie committed
208
    assert(constraints.debugAssertIsNormalized);
209 210 211 212 213
    if (_width == null && _height == null)
      return constraints.constrainWidth(0.0);
    return _sizeForConstraints(constraints).width;
  }

214
  @override
215
  double getMaxIntrinsicWidth(BoxConstraints constraints) {
Hixie's avatar
Hixie committed
216
    assert(constraints.debugAssertIsNormalized);
217 218 219
    return _sizeForConstraints(constraints).width;
  }

220
  @override
221
  double getMinIntrinsicHeight(BoxConstraints constraints) {
Hixie's avatar
Hixie committed
222
    assert(constraints.debugAssertIsNormalized);
223 224 225 226 227
    if (_width == null && _height == null)
      return constraints.constrainHeight(0.0);
    return _sizeForConstraints(constraints).height;
  }

228
  @override
229
  double getMaxIntrinsicHeight(BoxConstraints constraints) {
Hixie's avatar
Hixie committed
230
    assert(constraints.debugAssertIsNormalized);
231 232 233
    return _sizeForConstraints(constraints).height;
  }

234
  @override
Adam Barth's avatar
Adam Barth committed
235 236
  bool hitTestSelf(Point position) => true;

237
  @override
238 239 240 241
  void performLayout() {
    size = _sizeForConstraints(constraints);
  }

242
  @override
243 244 245 246 247 248 249 250 251
  void paint(PaintingContext context, Offset offset) {
    if (_image == null)
      return;
    paintImage(
      canvas: context.canvas,
      rect: offset & size,
      image: _image,
      colorFilter: _colorFilter,
      fit: _fit,
252
      alignment: _alignment,
253
      centerSlice: _centerSlice,
254 255 256 257
      repeat: _repeat
    );
  }

258
  @override
259 260 261
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('image: $image');
262
    if (width != null)
263
      description.add('width: $width');
264
    if (height != null)
265
      description.add('height: $height');
266
    if (scale != 1.0)
267
      description.add('scale: $scale');
268
    if (color != null)
269
      description.add('color: $color');
270
    if (fit != null)
271
      description.add('fit: $fit');
272
    if (alignment != null)
273
      description.add('alignment: $alignment');
274
    if (repeat != ImageRepeat.noRepeat)
275
      description.add('repeat: $repeat');
276
    if (centerSlice != null)
277
      description.add('centerSlice: $centerSlice');
278
  }
279
}