Factor out ImageDecoration and paintImage (#12202)
To make it cleaner when we RTLify these.
This commit is contained in:
parent
5e71de0878
commit
998d65b0b2
@ -28,6 +28,7 @@ export 'src/painting/edge_insets.dart';
|
|||||||
export 'src/painting/flutter_logo.dart';
|
export 'src/painting/flutter_logo.dart';
|
||||||
export 'src/painting/fractional_offset.dart';
|
export 'src/painting/fractional_offset.dart';
|
||||||
export 'src/painting/gradient.dart';
|
export 'src/painting/gradient.dart';
|
||||||
|
export 'src/painting/image.dart';
|
||||||
export 'src/painting/text_painter.dart';
|
export 'src/painting/text_painter.dart';
|
||||||
export 'src/painting/text_span.dart';
|
export 'src/painting/text_span.dart';
|
||||||
export 'src/painting/text_style.dart';
|
export 'src/painting/text_style.dart';
|
||||||
|
@ -11,11 +11,10 @@ import 'package:flutter/services.dart';
|
|||||||
import 'basic_types.dart';
|
import 'basic_types.dart';
|
||||||
import 'border.dart';
|
import 'border.dart';
|
||||||
import 'border_radius.dart';
|
import 'border_radius.dart';
|
||||||
import 'box_fit.dart';
|
|
||||||
import 'decoration.dart';
|
import 'decoration.dart';
|
||||||
import 'edge_insets.dart';
|
import 'edge_insets.dart';
|
||||||
import 'fractional_offset.dart';
|
|
||||||
import 'gradient.dart';
|
import 'gradient.dart';
|
||||||
|
import 'image.dart';
|
||||||
|
|
||||||
/// A shadow cast by a box.
|
/// A shadow cast by a box.
|
||||||
///
|
///
|
||||||
@ -132,264 +131,6 @@ class BoxShadow {
|
|||||||
String toString() => 'BoxShadow($color, $offset, $blurRadius, $spreadRadius)';
|
String toString() => 'BoxShadow($color, $offset, $blurRadius, $spreadRadius)';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How to paint any portions of a box not covered by an image.
|
|
||||||
enum ImageRepeat {
|
|
||||||
/// Repeat the image in both the x and y directions until the box is filled.
|
|
||||||
repeat,
|
|
||||||
|
|
||||||
/// Repeat the image in the x direction until the box is filled horizontally.
|
|
||||||
repeatX,
|
|
||||||
|
|
||||||
/// Repeat the image in the y direction until the box is filled vertically.
|
|
||||||
repeatY,
|
|
||||||
|
|
||||||
/// Leave uncovered poritions of the box transparent.
|
|
||||||
noRepeat
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterable<Rect> _generateImageTileRects(Rect outputRect, Rect fundamentalRect, ImageRepeat repeat) sync* {
|
|
||||||
if (repeat == ImageRepeat.noRepeat) {
|
|
||||||
yield fundamentalRect;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int startX = 0;
|
|
||||||
int startY = 0;
|
|
||||||
int stopX = 0;
|
|
||||||
int stopY = 0;
|
|
||||||
final double strideX = fundamentalRect.width;
|
|
||||||
final double strideY = fundamentalRect.height;
|
|
||||||
|
|
||||||
if (repeat == ImageRepeat.repeat || repeat == ImageRepeat.repeatX) {
|
|
||||||
startX = ((outputRect.left - fundamentalRect.left) / strideX).floor();
|
|
||||||
stopX = ((outputRect.right - fundamentalRect.right) / strideX).ceil();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (repeat == ImageRepeat.repeat || repeat == ImageRepeat.repeatY) {
|
|
||||||
startY = ((outputRect.top - fundamentalRect.top) / strideY).floor();
|
|
||||||
stopY = ((outputRect.bottom - fundamentalRect.bottom) / strideY).ceil();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = startX; i <= stopX; ++i) {
|
|
||||||
for (int j = startY; j <= stopY; ++j)
|
|
||||||
yield fundamentalRect.shift(new Offset(i * strideX, j * strideY));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Paints an image into the given rectangle on the canvas.
|
|
||||||
///
|
|
||||||
/// * `canvas`: The canvas onto which the image will be painted.
|
|
||||||
/// * `rect`: The region of the canvas into which the image will be painted.
|
|
||||||
/// The image might not fill the entire rectangle (e.g., depending on the
|
|
||||||
/// `fit`). If `rect` is empty, nothing is painted.
|
|
||||||
/// * `image`: The image to paint onto the canvas.
|
|
||||||
/// * `colorFilter`: If non-null, the color filter to apply when painting the
|
|
||||||
/// image.
|
|
||||||
/// * `fit`: How the image should be inscribed into `rect`. If null, the
|
|
||||||
/// default behavior depends on `centerSlice`. If `centerSlice` is also null,
|
|
||||||
/// the default behavior is [BoxFit.scaleDown]. If `centerSlice` is
|
|
||||||
/// non-null, the default behavior is [BoxFit.fill]. See [BoxFit] for
|
|
||||||
/// details.
|
|
||||||
/// * `repeat`: If the image does not fill `rect`, whether and how the image
|
|
||||||
/// should be repeated to fill `rect`. By default, the image is not repeated.
|
|
||||||
/// See [ImageRepeat] for details.
|
|
||||||
/// * `centerSlice`: The image is drawn in nine portions described by splitting
|
|
||||||
/// the image by drawing two horizontal lines and two vertical lines, where
|
|
||||||
/// `centerSlice` describes the rectangle formed by the four points where
|
|
||||||
/// these four lines intersect each other. (This forms a 3-by-3 grid
|
|
||||||
/// of regions, the center region being described by `centerSlice`.)
|
|
||||||
/// The four regions in the corners are drawn, without scaling, in the four
|
|
||||||
/// corners of the destination rectangle defined by applying `fit`. The
|
|
||||||
/// remaining five regions are drawn by stretching them to fit such that they
|
|
||||||
/// exactly cover the destination rectangle while maintaining their relative
|
|
||||||
/// positions.
|
|
||||||
/// * `alignment`: How the destination rectangle defined by applying `fit` is
|
|
||||||
/// aligned within `rect`. For example, if `fit` is [BoxFit.contain] and
|
|
||||||
/// `alignment` is [FractionalOffset.bottomRight], the image will be as large
|
|
||||||
/// as possible within `rect` and placed with its bottom right corner at the
|
|
||||||
/// bottom right corner of `rect`.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [paintBorder], which paints a border around a rectangle on a canvas.
|
|
||||||
/// * [DecorationImage], which holds a configuration for calling this function.
|
|
||||||
/// * [BoxDecoration], which uses this function to paint a [DecorationImage].
|
|
||||||
void paintImage({
|
|
||||||
@required Canvas canvas,
|
|
||||||
@required Rect rect,
|
|
||||||
@required ui.Image image,
|
|
||||||
ColorFilter colorFilter,
|
|
||||||
BoxFit fit,
|
|
||||||
FractionalOffset alignment,
|
|
||||||
Rect centerSlice,
|
|
||||||
ImageRepeat repeat: ImageRepeat.noRepeat,
|
|
||||||
}) {
|
|
||||||
assert(canvas != null);
|
|
||||||
assert(image != null);
|
|
||||||
if (rect.isEmpty)
|
|
||||||
return;
|
|
||||||
Size outputSize = rect.size;
|
|
||||||
Size inputSize = new Size(image.width.toDouble(), image.height.toDouble());
|
|
||||||
Offset sliceBorder;
|
|
||||||
if (centerSlice != null) {
|
|
||||||
sliceBorder = new Offset(
|
|
||||||
centerSlice.left + inputSize.width - centerSlice.right,
|
|
||||||
centerSlice.top + inputSize.height - centerSlice.bottom
|
|
||||||
);
|
|
||||||
outputSize -= sliceBorder;
|
|
||||||
inputSize -= sliceBorder;
|
|
||||||
}
|
|
||||||
fit ??= centerSlice == null ? BoxFit.scaleDown : BoxFit.fill;
|
|
||||||
assert(centerSlice == null || (fit != BoxFit.none && fit != BoxFit.cover));
|
|
||||||
final FittedSizes fittedSizes = applyBoxFit(fit, inputSize, outputSize);
|
|
||||||
final Size sourceSize = fittedSizes.source;
|
|
||||||
Size destinationSize = fittedSizes.destination;
|
|
||||||
if (centerSlice != null) {
|
|
||||||
outputSize += sliceBorder;
|
|
||||||
destinationSize += sliceBorder;
|
|
||||||
// We don't have the ability to draw a subset of the image at the same time
|
|
||||||
// as we apply a nine-patch stretch.
|
|
||||||
assert(sourceSize == inputSize, 'centerSlice was used with a BoxFit that does not guarantee that the image is fully visible.');
|
|
||||||
}
|
|
||||||
if (repeat != ImageRepeat.noRepeat && destinationSize == outputSize) {
|
|
||||||
// There's no need to repeat the image because we're exactly filling the
|
|
||||||
// output rect with the image.
|
|
||||||
repeat = ImageRepeat.noRepeat;
|
|
||||||
}
|
|
||||||
final Paint paint = new Paint()..isAntiAlias = false;
|
|
||||||
if (colorFilter != null)
|
|
||||||
paint.colorFilter = colorFilter;
|
|
||||||
if (sourceSize != destinationSize) {
|
|
||||||
// Use the "low" quality setting to scale the image, which corresponds to
|
|
||||||
// bilinear interpolation, rather than the default "none" which corresponds
|
|
||||||
// to nearest-neighbor.
|
|
||||||
paint.filterQuality = FilterQuality.low;
|
|
||||||
}
|
|
||||||
final double dx = (outputSize.width - destinationSize.width) * (alignment?.dx ?? 0.5);
|
|
||||||
final double dy = (outputSize.height - destinationSize.height) * (alignment?.dy ?? 0.5);
|
|
||||||
final Offset destinationPosition = rect.topLeft.translate(dx, dy);
|
|
||||||
final Rect destinationRect = destinationPosition & destinationSize;
|
|
||||||
if (repeat != ImageRepeat.noRepeat) {
|
|
||||||
canvas.save();
|
|
||||||
canvas.clipRect(rect);
|
|
||||||
}
|
|
||||||
if (centerSlice == null) {
|
|
||||||
final Rect sourceRect = (alignment ?? FractionalOffset.center).inscribe(
|
|
||||||
fittedSizes.source, Offset.zero & inputSize
|
|
||||||
);
|
|
||||||
for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
|
|
||||||
canvas.drawImageRect(image, sourceRect, tileRect, paint);
|
|
||||||
} else {
|
|
||||||
for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
|
|
||||||
canvas.drawImageNine(image, centerSlice, tileRect, paint);
|
|
||||||
}
|
|
||||||
if (repeat != ImageRepeat.noRepeat)
|
|
||||||
canvas.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An image for a box decoration.
|
|
||||||
///
|
|
||||||
/// The image is painted using [paintImage], which describes the meanings of the
|
|
||||||
/// various fields on this class in more detail.
|
|
||||||
@immutable
|
|
||||||
class DecorationImage {
|
|
||||||
/// Creates an image to show in a [BoxDecoration].
|
|
||||||
///
|
|
||||||
/// The [image] argument must not be null.
|
|
||||||
const DecorationImage({
|
|
||||||
@required this.image,
|
|
||||||
this.colorFilter,
|
|
||||||
this.fit,
|
|
||||||
this.alignment,
|
|
||||||
this.centerSlice,
|
|
||||||
this.repeat: ImageRepeat.noRepeat,
|
|
||||||
}) : assert(image != null);
|
|
||||||
|
|
||||||
/// The image to be painted into the decoration.
|
|
||||||
///
|
|
||||||
/// Typically this will be an [AssetImage] (for an image shipped with the
|
|
||||||
/// application) or a [NetworkImage] (for an image obtained from the network).
|
|
||||||
final ImageProvider image;
|
|
||||||
|
|
||||||
/// A color filter to apply to the image before painting it.
|
|
||||||
final ColorFilter colorFilter;
|
|
||||||
|
|
||||||
/// How the image should be inscribed into the box.
|
|
||||||
///
|
|
||||||
/// The default is [BoxFit.scaleDown] if [centerSlice] is null, and
|
|
||||||
/// [BoxFit.fill] if [centerSlice] is not null.
|
|
||||||
///
|
|
||||||
/// See the discussion at [paintImage] for more details.
|
|
||||||
final BoxFit fit;
|
|
||||||
|
|
||||||
/// How to align the image within its bounds.
|
|
||||||
///
|
|
||||||
/// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its
|
|
||||||
/// layout bounds. An alignment of (1.0, 0.5) aligns the image to the middle
|
|
||||||
/// of the right edge of its layout bounds.
|
|
||||||
///
|
|
||||||
/// Defaults to [FractionalOffset.center].
|
|
||||||
final FractionalOffset alignment;
|
|
||||||
|
|
||||||
/// The center slice for a nine-patch image.
|
|
||||||
///
|
|
||||||
/// The region of the image inside the center slice will be stretched both
|
|
||||||
/// horizontally and vertically to fit the image into its destination. The
|
|
||||||
/// region of the image above and below the center slice will be stretched
|
|
||||||
/// only horizontally and the region of the image to the left and right of
|
|
||||||
/// the center slice will be stretched only vertically.
|
|
||||||
///
|
|
||||||
/// The stretching will be applied in order to make the image fit into the box
|
|
||||||
/// specified by [fit]. When [centerSlice] is not null, [fit] defaults to
|
|
||||||
/// [BoxFit.fill], which distorts the destination image size relative to the
|
|
||||||
/// image's original aspect ratio. Values of [BoxFit] which do not distort the
|
|
||||||
/// destination image size will result in [centerSlice] having no effect
|
|
||||||
/// (since the nine regions of the image will be rendered with the same
|
|
||||||
/// scaling, as if it wasn't specified).
|
|
||||||
final Rect centerSlice;
|
|
||||||
|
|
||||||
/// How to paint any portions of the box that would not otherwise be covered
|
|
||||||
/// by the image.
|
|
||||||
final ImageRepeat repeat;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(dynamic other) {
|
|
||||||
if (identical(this, other))
|
|
||||||
return true;
|
|
||||||
if (runtimeType != other.runtimeType)
|
|
||||||
return false;
|
|
||||||
final DecorationImage typedOther = other;
|
|
||||||
return image == typedOther.image
|
|
||||||
&& colorFilter == typedOther.colorFilter
|
|
||||||
&& fit == typedOther.fit
|
|
||||||
&& alignment == typedOther.alignment
|
|
||||||
&& centerSlice == typedOther.centerSlice
|
|
||||||
&& repeat == typedOther.repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => hashValues(image, colorFilter, fit, alignment, centerSlice, repeat);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
final List<String> properties = <String>[];
|
|
||||||
properties.add('$image');
|
|
||||||
if (colorFilter != null)
|
|
||||||
properties.add('$colorFilter');
|
|
||||||
if (fit != null &&
|
|
||||||
!(fit == BoxFit.fill && centerSlice != null) &&
|
|
||||||
!(fit == BoxFit.scaleDown && centerSlice == null))
|
|
||||||
properties.add('$fit');
|
|
||||||
if (alignment != null)
|
|
||||||
properties.add('$alignment');
|
|
||||||
if (centerSlice != null)
|
|
||||||
properties.add('centerSlice: $centerSlice');
|
|
||||||
if (repeat != ImageRepeat.noRepeat)
|
|
||||||
properties.add('$repeat');
|
|
||||||
return '$runtimeType(${properties.join(", ")})';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An immutable description of how to paint a box.
|
/// An immutable description of how to paint a box.
|
||||||
///
|
///
|
||||||
/// The [BoxDecoration] class provides a variety of ways to draw a box.
|
/// The [BoxDecoration] class provides a variety of ways to draw a box.
|
||||||
|
271
packages/flutter/lib/src/painting/image.dart
Normal file
271
packages/flutter/lib/src/painting/image.dart
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
// Copyright 2015 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:ui' as ui show Image;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'basic_types.dart';
|
||||||
|
import 'border.dart';
|
||||||
|
import 'box_fit.dart';
|
||||||
|
import 'fractional_offset.dart';
|
||||||
|
|
||||||
|
/// How to paint any portions of a box not covered by an image.
|
||||||
|
enum ImageRepeat {
|
||||||
|
/// Repeat the image in both the x and y directions until the box is filled.
|
||||||
|
repeat,
|
||||||
|
|
||||||
|
/// Repeat the image in the x direction until the box is filled horizontally.
|
||||||
|
repeatX,
|
||||||
|
|
||||||
|
/// Repeat the image in the y direction until the box is filled vertically.
|
||||||
|
repeatY,
|
||||||
|
|
||||||
|
/// Leave uncovered poritions of the box transparent.
|
||||||
|
noRepeat
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An image for a box decoration.
|
||||||
|
///
|
||||||
|
/// The image is painted using [paintImage], which describes the meanings of the
|
||||||
|
/// various fields on this class in more detail.
|
||||||
|
@immutable
|
||||||
|
class DecorationImage {
|
||||||
|
/// Creates an image to show in a [BoxDecoration].
|
||||||
|
///
|
||||||
|
/// The [image] argument must not be null.
|
||||||
|
const DecorationImage({
|
||||||
|
@required this.image,
|
||||||
|
this.colorFilter,
|
||||||
|
this.fit,
|
||||||
|
this.alignment,
|
||||||
|
this.centerSlice,
|
||||||
|
this.repeat: ImageRepeat.noRepeat,
|
||||||
|
}) : assert(image != null);
|
||||||
|
|
||||||
|
/// The image to be painted into the decoration.
|
||||||
|
///
|
||||||
|
/// Typically this will be an [AssetImage] (for an image shipped with the
|
||||||
|
/// application) or a [NetworkImage] (for an image obtained from the network).
|
||||||
|
final ImageProvider image;
|
||||||
|
|
||||||
|
/// A color filter to apply to the image before painting it.
|
||||||
|
final ColorFilter colorFilter;
|
||||||
|
|
||||||
|
/// How the image should be inscribed into the box.
|
||||||
|
///
|
||||||
|
/// The default is [BoxFit.scaleDown] if [centerSlice] is null, and
|
||||||
|
/// [BoxFit.fill] if [centerSlice] is not null.
|
||||||
|
///
|
||||||
|
/// See the discussion at [paintImage] for more details.
|
||||||
|
final BoxFit fit;
|
||||||
|
|
||||||
|
/// How to align the image within its bounds.
|
||||||
|
///
|
||||||
|
/// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its
|
||||||
|
/// layout bounds. An alignment of (1.0, 0.5) aligns the image to the middle
|
||||||
|
/// of the right edge of its layout bounds.
|
||||||
|
///
|
||||||
|
/// Defaults to [FractionalOffset.center].
|
||||||
|
final FractionalOffset alignment;
|
||||||
|
|
||||||
|
/// The center slice for a nine-patch image.
|
||||||
|
///
|
||||||
|
/// The region of the image inside the center slice will be stretched both
|
||||||
|
/// horizontally and vertically to fit the image into its destination. The
|
||||||
|
/// region of the image above and below the center slice will be stretched
|
||||||
|
/// only horizontally and the region of the image to the left and right of
|
||||||
|
/// the center slice will be stretched only vertically.
|
||||||
|
///
|
||||||
|
/// The stretching will be applied in order to make the image fit into the box
|
||||||
|
/// specified by [fit]. When [centerSlice] is not null, [fit] defaults to
|
||||||
|
/// [BoxFit.fill], which distorts the destination image size relative to the
|
||||||
|
/// image's original aspect ratio. Values of [BoxFit] which do not distort the
|
||||||
|
/// destination image size will result in [centerSlice] having no effect
|
||||||
|
/// (since the nine regions of the image will be rendered with the same
|
||||||
|
/// scaling, as if it wasn't specified).
|
||||||
|
final Rect centerSlice;
|
||||||
|
|
||||||
|
/// How to paint any portions of the box that would not otherwise be covered
|
||||||
|
/// by the image.
|
||||||
|
final ImageRepeat repeat;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(dynamic other) {
|
||||||
|
if (identical(this, other))
|
||||||
|
return true;
|
||||||
|
if (runtimeType != other.runtimeType)
|
||||||
|
return false;
|
||||||
|
final DecorationImage typedOther = other;
|
||||||
|
return image == typedOther.image
|
||||||
|
&& colorFilter == typedOther.colorFilter
|
||||||
|
&& fit == typedOther.fit
|
||||||
|
&& alignment == typedOther.alignment
|
||||||
|
&& centerSlice == typedOther.centerSlice
|
||||||
|
&& repeat == typedOther.repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => hashValues(image, colorFilter, fit, alignment, centerSlice, repeat);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
final List<String> properties = <String>[];
|
||||||
|
properties.add('$image');
|
||||||
|
if (colorFilter != null)
|
||||||
|
properties.add('$colorFilter');
|
||||||
|
if (fit != null &&
|
||||||
|
!(fit == BoxFit.fill && centerSlice != null) &&
|
||||||
|
!(fit == BoxFit.scaleDown && centerSlice == null))
|
||||||
|
properties.add('$fit');
|
||||||
|
if (alignment != null)
|
||||||
|
properties.add('$alignment');
|
||||||
|
if (centerSlice != null)
|
||||||
|
properties.add('centerSlice: $centerSlice');
|
||||||
|
if (repeat != ImageRepeat.noRepeat)
|
||||||
|
properties.add('$repeat');
|
||||||
|
return '$runtimeType(${properties.join(", ")})';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Paints an image into the given rectangle on the canvas.
|
||||||
|
///
|
||||||
|
/// * `canvas`: The canvas onto which the image will be painted.
|
||||||
|
/// * `rect`: The region of the canvas into which the image will be painted.
|
||||||
|
/// The image might not fill the entire rectangle (e.g., depending on the
|
||||||
|
/// `fit`). If `rect` is empty, nothing is painted.
|
||||||
|
/// * `image`: The image to paint onto the canvas.
|
||||||
|
/// * `colorFilter`: If non-null, the color filter to apply when painting the
|
||||||
|
/// image.
|
||||||
|
/// * `fit`: How the image should be inscribed into `rect`. If null, the
|
||||||
|
/// default behavior depends on `centerSlice`. If `centerSlice` is also null,
|
||||||
|
/// the default behavior is [BoxFit.scaleDown]. If `centerSlice` is
|
||||||
|
/// non-null, the default behavior is [BoxFit.fill]. See [BoxFit] for
|
||||||
|
/// details.
|
||||||
|
/// * `repeat`: If the image does not fill `rect`, whether and how the image
|
||||||
|
/// should be repeated to fill `rect`. By default, the image is not repeated.
|
||||||
|
/// See [ImageRepeat] for details.
|
||||||
|
/// * `centerSlice`: The image is drawn in nine portions described by splitting
|
||||||
|
/// the image by drawing two horizontal lines and two vertical lines, where
|
||||||
|
/// `centerSlice` describes the rectangle formed by the four points where
|
||||||
|
/// these four lines intersect each other. (This forms a 3-by-3 grid
|
||||||
|
/// of regions, the center region being described by `centerSlice`.)
|
||||||
|
/// The four regions in the corners are drawn, without scaling, in the four
|
||||||
|
/// corners of the destination rectangle defined by applying `fit`. The
|
||||||
|
/// remaining five regions are drawn by stretching them to fit such that they
|
||||||
|
/// exactly cover the destination rectangle while maintaining their relative
|
||||||
|
/// positions.
|
||||||
|
/// * `alignment`: How the destination rectangle defined by applying `fit` is
|
||||||
|
/// aligned within `rect`. For example, if `fit` is [BoxFit.contain] and
|
||||||
|
/// `alignment` is [FractionalOffset.bottomRight], the image will be as large
|
||||||
|
/// as possible within `rect` and placed with its bottom right corner at the
|
||||||
|
/// bottom right corner of `rect`.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [paintBorder], which paints a border around a rectangle on a canvas.
|
||||||
|
/// * [DecorationImage], which holds a configuration for calling this function.
|
||||||
|
/// * [BoxDecoration], which uses this function to paint a [DecorationImage].
|
||||||
|
void paintImage({
|
||||||
|
@required Canvas canvas,
|
||||||
|
@required Rect rect,
|
||||||
|
@required ui.Image image,
|
||||||
|
ColorFilter colorFilter,
|
||||||
|
BoxFit fit,
|
||||||
|
FractionalOffset alignment,
|
||||||
|
Rect centerSlice,
|
||||||
|
ImageRepeat repeat: ImageRepeat.noRepeat,
|
||||||
|
}) {
|
||||||
|
assert(canvas != null);
|
||||||
|
assert(image != null);
|
||||||
|
if (rect.isEmpty)
|
||||||
|
return;
|
||||||
|
Size outputSize = rect.size;
|
||||||
|
Size inputSize = new Size(image.width.toDouble(), image.height.toDouble());
|
||||||
|
Offset sliceBorder;
|
||||||
|
if (centerSlice != null) {
|
||||||
|
sliceBorder = new Offset(
|
||||||
|
centerSlice.left + inputSize.width - centerSlice.right,
|
||||||
|
centerSlice.top + inputSize.height - centerSlice.bottom
|
||||||
|
);
|
||||||
|
outputSize -= sliceBorder;
|
||||||
|
inputSize -= sliceBorder;
|
||||||
|
}
|
||||||
|
fit ??= centerSlice == null ? BoxFit.scaleDown : BoxFit.fill;
|
||||||
|
assert(centerSlice == null || (fit != BoxFit.none && fit != BoxFit.cover));
|
||||||
|
final FittedSizes fittedSizes = applyBoxFit(fit, inputSize, outputSize);
|
||||||
|
final Size sourceSize = fittedSizes.source;
|
||||||
|
Size destinationSize = fittedSizes.destination;
|
||||||
|
if (centerSlice != null) {
|
||||||
|
outputSize += sliceBorder;
|
||||||
|
destinationSize += sliceBorder;
|
||||||
|
// We don't have the ability to draw a subset of the image at the same time
|
||||||
|
// as we apply a nine-patch stretch.
|
||||||
|
assert(sourceSize == inputSize, 'centerSlice was used with a BoxFit that does not guarantee that the image is fully visible.');
|
||||||
|
}
|
||||||
|
if (repeat != ImageRepeat.noRepeat && destinationSize == outputSize) {
|
||||||
|
// There's no need to repeat the image because we're exactly filling the
|
||||||
|
// output rect with the image.
|
||||||
|
repeat = ImageRepeat.noRepeat;
|
||||||
|
}
|
||||||
|
final Paint paint = new Paint()..isAntiAlias = false;
|
||||||
|
if (colorFilter != null)
|
||||||
|
paint.colorFilter = colorFilter;
|
||||||
|
if (sourceSize != destinationSize) {
|
||||||
|
// Use the "low" quality setting to scale the image, which corresponds to
|
||||||
|
// bilinear interpolation, rather than the default "none" which corresponds
|
||||||
|
// to nearest-neighbor.
|
||||||
|
paint.filterQuality = FilterQuality.low;
|
||||||
|
}
|
||||||
|
final double dx = (outputSize.width - destinationSize.width) * (alignment?.dx ?? 0.5);
|
||||||
|
final double dy = (outputSize.height - destinationSize.height) * (alignment?.dy ?? 0.5);
|
||||||
|
final Offset destinationPosition = rect.topLeft.translate(dx, dy);
|
||||||
|
final Rect destinationRect = destinationPosition & destinationSize;
|
||||||
|
if (repeat != ImageRepeat.noRepeat) {
|
||||||
|
canvas.save();
|
||||||
|
canvas.clipRect(rect);
|
||||||
|
}
|
||||||
|
if (centerSlice == null) {
|
||||||
|
final Rect sourceRect = (alignment ?? FractionalOffset.center).inscribe(
|
||||||
|
fittedSizes.source, Offset.zero & inputSize
|
||||||
|
);
|
||||||
|
for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
|
||||||
|
canvas.drawImageRect(image, sourceRect, tileRect, paint);
|
||||||
|
} else {
|
||||||
|
for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
|
||||||
|
canvas.drawImageNine(image, centerSlice, tileRect, paint);
|
||||||
|
}
|
||||||
|
if (repeat != ImageRepeat.noRepeat)
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable<Rect> _generateImageTileRects(Rect outputRect, Rect fundamentalRect, ImageRepeat repeat) sync* {
|
||||||
|
if (repeat == ImageRepeat.noRepeat) {
|
||||||
|
yield fundamentalRect;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int startX = 0;
|
||||||
|
int startY = 0;
|
||||||
|
int stopX = 0;
|
||||||
|
int stopY = 0;
|
||||||
|
final double strideX = fundamentalRect.width;
|
||||||
|
final double strideY = fundamentalRect.height;
|
||||||
|
|
||||||
|
if (repeat == ImageRepeat.repeat || repeat == ImageRepeat.repeatX) {
|
||||||
|
startX = ((outputRect.left - fundamentalRect.left) / strideX).floor();
|
||||||
|
stopX = ((outputRect.right - fundamentalRect.right) / strideX).ceil();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repeat == ImageRepeat.repeat || repeat == ImageRepeat.repeatY) {
|
||||||
|
startY = ((outputRect.top - fundamentalRect.top) / strideY).floor();
|
||||||
|
stopY = ((outputRect.bottom - fundamentalRect.bottom) / strideY).ceil();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = startX; i <= stopX; ++i) {
|
||||||
|
for (int j = startY; j <= stopY; ++j)
|
||||||
|
yield fundamentalRect.shift(new Offset(i * strideX, j * strideY));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user