mirror of
https://github.com/flutter/flutter
synced 2024-10-12 11:12:54 +00:00
This primarily implements DecorationImage.lerp(). It also makes some minor tweaks, the main one of which is defering to dart:ui for `clampDouble` instead of duplicating it in package:foundation. Fixes https://github.com/flutter/flutter/issues/12452 This was first landed in https://github.com/flutter/flutter/pull/130533 and reverted in https://github.com/flutter/flutter/pull/131347.
This commit is contained in:
parent
bb0c3172f8
commit
33e9fd8934
|
@ -50,7 +50,7 @@ linter:
|
|||
- avoid_field_initializers_in_const_classes
|
||||
# - avoid_final_parameters # incompatible with prefer_final_parameters
|
||||
- avoid_function_literals_in_foreach_calls
|
||||
- avoid_implementing_value_types
|
||||
# - avoid_implementing_value_types # see https://github.com/dart-lang/linter/issues/4558
|
||||
- avoid_init_to_null
|
||||
- avoid_js_rounded_ints
|
||||
# - avoid_multiple_declarations_per_line # seems to be a stylistic choice we don't subscribe to
|
||||
|
|
|
@ -34,7 +34,6 @@ export 'src/foundation/diagnostics.dart';
|
|||
export 'src/foundation/isolates.dart';
|
||||
export 'src/foundation/key.dart';
|
||||
export 'src/foundation/licenses.dart';
|
||||
export 'src/foundation/math.dart';
|
||||
export 'src/foundation/memory_allocations.dart';
|
||||
export 'src/foundation/node.dart';
|
||||
export 'src/foundation/object.dart';
|
||||
|
|
|
@ -3,9 +3,9 @@ nothing but core Dart packages. They can't depend on `dart:ui`, they
|
|||
can't depend on any `package:`, and they can't depend on anything
|
||||
outside this directory.
|
||||
|
||||
Currently they do depend on dart:ui, but only for `VoidCallback` (and
|
||||
maybe one day `lerpDouble`), which are all intended to be moved out
|
||||
of `dart:ui` and into `dart:core`.
|
||||
Currently they do depend on dart:ui, but only for `VoidCallback` and
|
||||
`clampDouble` (and maybe one day `lerpDouble`), which are all intended
|
||||
to be moved out of `dart:ui` and into `dart:core`.
|
||||
|
||||
There is currently also an unfortunate dependency on the platform
|
||||
dispatcher logic (SingletonFlutterWindow, Brightness,
|
||||
|
@ -14,5 +14,4 @@ PlatformDispatcher, window), though that should probably move to the
|
|||
|
||||
See also:
|
||||
|
||||
* https://github.com/dart-lang/sdk/issues/27791 (`VoidCallback`)
|
||||
* https://github.com/dart-lang/sdk/issues/25217 (`hashValues`, `hashList`, and `lerpDouble`)
|
||||
* https://github.com/dart-lang/sdk/issues/25217
|
||||
|
|
|
@ -22,7 +22,7 @@ import 'print.dart';
|
|||
import 'service_extensions.dart';
|
||||
import 'timeline.dart';
|
||||
|
||||
export 'dart:ui' show PlatformDispatcher, SingletonFlutterWindow; // ignore: deprecated_member_use
|
||||
export 'dart:ui' show PlatformDispatcher, SingletonFlutterWindow, clampDouble; // ignore: deprecated_member_use
|
||||
|
||||
export 'basic_types.dart' show AsyncCallback, AsyncValueGetter, AsyncValueSetter;
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' show clampDouble;
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'assertions.dart';
|
||||
import 'constants.dart';
|
||||
import 'debug.dart';
|
||||
import 'math.dart' show clampDouble;
|
||||
import 'object.dart';
|
||||
|
||||
// Examples can assume:
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
// 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.
|
||||
|
||||
/// Same as [num.clamp] but optimized for non-null [double].
|
||||
///
|
||||
/// This is faster because it avoids polymorphism, boxing, and special cases for
|
||||
/// floating point numbers.
|
||||
//
|
||||
// See also: //dev/benchmarks/microbenchmarks/lib/foundation/clamp.dart
|
||||
double clampDouble(double x, double min, double max) {
|
||||
assert(min <= max && !max.isNaN && !min.isNaN);
|
||||
if (x < min) {
|
||||
return min;
|
||||
}
|
||||
if (x > max) {
|
||||
return max;
|
||||
}
|
||||
if (x.isNaN) {
|
||||
return max;
|
||||
}
|
||||
return x;
|
||||
}
|
|
@ -2,10 +2,9 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui';
|
||||
import 'dart:ui' show clampDouble, lerpDouble;
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart' show clampDouble;
|
||||
|
||||
import 'color_scheme.dart';
|
||||
import 'colors.dart';
|
||||
|
|
|
@ -232,7 +232,7 @@ class BoxDecoration extends Decoration {
|
|||
BoxDecoration scale(double factor) {
|
||||
return BoxDecoration(
|
||||
color: Color.lerp(null, color, factor),
|
||||
image: image, // TODO(ianh): fade the image from transparent
|
||||
image: DecorationImage.lerp(null, image, factor),
|
||||
border: BoxBorder.lerp(null, border, factor),
|
||||
borderRadius: BorderRadiusGeometry.lerp(null, borderRadius, factor),
|
||||
boxShadow: BoxShadow.lerpList(null, boxShadow, factor),
|
||||
|
@ -307,7 +307,7 @@ class BoxDecoration extends Decoration {
|
|||
}
|
||||
return BoxDecoration(
|
||||
color: Color.lerp(a.color, b.color, t),
|
||||
image: t < 0.5 ? a.image : b.image, // TODO(ianh): cross-fade the image
|
||||
image: DecorationImage.lerp(a.image, b.image, t),
|
||||
border: BoxBorder.lerp(a.border, b.border, t),
|
||||
borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, b.borderRadius, t),
|
||||
boxShadow: BoxShadow.lerpList(a.boxShadow, b.boxShadow, t),
|
||||
|
|
|
@ -177,7 +177,7 @@ class DecorationImage {
|
|||
/// image needs to be repainted, e.g. because it is loading incrementally or
|
||||
/// because it is animated.
|
||||
DecorationImagePainter createPainter(VoidCallback onChanged) {
|
||||
return DecorationImagePainter._(this, onChanged);
|
||||
return _DecorationImagePainter._(this, onChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -246,6 +246,28 @@ class DecorationImage {
|
|||
];
|
||||
return '${objectRuntimeType(this, 'DecorationImage')}(${properties.join(", ")})';
|
||||
}
|
||||
|
||||
/// Linearly interpolates between two [DecorationImage]s.
|
||||
///
|
||||
/// The `t` argument represents position on the timeline, with 0.0 meaning
|
||||
/// that the interpolation has not started, returning `a`, 1.0 meaning that
|
||||
/// the interpolation has finished, returning `b`, and values in between
|
||||
/// meaning that the interpolation is at the relevant point on the timeline
|
||||
/// between `a` and `this`. The interpolation can be extrapolated beyond 0.0
|
||||
/// and 1.0, so negative values and values greater than 1.0 are valid (and can
|
||||
/// easily be generated by curves such as [Curves.elasticInOut]).
|
||||
///
|
||||
/// Values for `t` are usually obtained from an [Animation<double>], such as
|
||||
/// an [AnimationController].
|
||||
static DecorationImage? lerp(DecorationImage? a, DecorationImage? b, double t) {
|
||||
if (identical(a, b) || t == 0.0) {
|
||||
return a;
|
||||
}
|
||||
if (t == 1.0) {
|
||||
return b;
|
||||
}
|
||||
return _BlendedDecorationImage(a, b, t);
|
||||
}
|
||||
}
|
||||
|
||||
/// The painter for a [DecorationImage].
|
||||
|
@ -259,15 +281,7 @@ class DecorationImage {
|
|||
///
|
||||
/// This object should be disposed using the [dispose] method when it is no
|
||||
/// longer needed.
|
||||
class DecorationImagePainter {
|
||||
DecorationImagePainter._(this._details, this._onChanged);
|
||||
|
||||
final DecorationImage _details;
|
||||
final VoidCallback _onChanged;
|
||||
|
||||
ImageStream? _imageStream;
|
||||
ImageInfo? _image;
|
||||
|
||||
abstract interface class DecorationImagePainter {
|
||||
/// Draw the image onto the given canvas.
|
||||
///
|
||||
/// The image is drawn at the position and size given by the `rect` argument.
|
||||
|
@ -282,8 +296,34 @@ class DecorationImagePainter {
|
|||
/// because it had not yet been loaded the first time this method was called,
|
||||
/// then the `onChanged` callback passed to [DecorationImage.createPainter]
|
||||
/// will be called.
|
||||
void paint(Canvas canvas, Rect rect, Path? clipPath, ImageConfiguration configuration) {
|
||||
///
|
||||
/// The `blend` argument specifies the opacity that should be applied to the
|
||||
/// image due to this image being blended with another. The `blendMode`
|
||||
/// argument can be specified to override the [DecorationImagePainter]'s
|
||||
/// default [BlendMode] behavior. It is usually set to [BlendMode.srcOver] if
|
||||
/// this is the first or only image being blended, and [BlendMode.plus] if it
|
||||
/// is being blended with an image below.
|
||||
void paint(Canvas canvas, Rect rect, Path? clipPath, ImageConfiguration configuration, { double blend = 1.0, BlendMode blendMode = BlendMode.srcOver });
|
||||
|
||||
/// Releases the resources used by this painter.
|
||||
///
|
||||
/// This should be called whenever the painter is no longer needed.
|
||||
///
|
||||
/// After this method has been called, the object is no longer usable.
|
||||
void dispose();
|
||||
}
|
||||
|
||||
class _DecorationImagePainter implements DecorationImagePainter {
|
||||
_DecorationImagePainter._(this._details, this._onChanged);
|
||||
|
||||
final DecorationImage _details;
|
||||
final VoidCallback _onChanged;
|
||||
|
||||
ImageStream? _imageStream;
|
||||
ImageInfo? _image;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Rect rect, Path? clipPath, ImageConfiguration configuration, { double blend = 1.0, BlendMode blendMode = BlendMode.srcOver }) {
|
||||
bool flipHorizontally = false;
|
||||
if (_details.matchTextDirection) {
|
||||
assert(() {
|
||||
|
@ -338,10 +378,11 @@ class DecorationImagePainter {
|
|||
centerSlice: _details.centerSlice,
|
||||
repeat: _details.repeat,
|
||||
flipHorizontally: flipHorizontally,
|
||||
opacity: _details.opacity,
|
||||
opacity: _details.opacity * blend,
|
||||
filterQuality: _details.filterQuality,
|
||||
invertColors: _details.invertColors,
|
||||
isAntiAlias: _details.isAntiAlias,
|
||||
blendMode: blendMode,
|
||||
);
|
||||
|
||||
if (clipPath != null) {
|
||||
|
@ -364,12 +405,7 @@ class DecorationImagePainter {
|
|||
}
|
||||
}
|
||||
|
||||
/// Releases the resources used by this painter.
|
||||
///
|
||||
/// This should be called whenever the painter is no longer needed.
|
||||
///
|
||||
/// After this method has been called, the object is no longer usable.
|
||||
@mustCallSuper
|
||||
@override
|
||||
void dispose() {
|
||||
_imageStream?.removeListener(ImageStreamListener(
|
||||
_handleImage,
|
||||
|
@ -444,7 +480,7 @@ void debugFlushLastFrameImageSizeInfo() {
|
|||
/// 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.
|
||||
/// positions. See also [Canvas.drawImageNine].
|
||||
///
|
||||
/// * `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.
|
||||
|
@ -490,6 +526,7 @@ void paintImage({
|
|||
bool invertColors = false,
|
||||
FilterQuality filterQuality = FilterQuality.low,
|
||||
bool isAntiAlias = false,
|
||||
BlendMode blendMode = BlendMode.srcOver,
|
||||
}) {
|
||||
assert(
|
||||
image.debugGetOpenHandleStackTraces()?.isNotEmpty ?? true,
|
||||
|
@ -530,9 +567,10 @@ void paintImage({
|
|||
if (colorFilter != null) {
|
||||
paint.colorFilter = colorFilter;
|
||||
}
|
||||
paint.color = Color.fromRGBO(0, 0, 0, opacity);
|
||||
paint.color = Color.fromRGBO(0, 0, 0, clampDouble(opacity, 0.0, 1.0));
|
||||
paint.filterQuality = filterQuality;
|
||||
paint.invertColors = invertColors;
|
||||
paint.blendMode = blendMode;
|
||||
final double halfWidthDelta = (outputSize.width - destinationSize.width) / 2.0;
|
||||
final double halfHeightDelta = (outputSize.height - destinationSize.height) / 2.0;
|
||||
final double dx = halfWidthDelta + (flipHorizontally ? -alignment.x : alignment.x) * halfWidthDelta;
|
||||
|
@ -543,6 +581,12 @@ void paintImage({
|
|||
// Set to true if we added a saveLayer to the canvas to invert/flip the image.
|
||||
bool invertedCanvas = false;
|
||||
// Output size and destination rect are fully calculated.
|
||||
|
||||
// Implement debug-mode and profile-mode features:
|
||||
// - cacheWidth/cacheHeight warning
|
||||
// - debugInvertOversizedImages
|
||||
// - debugOnPaintImage
|
||||
// - Flutter.ImageSizesForFrame events in timeline
|
||||
if (!kReleaseMode) {
|
||||
// We can use the devicePixelRatio of the views directly here (instead of
|
||||
// going through a MediaQuery) because if it changes, whatever is aware of
|
||||
|
@ -554,7 +598,6 @@ void paintImage({
|
|||
0.0,
|
||||
(double previousValue, ui.FlutterView view) => math.max(previousValue, view.devicePixelRatio),
|
||||
);
|
||||
|
||||
final ImageSizeInfo sizeInfo = ImageSizeInfo(
|
||||
// Some ImageProvider implementations may not have given this.
|
||||
source: debugImageLabel ?? '<Unknown Image(${image.width}×${image.height})>',
|
||||
|
@ -599,7 +642,7 @@ void paintImage({
|
|||
return true;
|
||||
}());
|
||||
// Avoid emitting events that are the same as those emitted in the last frame.
|
||||
if (!kReleaseMode && !_lastFrameImageSizeInfo.contains(sizeInfo)) {
|
||||
if (!_lastFrameImageSizeInfo.contains(sizeInfo)) {
|
||||
final ImageSizeInfo? existingSizeInfo = _pendingImageSizeInfo[sizeInfo.source];
|
||||
if (existingSizeInfo == null || existingSizeInfo.displaySizeInBytes < sizeInfo.displaySizeInBytes) {
|
||||
_pendingImageSizeInfo[sizeInfo.source!] = sizeInfo;
|
||||
|
@ -691,3 +734,99 @@ Iterable<Rect> _generateImageTileRects(Rect outputRect, Rect fundamentalRect, Im
|
|||
}
|
||||
|
||||
Rect _scaleRect(Rect rect, double scale) => Rect.fromLTRB(rect.left * scale, rect.top * scale, rect.right * scale, rect.bottom * scale);
|
||||
|
||||
// Implements DecorationImage.lerp when the image is different.
|
||||
//
|
||||
// This class just paints both decorations on top of each other, blended together.
|
||||
//
|
||||
// The Decoration properties are faked by just forwarded to the target image.
|
||||
class _BlendedDecorationImage implements DecorationImage {
|
||||
const _BlendedDecorationImage(this.a, this.b, this.t) : assert(a != null || b != null);
|
||||
|
||||
final DecorationImage? a;
|
||||
final DecorationImage? b;
|
||||
final double t;
|
||||
|
||||
@override
|
||||
ImageProvider get image => b?.image ?? a!.image;
|
||||
@override
|
||||
ImageErrorListener? get onError => b?.onError ?? a!.onError;
|
||||
@override
|
||||
ColorFilter? get colorFilter => b?.colorFilter ?? a!.colorFilter;
|
||||
@override
|
||||
BoxFit? get fit => b?.fit ?? a!.fit;
|
||||
@override
|
||||
AlignmentGeometry get alignment => b?.alignment ?? a!.alignment;
|
||||
@override
|
||||
Rect? get centerSlice => b?.centerSlice ?? a!.centerSlice;
|
||||
@override
|
||||
ImageRepeat get repeat => b?.repeat ?? a!.repeat;
|
||||
@override
|
||||
bool get matchTextDirection => b?.matchTextDirection ?? a!.matchTextDirection;
|
||||
@override
|
||||
double get scale => b?.scale ?? a!.scale;
|
||||
@override
|
||||
double get opacity => b?.opacity ?? a!.opacity;
|
||||
@override
|
||||
FilterQuality get filterQuality => b?.filterQuality ?? a!.filterQuality;
|
||||
@override
|
||||
bool get invertColors => b?.invertColors ?? a!.invertColors;
|
||||
@override
|
||||
bool get isAntiAlias => b?.isAntiAlias ?? a!.isAntiAlias;
|
||||
|
||||
@override
|
||||
DecorationImagePainter createPainter(VoidCallback onChanged) {
|
||||
return _BlendedDecorationImagePainter._(
|
||||
a?.createPainter(onChanged),
|
||||
b?.createPainter(onChanged),
|
||||
t,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
return other is _BlendedDecorationImage
|
||||
&& other.a == a
|
||||
&& other.b == b
|
||||
&& other.t == t;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(a, b, t);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '${objectRuntimeType(this, '_BlendedDecorationImage')}($a, $b, $t)';
|
||||
}
|
||||
}
|
||||
|
||||
class _BlendedDecorationImagePainter implements DecorationImagePainter {
|
||||
_BlendedDecorationImagePainter._(this.a, this.b, this.t);
|
||||
|
||||
final DecorationImagePainter? a;
|
||||
final DecorationImagePainter? b;
|
||||
final double t;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Rect rect, Path? clipPath, ImageConfiguration configuration, { double blend = 1.0, BlendMode blendMode = BlendMode.srcOver }) {
|
||||
a?.paint(canvas, rect, clipPath, configuration, blend: blend * (1.0 - t), blendMode: blendMode);
|
||||
b?.paint(canvas, rect, clipPath, configuration, blend: blend * t, blendMode: a != null ? BlendMode.plus : blendMode);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
a?.dispose();
|
||||
b?.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '${objectRuntimeType(this, '_BlendedDecorationImagePainter')}($a, $b, $t)';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,7 +237,7 @@ class ShapeDecoration extends Decoration {
|
|||
return ShapeDecoration(
|
||||
color: Color.lerp(a?.color, b?.color, t),
|
||||
gradient: Gradient.lerp(a?.gradient, b?.gradient, t),
|
||||
image: t < 0.5 ? a?.image : b?.image, // TODO(ianh): cross-fade the image
|
||||
image: DecorationImage.lerp(a?.image, b?.image, t),
|
||||
shadows: BoxShadow.lerpList(a?.shadows, b?.shadows, t),
|
||||
shape: ShapeBorder.lerp(a?.shape, b?.shape, t)!,
|
||||
);
|
||||
|
|
440
packages/flutter/test/painting/decoration_image_lerp_test.dart
Normal file
440
packages/flutter/test/painting/decoration_image_lerp_test.dart
Normal file
|
@ -0,0 +1,440 @@
|
|||
// 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.
|
||||
|
||||
// This file is run as part of a reduced test set in CI on Mac and Windows
|
||||
// machines because it contains golden tests; see:
|
||||
// https://github.com/flutter/flutter/wiki/Writing-a-golden-file-test-for-package:flutter#reduced-test-set-tag
|
||||
@Tags(<String>['reduced-test-set'])
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('ImageDecoration.lerp', (WidgetTester tester) async {
|
||||
final MemoryImage green = MemoryImage(Uint8List.fromList(<int>[
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 0xdb, 0x56,
|
||||
0xca, 0x00, 0x00, 0x00, 0x03, 0x50, 0x4c, 0x54, 0x45, 0x00, 0xff, 0x00, 0x34, 0x5e, 0xc0, 0xa8,
|
||||
0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x01, 0xe2, 0x21, 0xbc, 0x33, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42,
|
||||
0x60, 0x82,
|
||||
]));
|
||||
final MemoryImage red = MemoryImage(Uint8List.fromList(<int>[
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 0xdb, 0x56,
|
||||
0xca, 0x00, 0x00, 0x00, 0x03, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x00, 0x00, 0x19, 0xe2, 0x09, 0x37,
|
||||
0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x01, 0xe2, 0x21, 0xbc, 0x33, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42,
|
||||
0x60, 0x82,
|
||||
]));
|
||||
|
||||
await tester.runAsync(() async {
|
||||
await load(green);
|
||||
await load(red);
|
||||
});
|
||||
|
||||
await tester.pumpWidget(
|
||||
ColoredBox(
|
||||
color: Colors.white,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: RepaintBoundary(
|
||||
child: Wrap(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <Widget>[
|
||||
TestImage(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat)
|
||||
),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: red, repeat: ImageRepeat.repeat),
|
||||
0.0,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: red, repeat: ImageRepeat.repeat),
|
||||
0.1,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: red, repeat: ImageRepeat.repeat),
|
||||
0.2,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: red, repeat: ImageRepeat.repeat),
|
||||
0.5,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: red, repeat: ImageRepeat.repeat),
|
||||
0.8,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: red, repeat: ImageRepeat.repeat),
|
||||
0.9,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: red, repeat: ImageRepeat.repeat),
|
||||
1.0,
|
||||
)),
|
||||
TestImage(
|
||||
DecorationImage(image: red, repeat: ImageRepeat.repeat),
|
||||
),
|
||||
for (double t = 0.0; t < 1.0; t += 0.125)
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
t,
|
||||
),
|
||||
DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
t,
|
||||
),
|
||||
t,
|
||||
)),
|
||||
for (double t = 0.0; t < 1.0; t += 0.125)
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
1.0 - t,
|
||||
),
|
||||
DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
t,
|
||||
),
|
||||
t,
|
||||
)),
|
||||
for (double t = 0.0; t < 1.0; t += 0.125)
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
t,
|
||||
),
|
||||
DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
1.0 - t,
|
||||
),
|
||||
t,
|
||||
)),
|
||||
for (double t = 0.0; t < 1.0; t += 0.125)
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
1.0 - t,
|
||||
),
|
||||
DecorationImage.lerp(
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: green, repeat: ImageRepeat.repeat),
|
||||
1.0 - t,
|
||||
),
|
||||
t,
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
find.byType(Wrap),
|
||||
matchesGoldenFile('decoration_image.lerp.0.png'),
|
||||
);
|
||||
|
||||
if (!kIsWeb) { // TODO(ianh): https://github.com/flutter/flutter/issues/130610
|
||||
final ui.Image image = (await tester.binding.runAsync<ui.Image>(() => captureImage(find.byType(Wrap).evaluate().single)))!;
|
||||
final Uint8List bytes = (await tester.binding.runAsync<ByteData?>(() => image.toByteData(format: ui.ImageByteFormat.rawStraightRgba)))!.buffer.asUint8List();
|
||||
expect(image.width, 792);
|
||||
expect(image.height, 48);
|
||||
expect(bytes, hasLength(image.width * image.height * 4));
|
||||
Color getPixel(int x, int y) {
|
||||
final int offset = (x + y * image.width) * 4;
|
||||
return Color.fromARGB(0xFF, bytes[offset], bytes[offset + 1], bytes[offset + 2]);
|
||||
}
|
||||
Color getBlockPixel(int index) {
|
||||
int x = 12 + index * 24;
|
||||
final int y = 12 + (x ~/ image.width) * 24;
|
||||
x %= image.width;
|
||||
return getPixel(x, y);
|
||||
}
|
||||
const Color lime = Color(0xFF00FF00);
|
||||
expect(getBlockPixel(0), lime); // pure green
|
||||
expect(getBlockPixel(1), lime); // 100% green 0% red
|
||||
expect(getBlockPixel(2), const Color(0xFF19E600));
|
||||
expect(getBlockPixel(3), const Color(0xFF33CC00));
|
||||
expect(getBlockPixel(4), const Color(0xFF808000)); // 50-50 mix green/red
|
||||
expect(getBlockPixel(5), const Color(0xFFCD3200));
|
||||
expect(getBlockPixel(6), const Color(0xFFE61900));
|
||||
expect(getBlockPixel(7), const Color(0xFFFF0000)); // 0% green 100% red
|
||||
expect(getBlockPixel(8), const Color(0xFFFF0000)); // pure red
|
||||
for (int index = 9; index < 40; index += 1) {
|
||||
expect(getBlockPixel(index), lime);
|
||||
}
|
||||
}
|
||||
}, skip: kIsWeb); // TODO(ianh): https://github.com/flutter/flutter/issues/130612, https://github.com/flutter/flutter/issues/130609
|
||||
|
||||
testWidgets('ImageDecoration.lerp', (WidgetTester tester) async {
|
||||
final MemoryImage cmyk = MemoryImage(Uint8List.fromList(<int>[
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
|
||||
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x02, 0x03, 0x00, 0x00, 0x00, 0xd4, 0x9f, 0x76,
|
||||
0xed, 0x00, 0x00, 0x00, 0x0c, 0x50, 0x4c, 0x54, 0x45, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
|
||||
0xff, 0x00, 0x00, 0x00, 0x00, 0x3b, 0x4c, 0x59, 0x13, 0x00, 0x00, 0x00, 0x0e, 0x49, 0x44, 0x41,
|
||||
0x54, 0x08, 0xd7, 0x63, 0x60, 0x05, 0xc2, 0xf5, 0x0c, 0xeb, 0x01, 0x03, 0x00, 0x01, 0x69, 0x19,
|
||||
0xea, 0x34, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
|
||||
]));
|
||||
final MemoryImage wrgb = MemoryImage(Uint8List.fromList(<int>[
|
||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
|
||||
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x02, 0x03, 0x00, 0x00, 0x00, 0xd4, 0x9f, 0x76,
|
||||
0xed, 0x00, 0x00, 0x00, 0x0c, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00,
|
||||
0xff, 0x00, 0xff, 0x00, 0x00, 0x1e, 0x46, 0xbb, 0x1c, 0x00, 0x00, 0x00, 0x0e, 0x49, 0x44, 0x41,
|
||||
0x54, 0x08, 0xd7, 0x63, 0xe0, 0x07, 0xc2, 0xa5, 0x0c, 0x4b, 0x01, 0x03, 0x50, 0x01, 0x69, 0x4a,
|
||||
0x78, 0x1d, 0x41, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
|
||||
]));
|
||||
|
||||
await tester.runAsync(() async {
|
||||
await load(cmyk);
|
||||
await load(wrgb);
|
||||
});
|
||||
|
||||
await tester.pumpWidget(
|
||||
ColoredBox(
|
||||
color: Colors.white,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: RepaintBoundary(
|
||||
child: Wrap(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <Widget>[
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, fit: BoxFit.contain),
|
||||
DecorationImage(image: cmyk, fit: BoxFit.contain),
|
||||
0.0,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, fit: BoxFit.contain),
|
||||
DecorationImage(image: cmyk, fit: BoxFit.contain),
|
||||
0.1,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, fit: BoxFit.contain),
|
||||
DecorationImage(image: cmyk, fit: BoxFit.contain),
|
||||
0.2,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, fit: BoxFit.contain),
|
||||
DecorationImage(image: cmyk, fit: BoxFit.contain),
|
||||
0.5,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, fit: BoxFit.contain),
|
||||
DecorationImage(image: cmyk, fit: BoxFit.contain),
|
||||
0.8,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, fit: BoxFit.contain),
|
||||
DecorationImage(image: cmyk, fit: BoxFit.contain),
|
||||
0.9,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, fit: BoxFit.contain),
|
||||
DecorationImage(image: cmyk, fit: BoxFit.contain),
|
||||
1.0,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, fit: BoxFit.cover),
|
||||
DecorationImage(image: cmyk, repeat: ImageRepeat.repeat),
|
||||
0.5,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, repeat: ImageRepeat.repeat),
|
||||
DecorationImage(image: cmyk, repeat: ImageRepeat.repeatY),
|
||||
0.5,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, repeat: ImageRepeat.repeatX),
|
||||
DecorationImage(image: cmyk, repeat: ImageRepeat.repeat),
|
||||
0.5,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, repeat: ImageRepeat.repeat, opacity: 0.2),
|
||||
DecorationImage(image: cmyk, repeat: ImageRepeat.repeat, opacity: 0.2),
|
||||
0.25,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, repeat: ImageRepeat.repeat, opacity: 0.2),
|
||||
DecorationImage(image: cmyk, repeat: ImageRepeat.repeat, opacity: 0.2),
|
||||
0.5,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, repeat: ImageRepeat.repeat, opacity: 0.2),
|
||||
DecorationImage(image: cmyk, repeat: ImageRepeat.repeat, opacity: 0.2),
|
||||
0.75,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: wrgb, scale: 0.5, repeat: ImageRepeat.repeatX),
|
||||
DecorationImage(image: cmyk, scale: 0.25, repeat: ImageRepeat.repeatY),
|
||||
0.5,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
0.0,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
0.25,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
0.5,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
0.75,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
1.0,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(0.0, 0.0, 1.0, 1.0)),
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
0.0,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(0.0, 0.0, 1.0, 1.0)),
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
0.25,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(0.0, 0.0, 1.0, 1.0)),
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
0.5,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(0.0, 0.0, 1.0, 1.0)),
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
0.75,
|
||||
)),
|
||||
TestImage(DecorationImage.lerp(
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(0.0, 0.0, 1.0, 1.0)),
|
||||
DecorationImage(image: cmyk, centerSlice: const Rect.fromLTWH(2.0, 2.0, 1.0, 1.0)),
|
||||
1.0,
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
find.byType(Wrap),
|
||||
matchesGoldenFile('decoration_image.lerp.1.png'),
|
||||
);
|
||||
|
||||
if (!kIsWeb) { // TODO(ianh): https://github.com/flutter/flutter/issues/130610
|
||||
final ui.Image image = (await tester.binding.runAsync<ui.Image>(() => captureImage(find.byType(Wrap).evaluate().single)))!;
|
||||
final Uint8List bytes = (await tester.binding.runAsync<ByteData?>(() => image.toByteData(format: ui.ImageByteFormat.rawStraightRgba)))!.buffer.asUint8List();
|
||||
expect(image.width, 24 * 24);
|
||||
expect(image.height, 1 * 24);
|
||||
expect(bytes, hasLength(image.width * image.height * 4));
|
||||
Color getPixel(int x, int y) {
|
||||
final int offset = (x + y * image.width) * 4;
|
||||
return Color.fromARGB(0xFF, bytes[offset], bytes[offset + 1], bytes[offset + 2]);
|
||||
}
|
||||
Color getPixelFromBlock(int index, int dx, int dy) {
|
||||
const int padding = 2;
|
||||
int x = index * 24 + dx + padding;
|
||||
final int y = (x ~/ image.width) * 24 + dy + padding;
|
||||
x %= image.width;
|
||||
return getPixel(x, y);
|
||||
}
|
||||
// wrgb image
|
||||
expect(getPixelFromBlock(0, 5, 5), const Color(0xFFFFFFFF));
|
||||
expect(getPixelFromBlock(0, 15, 5), const Color(0xFFFF0000));
|
||||
expect(getPixelFromBlock(0, 5, 15), const Color(0xFF00FF00));
|
||||
expect(getPixelFromBlock(0, 15, 15), const Color(0xFF0000FF));
|
||||
// wrgb/cmyk 50/50 blended image
|
||||
expect(getPixelFromBlock(3, 5, 5), const Color(0xFF80FFFF));
|
||||
expect(getPixelFromBlock(3, 15, 5), const Color(0xFFFF0080));
|
||||
expect(getPixelFromBlock(3, 5, 15), const Color(0xFF80FF00));
|
||||
expect(getPixelFromBlock(3, 15, 15), const Color(0xFF000080));
|
||||
// cmyk image
|
||||
expect(getPixelFromBlock(6, 5, 5), const Color(0xFF00FFFF));
|
||||
expect(getPixelFromBlock(6, 15, 5), const Color(0xFFFF00FF));
|
||||
expect(getPixelFromBlock(6, 5, 15), const Color(0xFFFFFF00));
|
||||
expect(getPixelFromBlock(6, 15, 15), const Color(0xFF000000));
|
||||
// top left corner control
|
||||
expect(getPixelFromBlock(14, 0, 0), const Color(0xFF00FFFF));
|
||||
expect(getPixelFromBlock(14, 1, 1), const Color(0xFF00FFFF));
|
||||
expect(getPixelFromBlock(14, 2, 0), const Color(0xFFFF00FF));
|
||||
expect(getPixelFromBlock(14, 19, 0), const Color(0xFFFF00FF));
|
||||
expect(getPixelFromBlock(14, 0, 2), const Color(0xFFFFFF00));
|
||||
expect(getPixelFromBlock(14, 0, 19), const Color(0xFFFFFF00));
|
||||
expect(getPixelFromBlock(14, 2, 2), const Color(0xFF000000));
|
||||
expect(getPixelFromBlock(14, 19, 19), const Color(0xFF000000));
|
||||
// bottom right corner control
|
||||
expect(getPixelFromBlock(19, 0, 0), const Color(0xFF00FFFF));
|
||||
expect(getPixelFromBlock(19, 17, 17), const Color(0xFF00FFFF));
|
||||
expect(getPixelFromBlock(19, 19, 0), const Color(0xFFFF00FF));
|
||||
expect(getPixelFromBlock(19, 19, 17), const Color(0xFFFF00FF));
|
||||
expect(getPixelFromBlock(19, 0, 19), const Color(0xFFFFFF00));
|
||||
expect(getPixelFromBlock(19, 17, 19), const Color(0xFFFFFF00));
|
||||
expect(getPixelFromBlock(19, 18, 18), const Color(0xFF000000));
|
||||
expect(getPixelFromBlock(19, 19, 19), const Color(0xFF000000));
|
||||
}
|
||||
}, skip: kIsWeb); // TODO(ianh): https://github.com/flutter/flutter/issues/130612, https://github.com/flutter/flutter/issues/130609
|
||||
}
|
||||
|
||||
Future<void> load(MemoryImage image) {
|
||||
final ImageStream stream = image.resolve(ImageConfiguration.empty);
|
||||
final Completer<ImageInfo> completer = Completer<ImageInfo>();
|
||||
void listener(ImageInfo image, bool syncCall) {
|
||||
completer.complete(image);
|
||||
}
|
||||
stream.addListener(ImageStreamListener(listener));
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
class TestImage extends StatelessWidget {
|
||||
TestImage(this.image); // ignore: use_key_in_widget_constructors, prefer_const_constructors_in_immutables
|
||||
|
||||
final DecorationImage? image;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
image: image,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue