box_fit.dart 5.96 KB
Newer Older
1 2 3 4 5 6
// Copyright 2016 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.

import 'dart:math' as math;

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

9 10
import 'basic_types.dart';

11
/// How a box should be inscribed into another box.
12
///
13
/// See also [applyBoxFit], which applies the sizing semantics of these values
14
/// (though not the alignment semantics).
15 16
enum BoxFit {
  /// Fill the target box by distorting the source's aspect ratio.
17 18
  fill,

19 20
  /// As large as possible while still containing the source entirely within the
  /// target box.
21 22
  contain,

23
  /// As small as possible while still covering the entire target box.
24 25
  cover,

26 27
  /// Make sure the full width of the source is shown, regardless of
  /// whether this means the source overflows the target box vertically.
28 29
  fitWidth,

30 31
  /// Make sure the full height of the source is shown, regardless of
  /// whether this means the source overflows the target box horizontally.
32 33
  fitHeight,

34 35
  /// Align the source within the target box (by default, centering) and discard
  /// any portions of the source that lie outside the box.
36 37
  none,

38 39 40
  /// 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.
41 42 43
  scaleDown
}

44
/// The pair of sizes returned by [applyBoxFit].
45
@immutable
46 47
class FittedSizes {
  /// Creates an object to store a pair of sizes,
48
  /// as would be returned by [applyBoxFit].
49 50 51 52 53 54 55 56 57
  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;
}

58
/// Apply an [BoxFit] value.
59
///
60 61 62
/// 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
63
/// that is being fitted, and the `outputSize` gives the size of the rectangle
64
/// into which the source is to be drawn.
65 66 67 68 69
///
/// 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
70 71
/// 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
72 73 74
/// smaller.
///
/// The [FittedSizes.destination] size is the subpart of the `outputSize` in
75
/// which to paint the (possibly cropped) source. If the
76
/// [FittedSizes.destination] size is smaller than the `outputSize` then the
77
/// source is being letterboxed (or pillarboxed).
78 79 80 81
///
/// 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
82
/// instance, and is how [BoxFit] is defined). The [FractionalOffset] class
83 84 85 86 87 88
/// provides a convenience function, [FractionalOffset.inscribe], for resolving
/// the sizes to rects, as shown in the example below.
///
/// == Example ==
///
/// This example paints an [Image] `image` onto the [Rect] `outputRect` on a
89
/// [Canvas] `canvas`, using a [Paint] paint, applying the [BoxFit] algorithm
90 91 92 93
/// `fit`:
///
/// ```dart
/// final Size imageSize = new Size(image.width.toDouble(), image.height.toDouble());
94
/// final FittedSizes sizes = applyBoxFit(fit, imageSize, outputRect.size);
95
/// final Rect inputSubrect = FractionalOffset.center.inscribe(sizes.source, Offset.zero & imageSize);
96 97 98
/// final Rect outputSubrect = FractionalOffset.center.inscribe(sizes.destination, outputRect);
/// canvas.drawImageRect(image, inputSubrect, outputSubrect, paint);
/// ```
99
FittedSizes applyBoxFit(BoxFit fit, Size inputSize, Size outputSize) {
100 101
  Size sourceSize, destinationSize;
  switch (fit) {
102
    case BoxFit.fill:
103 104 105
      sourceSize = inputSize;
      destinationSize = outputSize;
      break;
106
    case BoxFit.contain:
107 108 109 110 111 112
      sourceSize = inputSize;
      if (outputSize.width / outputSize.height > sourceSize.width / sourceSize.height)
        destinationSize = new Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
      else
        destinationSize = new Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
      break;
113
    case BoxFit.cover:
114 115 116 117 118 119 120
      if (outputSize.width / outputSize.height > inputSize.width / inputSize.height) {
        sourceSize = new Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
      } else {
        sourceSize = new Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
      }
      destinationSize = outputSize;
      break;
121
    case BoxFit.fitWidth:
122 123 124
      sourceSize = new Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
      destinationSize = new Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
      break;
125
    case BoxFit.fitHeight:
126 127 128
      sourceSize = new Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
      destinationSize = new Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
      break;
129
    case BoxFit.none:
130 131 132 133
      sourceSize = new Size(math.min(inputSize.width, outputSize.width),
                            math.min(inputSize.height, outputSize.height));
      destinationSize = sourceSize;
      break;
134
    case BoxFit.scaleDown:
135 136 137 138 139 140 141 142 143 144
      sourceSize = inputSize;
      destinationSize = inputSize;
      final double aspectRatio = inputSize.width / inputSize.height;
      if (destinationSize.height > outputSize.height)
        destinationSize = new Size(outputSize.height * aspectRatio, outputSize.height);
      if (destinationSize.width > outputSize.width)
        destinationSize = new Size(outputSize.width, outputSize.width / aspectRatio);
      break;
  }
  return new FittedSizes(sourceSize, destinationSize);
Adam Barth's avatar
Adam Barth committed
145
}