image_fit.dart 5.84 KB
Newer Older
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
// 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;

import 'basic_types.dart';

/// How an image should be inscribed into a box.
///
/// See also [applyImageFit], which applies the sizing semantics of these values
/// (though not the alignment semantics).
enum ImageFit {
  /// Fill the box by distorting the image's aspect ratio.
  fill,

  /// As large as possible while still containing the image entirely within the box.
  contain,

  /// As small as possible while still covering the entire box.
  cover,

  /// Make sure the full width of the image is shown, regardless of
  /// whether this means the image overflows the box vertically.
  fitWidth,

  /// Make sure the full height of the image is shown, regardless of
  /// whether this means the image overflows the box horizontally.
  fitHeight,

  /// Center the image within the box and discard any portions of the image that
  /// lie outside the box.
  none,

  /// Center the image within the box and, if necessary, scale the image down to
  /// ensure that the image fits within the box.
  scaleDown
}

/// The pair of sizes returned by [applyImageFit].
class FittedSizes {
  /// Creates an object to store a pair of sizes,
  /// as would be returned by [applyImageFit].
  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 an [ImageFit] value.
///
/// The arguments to this method, in addition to the [ImageFit] value to apply,
/// are two sizes, ostensibly the sizes of an input image and an output canvas.
/// Specifically, the `inputSize` argument gives the size of the complete image
/// that is being fitted, and the `outputSize` gives the size of the rectangle
/// into which the image 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 image is shown, then this will equal the
/// `inputSize`, but if the input image 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) input image. If the
/// [FittedSizes.destination] size is smaller than the `outputSize` then the
/// input image 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 [ImageFit] is defined). The [FractionalOffset] class
/// 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
/// [Canvas] `canvas`, using a [Paint] paint, applying the [ImageFit] algorithm
/// `fit`:
///
/// ```dart
/// final Size imageSize = new Size(image.width.toDouble(), image.height.toDouble());
/// final FittedSizes sizes = applyImageFit(fit, imageSize, outputRect.size);
/// final Rect inputSubrect = FractionalOffset.center.inscribe(sizes.source, Point.origin & imageSize);
/// final Rect outputSubrect = FractionalOffset.center.inscribe(sizes.destination, outputRect);
/// canvas.drawImageRect(image, inputSubrect, outputSubrect, paint);
/// ```
FittedSizes applyImageFit(ImageFit fit, Size inputSize, Size outputSize) {
  Size sourceSize, destinationSize;
  switch (fit) {
    case ImageFit.fill:
      sourceSize = inputSize;
      destinationSize = outputSize;
      break;
    case ImageFit.contain:
      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;
    case ImageFit.cover:
      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;
    case ImageFit.fitWidth:
      sourceSize = new Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
      destinationSize = new Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
      break;
    case ImageFit.fitHeight:
      sourceSize = new Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
      destinationSize = new Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
      break;
    case ImageFit.none:
      sourceSize = new Size(math.min(inputSize.width, outputSize.width),
                            math.min(inputSize.height, outputSize.height));
      destinationSize = sourceSize;
      break;
    case ImageFit.scaleDown:
      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
140
}