[skwasm] Change default `FilterQuality` to `None` for image shaders. (#52468)
This brings the behavior in line with the CanvasKit renderer, which also uses `None` for the default image shader quality.
I added a few unit tests to cover different filter qualities. It turns out, there is a skia issue that manifests both in CanvasKit and Skwasm when a lazy image is rendered with medium quality. See https://g-issues.skia.org/issues/338095525
diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart
index 36fd442..378bc72 100644
--- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart
+++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart
@@ -186,7 +186,7 @@
image.handle,
tmx.index,
tmy.index,
- (filterQuality ?? ui.FilterQuality.medium).index,
+ (filterQuality ?? ui.FilterQuality.none).index,
localMatrix,
));
});
@@ -195,7 +195,7 @@
image.handle,
tmx.index,
tmy.index,
- (filterQuality ?? ui.FilterQuality.medium).index,
+ (filterQuality ?? ui.FilterQuality.none).index,
nullptr,
));
}
diff --git a/lib/web_ui/test/ui/image_golden_test.dart b/lib/web_ui/test/ui/image_golden_test.dart
index 9adad98..504aa6e 100644
--- a/lib/web_ui/test/ui/image_golden_test.dart
+++ b/lib/web_ui/test/ui/image_golden_test.dart
@@ -86,17 +86,23 @@
// `imageGenerator` should produce an image that is 150x150 pixels.
void emitImageTests(String name, Future<ui.Image> Function() imageGenerator) {
group(name, () {
- late ui.Image image;
- setUp(() async {
- image = await imageGenerator();
- });
+ final List<ui.Image> images = <ui.Image>[];
+
+ Future<ui.Image> generateImage() async {
+ final ui.Image image = await imageGenerator();
+ images.add(image);
+ return image;
+ }
tearDown(() {
- image.dispose();
+ for (final ui.Image image in images) {
+ image.dispose();
+ }
+ images.clear();
});
test('drawImage', () async {
- final ui.Image image = await imageGenerator();
+ final ui.Image image = await generateImage();
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
@@ -111,7 +117,7 @@
});
test('drawImageRect', () async {
- final ui.Image image = await imageGenerator();
+ final ui.Image image = await generateImage();
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
@@ -147,7 +153,7 @@
});
test('drawImageNine', () async {
- final ui.Image image = await imageGenerator();
+ final ui.Image image = await generateImage();
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
@@ -164,29 +170,42 @@
});
test('image_shader_cubic_rotated', () async {
- final ui.Image image = await imageGenerator();
-
- final Float64List matrix = Matrix4.rotationZ(pi / 6).toFloat64();
- final ui.ImageShader shader = ui.ImageShader(
- image,
- ui.TileMode.repeated,
- ui.TileMode.repeated,
- matrix,
- filterQuality: ui.FilterQuality.high,
- );
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
- canvas.drawOval(
- const ui.Rect.fromLTRB(0, 50, 300, 250),
- ui.Paint()..shader = shader
- );
+ final Float64List matrix = Matrix4.rotationZ(pi / 6).toFloat64();
+ Future<void> drawOvalWithShader(ui.Rect rect, ui.FilterQuality quality) async {
+ final ui.Image image = await generateImage();
+ final ui.ImageShader shader = ui.ImageShader(
+ image,
+ ui.TileMode.repeated,
+ ui.TileMode.repeated,
+ matrix,
+ filterQuality: quality,
+ );
+ canvas.drawOval(
+ rect,
+ ui.Paint()..shader = shader
+ );
+ }
+
+ // Draw image shader with all four qualities.
+ await drawOvalWithShader(const ui.Rect.fromLTRB(0, 0, 150, 100), ui.FilterQuality.none);
+ await drawOvalWithShader(const ui.Rect.fromLTRB(150, 0, 300, 100), ui.FilterQuality.low);
+
+ // Note that for images that skia handles lazily (ones created via
+ // `createImageFromImageBitmap` or `instantiateImageCodecFromUrl`)
+ // there is a skia bug that this just renders a black oval instead of
+ // actually texturing it with the image.
+ // See https://g-issues.skia.org/issues/338095525
+ await drawOvalWithShader(const ui.Rect.fromLTRB(0, 100, 150, 200), ui.FilterQuality.medium);
+ await drawOvalWithShader(const ui.Rect.fromLTRB(150, 100, 300, 200), ui.FilterQuality.high);
await drawPictureUsingCurrentRenderer(recorder.endRecording());
await matchGoldenFile('${name}_image_shader_cubic_rotated.png', region: drawRegion);
});
test('fragment_shader_sampler', () async {
- final ui.Image image = await imageGenerator();
+ final ui.Image image = await generateImage();
final ui.FragmentProgram program = await renderer.createFragmentProgram('glitch_shader');
final ui.FragmentShader shader = program.fragmentShader();
@@ -210,8 +229,53 @@
await matchGoldenFile('${name}_fragment_shader_sampler.png', region: drawRegion);
}, skip: isHtml); // HTML doesn't support fragment shaders
+ test('drawVertices with image shader', () async {
+ final ui.Image image = await generateImage();
+
+ final Float64List matrix = Matrix4.rotationZ(pi / 6).toFloat64();
+ final ui.ImageShader shader = ui.ImageShader(
+ image,
+ ui.TileMode.decal,
+ ui.TileMode.decal,
+ matrix,
+ );
+
+ // Draw an octagon
+ const List<ui.Offset> vertexValues = <ui.Offset>[
+ ui.Offset(50, 0),
+ ui.Offset(100, 0),
+ ui.Offset(150, 50),
+ ui.Offset(150, 100),
+ ui.Offset(100, 150),
+ ui.Offset(50, 150),
+ ui.Offset(0, 100),
+ ui.Offset(0, 50),
+ ];
+ final ui.Vertices vertices = ui.Vertices(
+ ui.VertexMode.triangles,
+ vertexValues,
+ textureCoordinates: vertexValues,
+ indices: <int>[
+ 0, 1, 2, //
+ 0, 2, 3, //
+ 0, 3, 4, //
+ 0, 4, 5, //
+ 0, 5, 6, //
+ 0, 6, 7, //
+ ],
+ );
+
+ final ui.PictureRecorder recorder = ui.PictureRecorder();
+ final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
+ canvas.drawVertices(vertices, ui.BlendMode.srcOver, ui.Paint()..shader = shader);
+
+ await drawPictureUsingCurrentRenderer(recorder.endRecording());
+
+ await matchGoldenFile('${name}_drawVertices_imageShader.png', region: drawRegion);
+ }, skip: isHtml); // https://github.com/flutter/flutter/issues/127454;
+
test('toByteData_rgba', () async {
- final ui.Image image = await imageGenerator();
+ final ui.Image image = await generateImage();
final ByteData? rgbaData = await image.toByteData();
expect(rgbaData, isNotNull);
@@ -219,7 +283,7 @@
});
test('toByteData_png', () async {
- final ui.Image image = await imageGenerator();
+ final ui.Image image = await generateImage();
final ByteData? pngData = await image.toByteData(format: ui.ImageByteFormat.png);
expect(pngData, isNotNull);