box_fit.dart 7.64 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

6 7
import 'dart:math' as math;

8 9
import 'package:flutter/foundation.dart';

10 11
import 'basic_types.dart';

12
/// How a box should be inscribed into another box.
13
///
14 15 16 17
/// See also:
///
///  * [applyBoxFit], which applies the sizing semantics of these values (though
///    not the alignment semantics).
18 19
enum BoxFit {
  /// Fill the target box by distorting the source's aspect ratio.
20
  ///
21
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_fill.png)
22 23
  fill,

24 25
  /// As large as possible while still containing the source entirely within the
  /// target box.
26
  ///
27
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_contain.png)
28 29
  contain,

30
  /// As small as possible while still covering the entire target box.
31
  ///
32
  /// {@template flutter.painting.BoxFit.cover}
33 34 35 36
  /// To actually clip the content, use `clipBehavior: Clip.hardEdge` alongside
  /// this in a [FittedBox].
  /// {@endtemplate}
  ///
37
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_cover.png)
38 39
  cover,

40 41
  /// Make sure the full width of the source is shown, regardless of
  /// whether this means the source overflows the target box vertically.
42
  ///
43
  /// {@macro flutter.painting.BoxFit.cover}
44
  ///
45
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_fitWidth.png)
46 47
  fitWidth,

48 49
  /// Make sure the full height of the source is shown, regardless of
  /// whether this means the source overflows the target box horizontally.
50
  ///
51
  /// {@macro flutter.painting.BoxFit.cover}
52
  ///
53
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_fitHeight.png)
54 55
  fitHeight,

56 57
  /// Align the source within the target box (by default, centering) and discard
  /// any portions of the source that lie outside the box.
58 59 60
  ///
  /// The source image is not resized.
  ///
61
  /// {@macro flutter.painting.BoxFit.cover}
62
  ///
63
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_none.png)
64 65
  none,

66 67 68
  /// Align the source within the target box (by default, centering) and, if
  /// necessary, scale the source down to ensure that the source fits within the
  /// box.
69 70 71 72
  ///
  /// This is the same as `contain` if that would shrink the image, otherwise it
  /// is the same as `none`.
  ///
73
  /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_scaleDown.png)
74
  scaleDown,
75 76
}

77
/// The pair of sizes returned by [applyBoxFit].
78
@immutable
79 80
class FittedSizes {
  /// Creates an object to store a pair of sizes,
81
  /// as would be returned by [applyBoxFit].
82 83 84 85 86 87 88 89 90
  const FittedSizes(this.source, this.destination);

  /// The size of the part of the input to show on the output.
  final Size source;

  /// The size of the part of the output on which to show the input.
  final Size destination;
}

91
/// Apply a [BoxFit] value.
92
///
93 94 95
/// The arguments to this method, in addition to the [BoxFit] value to apply,
/// are two sizes, ostensibly the sizes of an input box and an output box.
/// Specifically, the `inputSize` argument gives the size of the complete source
96
/// that is being fitted, and the `outputSize` gives the size of the rectangle
97
/// into which the source is to be drawn.
98 99 100 101 102
///
/// This function then returns two sizes, combined into a single [FittedSizes]
/// object.
///
/// The [FittedSizes.source] size is the subpart of the `inputSize` that is to
103 104
/// be shown. If the entire input source is shown, then this will equal the
/// `inputSize`, but if the input source is to be cropped down, this may be
105 106 107
/// smaller.
///
/// The [FittedSizes.destination] size is the subpart of the `outputSize` in
108
/// which to paint the (possibly cropped) source. If the
109
/// [FittedSizes.destination] size is smaller than the `outputSize` then the
110
/// source is being letterboxed (or pillarboxed).
111 112 113 114
///
/// This method does not express an opinion regarding the alignment of the
/// source and destination sizes within the input and output rectangles.
/// Typically they are centered (this is what [BoxDecoration] does, for
115 116 117
/// instance, and is how [BoxFit] is defined). The [Alignment] class provides a
/// convenience function, [Alignment.inscribe], for resolving the sizes to
/// rects, as shown in the example below.
118
///
119
/// {@tool snippet}
120
///
121 122
/// This function paints a [dart:ui.Image] `image` onto the [Rect] `outputRect` on a
/// [Canvas] `canvas`, using a [Paint] `paint`, applying the [BoxFit] algorithm
123 124 125
/// `fit`:
///
/// ```dart
126
/// void paintImage(ui.Image image, Rect outputRect, Canvas canvas, Paint paint, BoxFit fit) {
127
///   final Size imageSize = Size(image.width.toDouble(), image.height.toDouble());
128
///   final FittedSizes sizes = applyBoxFit(fit, imageSize, outputRect.size);
129 130
///   final Rect inputSubrect = Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
///   final Rect outputSubrect = Alignment.center.inscribe(sizes.destination, outputRect);
131 132
///   canvas.drawImageRect(image, inputSubrect, outputSubrect, paint);
/// }
133
/// ```
134
/// {@end-tool}
Ian Hickson's avatar
Ian Hickson committed
135 136 137 138 139 140 141
///
/// See also:
///
///  * [FittedBox], a widget that applies this algorithm to another widget.
///  * [paintImage], a function that applies this algorithm to images for painting.
///  * [DecoratedBox], [BoxDecoration], and [DecorationImage], which together
///    provide access to [paintImage] at the widgets layer.
142
FittedSizes applyBoxFit(BoxFit fit, Size inputSize, Size outputSize) {
143 144 145
  if (inputSize.height <= 0.0 || inputSize.width <= 0.0 || outputSize.height <= 0.0 || outputSize.width <= 0.0)
    return const FittedSizes(Size.zero, Size.zero);

146 147
  Size sourceSize, destinationSize;
  switch (fit) {
148
    case BoxFit.fill:
149 150 151
      sourceSize = inputSize;
      destinationSize = outputSize;
      break;
152
    case BoxFit.contain:
153 154
      sourceSize = inputSize;
      if (outputSize.width / outputSize.height > sourceSize.width / sourceSize.height)
155
        destinationSize = Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
156
      else
157
        destinationSize = Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
158
      break;
159
    case BoxFit.cover:
160
      if (outputSize.width / outputSize.height > inputSize.width / inputSize.height) {
161
        sourceSize = Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
162
      } else {
163
        sourceSize = Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
164 165 166
      }
      destinationSize = outputSize;
      break;
167
    case BoxFit.fitWidth:
168 169
      sourceSize = Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
      destinationSize = Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
170
      break;
171
    case BoxFit.fitHeight:
172 173
      sourceSize = Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
      destinationSize = Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
174
      break;
175
    case BoxFit.none:
176
      sourceSize = Size(math.min(inputSize.width, outputSize.width), math.min(inputSize.height, outputSize.height));
177 178
      destinationSize = sourceSize;
      break;
179
    case BoxFit.scaleDown:
180 181 182 183
      sourceSize = inputSize;
      destinationSize = inputSize;
      final double aspectRatio = inputSize.width / inputSize.height;
      if (destinationSize.height > outputSize.height)
184
        destinationSize = Size(outputSize.height * aspectRatio, outputSize.height);
185
      if (destinationSize.width > outputSize.width)
186
        destinationSize = Size(outputSize.width, outputSize.width / aspectRatio);
187 188
      break;
  }
189
  return FittedSizes(sourceSize, destinationSize);
Adam Barth's avatar
Adam Barth committed
190
}