1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'basic_types.dart';
/// How a box should be inscribed into another box.
///
/// See also:
///
/// * [applyBoxFit], which applies the sizing semantics of these values (though
/// not the alignment semantics).
enum BoxFit {
/// Fill the target box by distorting the source's aspect ratio.
///
/// 
fill,
/// As large as possible while still containing the source entirely within the
/// target box.
///
/// 
contain,
/// As small as possible while still covering the entire target box.
///
/// {@template flutter.painting.BoxFit.cover}
/// To actually clip the content, use `clipBehavior: Clip.hardEdge` alongside
/// this in a [FittedBox].
/// {@endtemplate}
///
/// 
cover,
/// Make sure the full width of the source is shown, regardless of
/// whether this means the source overflows the target box vertically.
///
/// {@macro flutter.painting.BoxFit.cover}
///
/// 
fitWidth,
/// Make sure the full height of the source is shown, regardless of
/// whether this means the source overflows the target box horizontally.
///
/// {@macro flutter.painting.BoxFit.cover}
///
/// 
fitHeight,
/// Align the source within the target box (by default, centering) and discard
/// any portions of the source that lie outside the box.
///
/// The source image is not resized.
///
/// {@macro flutter.painting.BoxFit.cover}
///
/// 
none,
/// 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.
///
/// This is the same as `contain` if that would shrink the image, otherwise it
/// is the same as `none`.
///
/// 
scaleDown,
}
/// The pair of sizes returned by [applyBoxFit].
@immutable
class FittedSizes {
/// Creates an object to store a pair of sizes,
/// as would be returned by [applyBoxFit].
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;
}
/// Apply a [BoxFit] value.
///
/// 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
/// that is being fitted, and the `outputSize` gives the size of the rectangle
/// into which the source is to be drawn.
///
/// 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
/// 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
/// smaller.
///
/// The [FittedSizes.destination] size is the subpart of the `outputSize` in
/// which to paint the (possibly cropped) source. If the
/// [FittedSizes.destination] size is smaller than the `outputSize` then the
/// source is being letterboxed (or pillarboxed).
///
/// 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
/// 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.
///
/// {@tool snippet}
///
/// This function paints a [dart:ui.Image] `image` onto the [Rect] `outputRect` on a
/// [Canvas] `canvas`, using a [Paint] `paint`, applying the [BoxFit] algorithm
/// `fit`:
///
/// ```dart
/// void paintImage(ui.Image image, Rect outputRect, Canvas canvas, Paint paint, BoxFit fit) {
/// final Size imageSize = Size(image.width.toDouble(), image.height.toDouble());
/// final FittedSizes sizes = applyBoxFit(fit, imageSize, outputRect.size);
/// final Rect inputSubrect = Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
/// final Rect outputSubrect = Alignment.center.inscribe(sizes.destination, outputRect);
/// canvas.drawImageRect(image, inputSubrect, outputSubrect, paint);
/// }
/// ```
/// {@end-tool}
///
/// 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.
FittedSizes applyBoxFit(BoxFit fit, Size inputSize, Size outputSize) {
if (inputSize.height <= 0.0 || inputSize.width <= 0.0 || outputSize.height <= 0.0 || outputSize.width <= 0.0) {
return const FittedSizes(Size.zero, Size.zero);
}
Size sourceSize, destinationSize;
switch (fit) {
case BoxFit.fill:
sourceSize = inputSize;
destinationSize = outputSize;
break;
case BoxFit.contain:
sourceSize = inputSize;
if (outputSize.width / outputSize.height > sourceSize.width / sourceSize.height) {
destinationSize = Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
} else {
destinationSize = Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
}
break;
case BoxFit.cover:
if (outputSize.width / outputSize.height > inputSize.width / inputSize.height) {
sourceSize = Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
} else {
sourceSize = Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
}
destinationSize = outputSize;
break;
case BoxFit.fitWidth:
sourceSize = Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
destinationSize = Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
break;
case BoxFit.fitHeight:
sourceSize = Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
destinationSize = Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
break;
case BoxFit.none:
sourceSize = Size(math.min(inputSize.width, outputSize.width), math.min(inputSize.height, outputSize.height));
destinationSize = sourceSize;
break;
case BoxFit.scaleDown:
sourceSize = inputSize;
destinationSize = inputSize;
final double aspectRatio = inputSize.width / inputSize.height;
if (destinationSize.height > outputSize.height) {
destinationSize = Size(outputSize.height * aspectRatio, outputSize.height);
}
if (destinationSize.width > outputSize.width) {
destinationSize = Size(outputSize.width, outputSize.width / aspectRatio);
}
break;
}
return FittedSizes(sourceSize, destinationSize);
}