diff --git a/analysis_options.yaml b/analysis_options.yaml index a9d29631e1..747d8e24b4 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -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 # see https://github.com/dart-lang/linter/issues/4558 + - avoid_implementing_value_types - avoid_init_to_null - avoid_js_rounded_ints # - avoid_multiple_declarations_per_line # seems to be a stylistic choice we don't subscribe to diff --git a/packages/flutter/lib/foundation.dart b/packages/flutter/lib/foundation.dart index 378b206d74..43ac813387 100644 --- a/packages/flutter/lib/foundation.dart +++ b/packages/flutter/lib/foundation.dart @@ -34,6 +34,7 @@ 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'; diff --git a/packages/flutter/lib/src/foundation/README.md b/packages/flutter/lib/src/foundation/README.md index d32effc8c2..5e4de0755a 100644 --- a/packages/flutter/lib/src/foundation/README.md +++ b/packages/flutter/lib/src/foundation/README.md @@ -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 -`clampDouble` (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 +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,4 +14,5 @@ PlatformDispatcher, window), though that should probably move to the See also: - * https://github.com/dart-lang/sdk/issues/25217 + * https://github.com/dart-lang/sdk/issues/27791 (`VoidCallback`) + * https://github.com/dart-lang/sdk/issues/25217 (`hashValues`, `hashList`, and `lerpDouble`) diff --git a/packages/flutter/lib/src/foundation/binding.dart b/packages/flutter/lib/src/foundation/binding.dart index 83204bd57b..de0bac79bc 100644 --- a/packages/flutter/lib/src/foundation/binding.dart +++ b/packages/flutter/lib/src/foundation/binding.dart @@ -22,7 +22,7 @@ import 'print.dart'; import 'service_extensions.dart'; import 'timeline.dart'; -export 'dart:ui' show PlatformDispatcher, SingletonFlutterWindow, clampDouble; // ignore: deprecated_member_use +export 'dart:ui' show PlatformDispatcher, SingletonFlutterWindow; // ignore: deprecated_member_use export 'basic_types.dart' show AsyncCallback, AsyncValueGetter, AsyncValueSetter; diff --git a/packages/flutter/lib/src/foundation/diagnostics.dart b/packages/flutter/lib/src/foundation/diagnostics.dart index e5c1345ade..f4f9d5c4c0 100644 --- a/packages/flutter/lib/src/foundation/diagnostics.dart +++ b/packages/flutter/lib/src/foundation/diagnostics.dart @@ -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: diff --git a/packages/flutter/lib/src/foundation/math.dart b/packages/flutter/lib/src/foundation/math.dart new file mode 100644 index 0000000000..053192ad50 --- /dev/null +++ b/packages/flutter/lib/src/foundation/math.dart @@ -0,0 +1,23 @@ +// 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; +} diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index 0b13366825..57300b5601 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui' show clampDouble, lerpDouble; +import 'dart:ui'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart' show clampDouble; import 'color_scheme.dart'; import 'colors.dart'; diff --git a/packages/flutter/lib/src/painting/box_decoration.dart b/packages/flutter/lib/src/painting/box_decoration.dart index e076247d99..444eb40608 100644 --- a/packages/flutter/lib/src/painting/box_decoration.dart +++ b/packages/flutter/lib/src/painting/box_decoration.dart @@ -232,7 +232,7 @@ class BoxDecoration extends Decoration { BoxDecoration scale(double factor) { return BoxDecoration( color: Color.lerp(null, color, factor), - image: DecorationImage.lerp(null, image, factor), + image: image, // TODO(ianh): fade the image from transparent 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: DecorationImage.lerp(a.image, b.image, t), + image: t < 0.5 ? a.image : b.image, // TODO(ianh): cross-fade the image border: BoxBorder.lerp(a.border, b.border, t), borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, b.borderRadius, t), boxShadow: BoxShadow.lerpList(a.boxShadow, b.boxShadow, t), diff --git a/packages/flutter/lib/src/painting/decoration_image.dart b/packages/flutter/lib/src/painting/decoration_image.dart index c91a9f0833..5c8ed9df03 100644 --- a/packages/flutter/lib/src/painting/decoration_image.dart +++ b/packages/flutter/lib/src/painting/decoration_image.dart @@ -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,28 +246,6 @@ 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], 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]. @@ -281,7 +259,15 @@ class DecorationImage { /// /// This object should be disposed using the [dispose] method when it is no /// longer needed. -abstract interface class DecorationImagePainter { +class DecorationImagePainter { + DecorationImagePainter._(this._details, this._onChanged); + + final DecorationImage _details; + final VoidCallback _onChanged; + + ImageStream? _imageStream; + ImageInfo? _image; + /// Draw the image onto the given canvas. /// /// The image is drawn at the position and size given by the `rect` argument. @@ -296,34 +282,8 @@ abstract interface 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. - /// - /// 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 }); + void paint(Canvas canvas, Rect rect, Path? clipPath, ImageConfiguration configuration) { - /// 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(() { @@ -378,11 +338,10 @@ class _DecorationImagePainter implements DecorationImagePainter { centerSlice: _details.centerSlice, repeat: _details.repeat, flipHorizontally: flipHorizontally, - opacity: _details.opacity * blend, + opacity: _details.opacity, filterQuality: _details.filterQuality, invertColors: _details.invertColors, isAntiAlias: _details.isAntiAlias, - blendMode: blendMode, ); if (clipPath != null) { @@ -405,7 +364,12 @@ class _DecorationImagePainter implements DecorationImagePainter { } } - @override + /// 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 void dispose() { _imageStream?.removeListener(ImageStreamListener( _handleImage, @@ -480,7 +444,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. See also [Canvas.drawImageNine]. +/// positions. /// /// * `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. @@ -526,7 +490,6 @@ void paintImage({ bool invertColors = false, FilterQuality filterQuality = FilterQuality.low, bool isAntiAlias = false, - BlendMode blendMode = BlendMode.srcOver, }) { assert( image.debugGetOpenHandleStackTraces()?.isNotEmpty ?? true, @@ -567,10 +530,9 @@ void paintImage({ if (colorFilter != null) { paint.colorFilter = colorFilter; } - paint.color = Color.fromRGBO(0, 0, 0, clampDouble(opacity, 0.0, 1.0)); + paint.color = Color.fromRGBO(0, 0, 0, opacity); 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; @@ -581,12 +543,6 @@ 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 @@ -598,6 +554,7 @@ 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 ?? '', @@ -642,7 +599,7 @@ void paintImage({ return true; }()); // Avoid emitting events that are the same as those emitted in the last frame. - if (!_lastFrameImageSizeInfo.contains(sizeInfo)) { + if (!kReleaseMode && !_lastFrameImageSizeInfo.contains(sizeInfo)) { final ImageSizeInfo? existingSizeInfo = _pendingImageSizeInfo[sizeInfo.source]; if (existingSizeInfo == null || existingSizeInfo.displaySizeInBytes < sizeInfo.displaySizeInBytes) { _pendingImageSizeInfo[sizeInfo.source!] = sizeInfo; @@ -734,99 +691,3 @@ Iterable _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)'; - } -} diff --git a/packages/flutter/lib/src/painting/shape_decoration.dart b/packages/flutter/lib/src/painting/shape_decoration.dart index bf7a0e872d..20785bced2 100644 --- a/packages/flutter/lib/src/painting/shape_decoration.dart +++ b/packages/flutter/lib/src/painting/shape_decoration.dart @@ -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: DecorationImage.lerp(a?.image, b?.image, t), + image: t < 0.5 ? a?.image : b?.image, // TODO(ianh): cross-fade the image shadows: BoxShadow.lerpList(a?.shadows, b?.shadows, t), shape: ShapeBorder.lerp(a?.shape, b?.shape, t)!, ); diff --git a/packages/flutter/test/painting/decoration_image_lerp_test.dart b/packages/flutter/test/painting/decoration_image_lerp_test.dart deleted file mode 100644 index 1e0f6f4998..0000000000 --- a/packages/flutter/test/painting/decoration_image_lerp_test.dart +++ /dev/null @@ -1,440 +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. - -// 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(['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([ - 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([ - 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: [ - 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(() => captureImage(find.byType(Wrap).evaluate().single)))!; - final Uint8List bytes = (await tester.binding.runAsync(() => 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([ - 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([ - 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: [ - 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(() => captureImage(find.byType(Wrap).evaluate().single)))!; - final Uint8List bytes = (await tester.binding.runAsync(() => 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 load(MemoryImage image) { - final ImageStream stream = image.resolve(ImageConfiguration.empty); - final Completer completer = Completer(); - 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, - ), - ), - ), - ); - } -}