blob: 25b2e84a90b0270019f8bc6661f5443045bf672e [file] [log] [blame]
// 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.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_fill.png)
fill,
/// As large as possible while still containing the source entirely within the
/// target box.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_contain.png)
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}
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_cover.png)
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}
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_fitWidth.png)
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}
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_fitHeight.png)
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}
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_none.png)
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`.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/box_fit_scaleDown.png)
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);
}