Skip to content

Commit 237515c

Browse files
Revert "[framework] use shader tiling instead of repeated calls to drawImage (#119495)"
This reverts commit 69421c1.
1 parent cf18912 commit 237515c

File tree

4 files changed

+110
-160
lines changed

4 files changed

+110
-160
lines changed

packages/flutter/lib/src/painting/decoration_image.dart

Lines changed: 5 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import 'dart:ui' as ui show FlutterView, Image;
88

99
import 'package:flutter/foundation.dart';
1010
import 'package:flutter/scheduler.dart';
11-
import 'package:vector_math/vector_math_64.dart';
1211

1312
import 'alignment.dart';
1413
import 'basic_types.dart';
@@ -405,61 +404,6 @@ void debugFlushLastFrameImageSizeInfo() {
405404
}());
406405
}
407406

408-
/// Information that describes how to tile an image for a given [ImageRepeat]
409-
/// enum.
410-
///
411-
/// Used with [createTilingInfo].
412-
@visibleForTesting
413-
@immutable
414-
class ImageTilingInfo {
415-
/// Create a new [ImageTilingInfo] object.
416-
const ImageTilingInfo({
417-
required this.tmx,
418-
required this.tmy,
419-
required this.transform,
420-
});
421-
422-
/// The tile mode for the x-axis.
423-
final TileMode tmx;
424-
425-
/// The tile mode for the y-axis.
426-
final TileMode tmy;
427-
428-
/// The transform to apply to the image shader.
429-
final Matrix4 transform;
430-
431-
@override
432-
String toString() {
433-
if (!kDebugMode) {
434-
return 'ImageTilingInfo';
435-
}
436-
return 'ImageTilingInfo($tmx, $tmy, $transform)';
437-
}
438-
}
439-
440-
/// Create the [ImageTilingInfo] for a given [ImageRepeat], canvas [rect],
441-
/// [destinationRect], and [sourceRect].
442-
@visibleForTesting
443-
ImageTilingInfo createTilingInfo(ImageRepeat repeat, Rect rect, Rect destinationRect, Rect sourceRect) {
444-
assert(repeat != ImageRepeat.noRepeat);
445-
final TileMode tmx = (repeat == ImageRepeat.repeatX || repeat == ImageRepeat.repeat)
446-
? TileMode.repeated
447-
: TileMode.decal;
448-
final TileMode tmy = (repeat == ImageRepeat.repeatY || repeat == ImageRepeat.repeat)
449-
? TileMode.repeated
450-
: TileMode.decal;
451-
final Rect data = _generateImageTileRects(rect, destinationRect, repeat).first;
452-
final Matrix4 transform = Matrix4.identity()
453-
..scale(data.width / sourceRect.width, data.height / sourceRect.height)
454-
..setTranslationRaw(data.topLeft.dx, data.topLeft.dy, 0);
455-
456-
return ImageTilingInfo(
457-
tmx: tmx,
458-
tmy: tmy,
459-
transform: transform,
460-
);
461-
}
462-
463407
/// Paints an image into the given rectangle on the canvas.
464408
///
465409
/// The arguments have the following meanings:
@@ -682,8 +626,7 @@ void paintImage({
682626
if (needSave) {
683627
canvas.save();
684628
}
685-
if (repeat != ImageRepeat.noRepeat && centerSlice != null) {
686-
// Don't clip if an image shader is used.
629+
if (repeat != ImageRepeat.noRepeat) {
687630
canvas.clipRect(rect);
688631
}
689632
if (flipHorizontally) {
@@ -699,12 +642,9 @@ void paintImage({
699642
if (repeat == ImageRepeat.noRepeat) {
700643
canvas.drawImageRect(image, sourceRect, destinationRect, paint);
701644
} else {
702-
final ImageTilingInfo info = createTilingInfo(repeat, rect, destinationRect, sourceRect);
703-
final ImageShader shader = ImageShader(image, info.tmx, info.tmy, info.transform.storage, filterQuality: filterQuality);
704-
canvas.drawRect(
705-
rect,
706-
paint..shader = shader
707-
);
645+
for (final Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat)) {
646+
canvas.drawImageRect(image, sourceRect, tileRect, paint);
647+
}
708648
}
709649
} else {
710650
canvas.scale(1 / scale);
@@ -725,7 +665,7 @@ void paintImage({
725665
}
726666
}
727667

728-
List<Rect> _generateImageTileRects(Rect outputRect, Rect fundamentalRect, ImageRepeat repeat) {
668+
Iterable<Rect> _generateImageTileRects(Rect outputRect, Rect fundamentalRect, ImageRepeat repeat) {
729669
int startX = 0;
730670
int startY = 0;
731671
int stopX = 0;

packages/flutter/test/painting/decoration_test.dart

Lines changed: 37 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import 'package:fake_async/fake_async.dart';
99
import 'package:flutter/foundation.dart';
1010
import 'package:flutter/painting.dart';
1111
import 'package:flutter_test/flutter_test.dart';
12-
import 'package:vector_math/vector_math_64.dart';
1312

1413
import '../image_data.dart';
1514
import '../painting/mocks_for_image_cache.dart';
@@ -679,8 +678,7 @@ void main() {
679678
final TestCanvas canvas = TestCanvas();
680679

681680
// Paint a square image into an output rect that is twice as wide as it is
682-
// tall. One copy of the image should be painted, aligned so that a repeating
683-
// tile mode causes it to appear twice.
681+
// tall. Two copies of the image should be painted, one next to the other.
684682
const Rect outputRect = Rect.fromLTWH(30.0, 30.0, 400.0, 200.0);
685683
final ui.Image image = await createTestImage(width: 100, height: 100);
686684

@@ -693,29 +691,34 @@ void main() {
693691
repeat: ImageRepeat.repeatX,
694692
);
695693

696-
final List<Invocation> calls = canvas.invocations.where((Invocation call) => call.memberName == #drawRect).toList();
694+
const Size imageSize = Size(100.0, 100.0);
697695

698-
expect(calls, hasLength(1));
699-
final Invocation call = calls[0];
700-
expect(call.isMethod, isTrue);
701-
expect(call.positionalArguments, hasLength(2));
696+
final List<Invocation> calls = canvas.invocations.where((Invocation call) => call.memberName == #drawImageRect).toList();
697+
final Set<Rect> tileRects = <Rect>{};
698+
699+
expect(calls, hasLength(2));
700+
for (final Invocation call in calls) {
701+
expect(call.isMethod, isTrue);
702+
expect(call.positionalArguments, hasLength(4));
702703

703-
// A tiled image is drawn as a rect with a shader.
704-
expect(call.positionalArguments[0], isA<Rect>());
705-
expect(call.positionalArguments[1], isA<Paint>());
704+
expect(call.positionalArguments[0], isA<ui.Image>());
705+
706+
// sourceRect should contain all pixels of the source image
707+
expect(call.positionalArguments[1], Offset.zero & imageSize);
706708

707-
final Paint paint = call.positionalArguments[1] as Paint;
709+
tileRects.add(call.positionalArguments[2] as Rect);
708710

709-
expect(paint.shader, isA<ImageShader>());
710-
expect(call.positionalArguments[0], outputRect);
711+
expect(call.positionalArguments[3], isA<Paint>());
712+
}
713+
714+
expect(tileRects, <Rect>{const Rect.fromLTWH(30.0, 30.0, 200.0, 200.0), const Rect.fromLTWH(230.0, 30.0, 200.0, 200.0)});
711715
});
712716

713717
test('paintImage with repeatY and fitWidth', () async {
714718
final TestCanvas canvas = TestCanvas();
715719

716720
// Paint a square image into an output rect that is twice as tall as it is
717-
// wide. One copy of the image should be painted, aligned so that a repeating
718-
// tile mode causes it to appear twice.
721+
// wide. Two copies of the image should be painted, one above the other.
719722
const Rect outputRect = Rect.fromLTWH(30.0, 30.0, 200.0, 400.0);
720723
final ui.Image image = await createTestImage(width: 100, height: 100);
721724

@@ -727,21 +730,28 @@ void main() {
727730
fit: BoxFit.fitWidth,
728731
repeat: ImageRepeat.repeatY,
729732
);
730-
final List<Invocation> calls = canvas.invocations.where((Invocation call) => call.memberName == #drawRect).toList();
731733

732-
expect(calls, hasLength(1));
733-
final Invocation call = calls[0];
734-
expect(call.isMethod, isTrue);
735-
expect(call.positionalArguments, hasLength(2));
734+
const Size imageSize = Size(100.0, 100.0);
736735

737-
// A tiled image is drawn as a rect with a shader.
738-
expect(call.positionalArguments[0], isA<Rect>());
739-
expect(call.positionalArguments[1], isA<Paint>());
736+
final List<Invocation> calls = canvas.invocations.where((Invocation call) => call.memberName == #drawImageRect).toList();
737+
final Set<Rect> tileRects = <Rect>{};
740738

741-
final Paint paint = call.positionalArguments[1] as Paint;
739+
expect(calls, hasLength(2));
740+
for (final Invocation call in calls) {
741+
expect(call.isMethod, isTrue);
742+
expect(call.positionalArguments, hasLength(4));
742743

743-
expect(paint.shader, isA<ImageShader>());
744-
expect(call.positionalArguments[0], outputRect);
744+
expect(call.positionalArguments[0], isA<ui.Image>());
745+
746+
// sourceRect should contain all pixels of the source image
747+
expect(call.positionalArguments[1], Offset.zero & imageSize);
748+
749+
tileRects.add(call.positionalArguments[2] as Rect);
750+
751+
expect(call.positionalArguments[3], isA<Paint>());
752+
}
753+
754+
expect(tileRects, <Rect>{const Rect.fromLTWH(30.0, 30.0, 200.0, 200.0), const Rect.fromLTWH(30.0, 230.0, 200.0, 200.0)});
745755
});
746756

747757
test('DecorationImage scale test', () async {
@@ -790,52 +800,4 @@ void main() {
790800

791801
info.dispose();
792802
}, skip: kIsWeb); // https://github.com/flutter/flutter/issues/87442
793-
794-
test('Compute image tiling', () {
795-
expect(() => createTilingInfo(ImageRepeat.noRepeat, Rect.zero, Rect.zero, Rect.zero), throwsAssertionError);
796-
797-
// These tests draw a 16x9 image into a 100x50 container with a destination
798-
// size of and make assertions based on observed behavior and the original
799-
// rectangles from https://github.com/flutter/flutter/pull/119495/
800-
801-
final ImageTilingInfo repeatX = createTilingInfo(
802-
ImageRepeat.repeatX,
803-
const Rect.fromLTRB(0.0, 0.0, 100.0, 50.0),
804-
const Rect.fromLTRB(84.0, 0.0, 100.0, 9.0),
805-
const Rect.fromLTRB(0.0, 0.0, 16.0, 9.0),
806-
);
807-
808-
expect(repeatX.tmx, TileMode.repeated);
809-
expect(repeatX.tmy, TileMode.decal);
810-
expect(repeatX.transform, matrixMoreOrLessEquals(Matrix4.identity()
811-
..scale(1.0, 1.0)
812-
..setTranslationRaw(-12.0, 0.0, 0.0)
813-
));
814-
815-
final ImageTilingInfo repeatY = createTilingInfo(
816-
ImageRepeat.repeatY,
817-
const Rect.fromLTRB(0.0, 0.0, 100.0, 50.0),
818-
const Rect.fromLTRB(84.0, 0.0, 100.0, 9.0),
819-
const Rect.fromLTRB(0.0, 0.0, 16.0, 9.0),
820-
);
821-
expect(repeatY.tmx, TileMode.decal);
822-
expect(repeatY.tmy, TileMode.repeated);
823-
expect(repeatY.transform, matrixMoreOrLessEquals(Matrix4.identity()
824-
..scale(1.0, 1.0)
825-
..setTranslationRaw(84.0, 0.0, 0.0)
826-
));
827-
828-
final ImageTilingInfo repeat = createTilingInfo(
829-
ImageRepeat.repeat,
830-
const Rect.fromLTRB(0.0, 0.0, 100.0, 50.0),
831-
const Rect.fromLTRB(84.0, 0.0, 100.0, 9.0),
832-
const Rect.fromLTRB(0.0, 0.0, 16.0, 9.0),
833-
);
834-
expect(repeat.tmx, TileMode.repeated);
835-
expect(repeat.tmy, TileMode.repeated);
836-
expect(repeat.transform, matrixMoreOrLessEquals(Matrix4.identity()
837-
..scale(1.0, 1.0)
838-
..setTranslationRaw(-12.0, 0.0, 0.0)
839-
));
840-
});
841803
}

packages/flutter/test/rendering/mock_canvas.dart

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ abstract class PaintPattern {
193193
/// painting has completed, not at the time of the call. If the same [Paint]
194194
/// object is reused multiple times, then this may not match the actual
195195
/// arguments as they were seen by the method.
196-
void rect({ Rect? rect, Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style, Matcher? shader });
196+
void rect({ Rect? rect, Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style });
197197

198198
/// Indicates that a rounded rectangle clip is expected next.
199199
///
@@ -734,8 +734,8 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher imp
734734
}
735735

736736
@override
737-
void rect({ Rect? rect, Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style, Matcher? shader }) {
738-
_predicates.add(_RectPaintPredicate(rect: rect, color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style, shader: shader));
737+
void rect({ Rect? rect, Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style }) {
738+
_predicates.add(_RectPaintPredicate(rect: rect, color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style));
739739
}
740740

741741
@override
@@ -891,7 +891,6 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate {
891891
this.strokeWidth,
892892
this.hasMaskFilter,
893893
this.style,
894-
this.shader,
895894
});
896895

897896
final Symbol symbol;
@@ -902,7 +901,6 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate {
902901
final double? strokeWidth;
903902
final bool? hasMaskFilter;
904903
final PaintingStyle? style;
905-
final Matcher? shader;
906904

907905
String get methodName => _symbolName(symbol);
908906

@@ -937,9 +935,6 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate {
937935
if (style != null && paintArgument.style != style) {
938936
throw 'It called $methodName with a paint whose style, ${paintArgument.style}, was not exactly the expected style ($style).';
939937
}
940-
if (shader != null && !shader!.matches(paintArgument.shader, <dynamic, dynamic>{})) {
941-
throw 'It called $methodName with a paint whose shader, ${paintArgument.shader}, was not exactly the expected shader ($shader).';
942-
}
943938
}
944939

945940
@override
@@ -980,7 +975,6 @@ class _OneParameterPaintPredicate<T> extends _DrawCommandPaintPredicate {
980975
required double? strokeWidth,
981976
required bool? hasMaskFilter,
982977
required PaintingStyle? style,
983-
Matcher? shader,
984978
}) : super(
985979
symbol,
986980
name,
@@ -990,7 +984,6 @@ class _OneParameterPaintPredicate<T> extends _DrawCommandPaintPredicate {
990984
strokeWidth: strokeWidth,
991985
hasMaskFilter: hasMaskFilter,
992986
style: style,
993-
shader: shader,
994987
);
995988

996989
final T? expected;
@@ -1076,15 +1069,14 @@ class _TwoParameterPaintPredicate<T1, T2> extends _DrawCommandPaintPredicate {
10761069
}
10771070

10781071
class _RectPaintPredicate extends _OneParameterPaintPredicate<Rect> {
1079-
_RectPaintPredicate({ Rect? rect, Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style, Matcher? shader }) : super(
1072+
_RectPaintPredicate({ Rect? rect, Color? color, double? strokeWidth, bool? hasMaskFilter, PaintingStyle? style }) : super(
10801073
#drawRect,
10811074
'a rectangle',
10821075
expected: rect,
10831076
color: color,
10841077
strokeWidth: strokeWidth,
10851078
hasMaskFilter: hasMaskFilter,
10861079
style: style,
1087-
shader: shader,
10881080
);
10891081
}
10901082

0 commit comments

Comments
 (0)