@@ -177,7 +177,7 @@ class DecorationImage {
177
177
/// image needs to be repainted, e.g. because it is loading incrementally or
178
178
/// because it is animated.
179
179
DecorationImagePainter createPainter (VoidCallback onChanged) {
180
- return DecorationImagePainter ._(this , onChanged);
180
+ return _DecorationImagePainter ._(this , onChanged);
181
181
}
182
182
183
183
@override
@@ -246,6 +246,28 @@ class DecorationImage {
246
246
];
247
247
return '${objectRuntimeType (this , 'DecorationImage' )}(${properties .join (", " )})' ;
248
248
}
249
+
250
+ /// Linearly interpolates between two [DecorationImage] s.
251
+ ///
252
+ /// The `t` argument represents position on the timeline, with 0.0 meaning
253
+ /// that the interpolation has not started, returning `a` , 1.0 meaning that
254
+ /// the interpolation has finished, returning `b` , and values in between
255
+ /// meaning that the interpolation is at the relevant point on the timeline
256
+ /// between `a` and `this` . The interpolation can be extrapolated beyond 0.0
257
+ /// and 1.0, so negative values and values greater than 1.0 are valid (and can
258
+ /// easily be generated by curves such as [Curves.elasticInOut] ).
259
+ ///
260
+ /// Values for `t` are usually obtained from an [Animation<double>] , such as
261
+ /// an [AnimationController] .
262
+ static DecorationImage ? lerp (DecorationImage ? a, DecorationImage ? b, double t) {
263
+ if (identical (a, b) || t == 0.0 ) {
264
+ return a;
265
+ }
266
+ if (t == 1.0 ) {
267
+ return b;
268
+ }
269
+ return _BlendedDecorationImage (a, b, t);
270
+ }
249
271
}
250
272
251
273
/// The painter for a [DecorationImage] .
@@ -259,15 +281,7 @@ class DecorationImage {
259
281
///
260
282
/// This object should be disposed using the [dispose] method when it is no
261
283
/// longer needed.
262
- class DecorationImagePainter {
263
- DecorationImagePainter ._(this ._details, this ._onChanged);
264
-
265
- final DecorationImage _details;
266
- final VoidCallback _onChanged;
267
-
268
- ImageStream ? _imageStream;
269
- ImageInfo ? _image;
270
-
284
+ abstract interface class DecorationImagePainter {
271
285
/// Draw the image onto the given canvas.
272
286
///
273
287
/// The image is drawn at the position and size given by the `rect` argument.
@@ -282,8 +296,34 @@ class DecorationImagePainter {
282
296
/// because it had not yet been loaded the first time this method was called,
283
297
/// then the `onChanged` callback passed to [DecorationImage.createPainter]
284
298
/// will be called.
285
- void paint (Canvas canvas, Rect rect, Path ? clipPath, ImageConfiguration configuration) {
299
+ ///
300
+ /// The `blend` argument specifies the opacity that should be applied to the
301
+ /// image due to this image being blended with another. The `blendMode`
302
+ /// argument can be specified to override the [DecorationImagePainter] 's
303
+ /// default [BlendMode] behavior. It is usually set to [BlendMode.srcOver] if
304
+ /// this is the first or only image being blended, and [BlendMode.plus] if it
305
+ /// is being blended with an image below.
306
+ void paint (Canvas canvas, Rect rect, Path ? clipPath, ImageConfiguration configuration, { double blend = 1.0 , BlendMode blendMode = BlendMode .srcOver });
286
307
308
+ /// Releases the resources used by this painter.
309
+ ///
310
+ /// This should be called whenever the painter is no longer needed.
311
+ ///
312
+ /// After this method has been called, the object is no longer usable.
313
+ void dispose ();
314
+ }
315
+
316
+ class _DecorationImagePainter implements DecorationImagePainter {
317
+ _DecorationImagePainter ._(this ._details, this ._onChanged);
318
+
319
+ final DecorationImage _details;
320
+ final VoidCallback _onChanged;
321
+
322
+ ImageStream ? _imageStream;
323
+ ImageInfo ? _image;
324
+
325
+ @override
326
+ void paint (Canvas canvas, Rect rect, Path ? clipPath, ImageConfiguration configuration, { double blend = 1.0 , BlendMode blendMode = BlendMode .srcOver }) {
287
327
bool flipHorizontally = false ;
288
328
if (_details.matchTextDirection) {
289
329
assert (() {
@@ -338,10 +378,11 @@ class DecorationImagePainter {
338
378
centerSlice: _details.centerSlice,
339
379
repeat: _details.repeat,
340
380
flipHorizontally: flipHorizontally,
341
- opacity: _details.opacity,
381
+ opacity: _details.opacity * blend ,
342
382
filterQuality: _details.filterQuality,
343
383
invertColors: _details.invertColors,
344
384
isAntiAlias: _details.isAntiAlias,
385
+ blendMode: blendMode,
345
386
);
346
387
347
388
if (clipPath != null ) {
@@ -364,12 +405,7 @@ class DecorationImagePainter {
364
405
}
365
406
}
366
407
367
- /// Releases the resources used by this painter.
368
- ///
369
- /// This should be called whenever the painter is no longer needed.
370
- ///
371
- /// After this method has been called, the object is no longer usable.
372
- @mustCallSuper
408
+ @override
373
409
void dispose () {
374
410
_imageStream? .removeListener (ImageStreamListener (
375
411
_handleImage,
@@ -444,7 +480,7 @@ void debugFlushLastFrameImageSizeInfo() {
444
480
/// corners of the destination rectangle defined by applying `fit`. The
445
481
/// remaining five regions are drawn by stretching them to fit such that they
446
482
/// exactly cover the destination rectangle while maintaining their relative
447
- /// positions.
483
+ /// positions. See also [Canvas.drawImageNine].
448
484
///
449
485
/// * `repeat` : If the image does not fill `rect` , whether and how the image
450
486
/// should be repeated to fill `rect`. By default, the image is not repeated.
@@ -490,6 +526,7 @@ void paintImage({
490
526
bool invertColors = false ,
491
527
FilterQuality filterQuality = FilterQuality .low,
492
528
bool isAntiAlias = false ,
529
+ BlendMode blendMode = BlendMode .srcOver,
493
530
}) {
494
531
assert (
495
532
image.debugGetOpenHandleStackTraces ()? .isNotEmpty ?? true ,
@@ -530,9 +567,10 @@ void paintImage({
530
567
if (colorFilter != null ) {
531
568
paint.colorFilter = colorFilter;
532
569
}
533
- paint.color = Color .fromRGBO (0 , 0 , 0 , opacity);
570
+ paint.color = Color .fromRGBO (0 , 0 , 0 , clampDouble ( opacity, 0.0 , 1.0 ) );
534
571
paint.filterQuality = filterQuality;
535
572
paint.invertColors = invertColors;
573
+ paint.blendMode = blendMode;
536
574
final double halfWidthDelta = (outputSize.width - destinationSize.width) / 2.0 ;
537
575
final double halfHeightDelta = (outputSize.height - destinationSize.height) / 2.0 ;
538
576
final double dx = halfWidthDelta + (flipHorizontally ? - alignment.x : alignment.x) * halfWidthDelta;
@@ -543,6 +581,12 @@ void paintImage({
543
581
// Set to true if we added a saveLayer to the canvas to invert/flip the image.
544
582
bool invertedCanvas = false ;
545
583
// Output size and destination rect are fully calculated.
584
+
585
+ // Implement debug-mode and profile-mode features:
586
+ // - cacheWidth/cacheHeight warning
587
+ // - debugInvertOversizedImages
588
+ // - debugOnPaintImage
589
+ // - Flutter.ImageSizesForFrame events in timeline
546
590
if (! kReleaseMode) {
547
591
// We can use the devicePixelRatio of the views directly here (instead of
548
592
// going through a MediaQuery) because if it changes, whatever is aware of
@@ -554,7 +598,6 @@ void paintImage({
554
598
0.0 ,
555
599
(double previousValue, ui.FlutterView view) => math.max (previousValue, view.devicePixelRatio),
556
600
);
557
-
558
601
final ImageSizeInfo sizeInfo = ImageSizeInfo (
559
602
// Some ImageProvider implementations may not have given this.
560
603
source: debugImageLabel ?? '<Unknown Image(${image .width }×${image .height })>' ,
@@ -599,7 +642,7 @@ void paintImage({
599
642
return true ;
600
643
}());
601
644
// Avoid emitting events that are the same as those emitted in the last frame.
602
- if (! kReleaseMode && ! _lastFrameImageSizeInfo.contains (sizeInfo)) {
645
+ if (! _lastFrameImageSizeInfo.contains (sizeInfo)) {
603
646
final ImageSizeInfo ? existingSizeInfo = _pendingImageSizeInfo[sizeInfo.source];
604
647
if (existingSizeInfo == null || existingSizeInfo.displaySizeInBytes < sizeInfo.displaySizeInBytes) {
605
648
_pendingImageSizeInfo[sizeInfo.source! ] = sizeInfo;
@@ -691,3 +734,99 @@ Iterable<Rect> _generateImageTileRects(Rect outputRect, Rect fundamentalRect, Im
691
734
}
692
735
693
736
Rect _scaleRect (Rect rect, double scale) => Rect .fromLTRB (rect.left * scale, rect.top * scale, rect.right * scale, rect.bottom * scale);
737
+
738
+ // Implements DecorationImage.lerp when the image is different.
739
+ //
740
+ // This class just paints both decorations on top of each other, blended together.
741
+ //
742
+ // The Decoration properties are faked by just forwarded to the target image.
743
+ class _BlendedDecorationImage implements DecorationImage {
744
+ const _BlendedDecorationImage (this .a, this .b, this .t) : assert (a != null || b != null );
745
+
746
+ final DecorationImage ? a;
747
+ final DecorationImage ? b;
748
+ final double t;
749
+
750
+ @override
751
+ ImageProvider get image => b? .image ?? a! .image;
752
+ @override
753
+ ImageErrorListener ? get onError => b? .onError ?? a! .onError;
754
+ @override
755
+ ColorFilter ? get colorFilter => b? .colorFilter ?? a! .colorFilter;
756
+ @override
757
+ BoxFit ? get fit => b? .fit ?? a! .fit;
758
+ @override
759
+ AlignmentGeometry get alignment => b? .alignment ?? a! .alignment;
760
+ @override
761
+ Rect ? get centerSlice => b? .centerSlice ?? a! .centerSlice;
762
+ @override
763
+ ImageRepeat get repeat => b? .repeat ?? a! .repeat;
764
+ @override
765
+ bool get matchTextDirection => b? .matchTextDirection ?? a! .matchTextDirection;
766
+ @override
767
+ double get scale => b? .scale ?? a! .scale;
768
+ @override
769
+ double get opacity => b? .opacity ?? a! .opacity;
770
+ @override
771
+ FilterQuality get filterQuality => b? .filterQuality ?? a! .filterQuality;
772
+ @override
773
+ bool get invertColors => b? .invertColors ?? a! .invertColors;
774
+ @override
775
+ bool get isAntiAlias => b? .isAntiAlias ?? a! .isAntiAlias;
776
+
777
+ @override
778
+ DecorationImagePainter createPainter (VoidCallback onChanged) {
779
+ return _BlendedDecorationImagePainter ._(
780
+ a? .createPainter (onChanged),
781
+ b? .createPainter (onChanged),
782
+ t,
783
+ );
784
+ }
785
+
786
+ @override
787
+ bool operator == (Object other) {
788
+ if (identical (this , other)) {
789
+ return true ;
790
+ }
791
+ if (other.runtimeType != runtimeType) {
792
+ return false ;
793
+ }
794
+ return other is _BlendedDecorationImage
795
+ && other.a == a
796
+ && other.b == b
797
+ && other.t == t;
798
+ }
799
+
800
+ @override
801
+ int get hashCode => Object .hash (a, b, t);
802
+
803
+ @override
804
+ String toString () {
805
+ return '${objectRuntimeType (this , '_BlendedDecorationImage' )}($a , $b , $t )' ;
806
+ }
807
+ }
808
+
809
+ class _BlendedDecorationImagePainter implements DecorationImagePainter {
810
+ _BlendedDecorationImagePainter ._(this .a, this .b, this .t);
811
+
812
+ final DecorationImagePainter ? a;
813
+ final DecorationImagePainter ? b;
814
+ final double t;
815
+
816
+ @override
817
+ void paint (Canvas canvas, Rect rect, Path ? clipPath, ImageConfiguration configuration, { double blend = 1.0 , BlendMode blendMode = BlendMode .srcOver }) {
818
+ a? .paint (canvas, rect, clipPath, configuration, blend: blend * (1.0 - t), blendMode: blendMode);
819
+ b? .paint (canvas, rect, clipPath, configuration, blend: blend * t, blendMode: a != null ? BlendMode .plus : blendMode);
820
+ }
821
+
822
+ @override
823
+ void dispose () {
824
+ a? .dispose ();
825
+ b? .dispose ();
826
+ }
827
+
828
+ @override
829
+ String toString () {
830
+ return '${objectRuntimeType (this , '_BlendedDecorationImagePainter' )}($a , $b , $t )' ;
831
+ }
832
+ }
0 commit comments