Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 21ae0cc

Browse files
[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
1 parent 14f2e8c commit 21ae0cc

File tree

2 files changed

+91
-27
lines changed

2 files changed

+91
-27
lines changed

lib/web_ui/lib/src/engine/skwasm/skwasm_impl/shaders.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ class SkwasmImageShader extends SkwasmNativeShader implements ui.ImageShader {
186186
image.handle,
187187
tmx.index,
188188
tmy.index,
189-
(filterQuality ?? ui.FilterQuality.medium).index,
189+
(filterQuality ?? ui.FilterQuality.none).index,
190190
localMatrix,
191191
));
192192
});
@@ -195,7 +195,7 @@ class SkwasmImageShader extends SkwasmNativeShader implements ui.ImageShader {
195195
image.handle,
196196
tmx.index,
197197
tmy.index,
198-
(filterQuality ?? ui.FilterQuality.medium).index,
198+
(filterQuality ?? ui.FilterQuality.none).index,
199199
nullptr,
200200
));
201201
}

lib/web_ui/test/ui/image_golden_test.dart

Lines changed: 89 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,23 @@ Future<void> testMain() async {
8686
// `imageGenerator` should produce an image that is 150x150 pixels.
8787
void emitImageTests(String name, Future<ui.Image> Function() imageGenerator) {
8888
group(name, () {
89-
late ui.Image image;
90-
setUp(() async {
91-
image = await imageGenerator();
92-
});
89+
final List<ui.Image> images = <ui.Image>[];
90+
91+
Future<ui.Image> generateImage() async {
92+
final ui.Image image = await imageGenerator();
93+
images.add(image);
94+
return image;
95+
}
9396

9497
tearDown(() {
95-
image.dispose();
98+
for (final ui.Image image in images) {
99+
image.dispose();
100+
}
101+
images.clear();
96102
});
97103

98104
test('drawImage', () async {
99-
final ui.Image image = await imageGenerator();
105+
final ui.Image image = await generateImage();
100106

101107
final ui.PictureRecorder recorder = ui.PictureRecorder();
102108
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
@@ -111,7 +117,7 @@ Future<void> testMain() async {
111117
});
112118

113119
test('drawImageRect', () async {
114-
final ui.Image image = await imageGenerator();
120+
final ui.Image image = await generateImage();
115121

116122
final ui.PictureRecorder recorder = ui.PictureRecorder();
117123
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
@@ -147,7 +153,7 @@ Future<void> testMain() async {
147153
});
148154

149155
test('drawImageNine', () async {
150-
final ui.Image image = await imageGenerator();
156+
final ui.Image image = await generateImage();
151157

152158
final ui.PictureRecorder recorder = ui.PictureRecorder();
153159
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
@@ -164,29 +170,42 @@ Future<void> testMain() async {
164170
});
165171

166172
test('image_shader_cubic_rotated', () async {
167-
final ui.Image image = await imageGenerator();
168-
169-
final Float64List matrix = Matrix4.rotationZ(pi / 6).toFloat64();
170-
final ui.ImageShader shader = ui.ImageShader(
171-
image,
172-
ui.TileMode.repeated,
173-
ui.TileMode.repeated,
174-
matrix,
175-
filterQuality: ui.FilterQuality.high,
176-
);
177173
final ui.PictureRecorder recorder = ui.PictureRecorder();
178174
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
179-
canvas.drawOval(
180-
const ui.Rect.fromLTRB(0, 50, 300, 250),
181-
ui.Paint()..shader = shader
182-
);
175+
final Float64List matrix = Matrix4.rotationZ(pi / 6).toFloat64();
176+
Future<void> drawOvalWithShader(ui.Rect rect, ui.FilterQuality quality) async {
177+
final ui.Image image = await generateImage();
178+
final ui.ImageShader shader = ui.ImageShader(
179+
image,
180+
ui.TileMode.repeated,
181+
ui.TileMode.repeated,
182+
matrix,
183+
filterQuality: quality,
184+
);
185+
canvas.drawOval(
186+
rect,
187+
ui.Paint()..shader = shader
188+
);
189+
}
190+
191+
// Draw image shader with all four qualities.
192+
await drawOvalWithShader(const ui.Rect.fromLTRB(0, 0, 150, 100), ui.FilterQuality.none);
193+
await drawOvalWithShader(const ui.Rect.fromLTRB(150, 0, 300, 100), ui.FilterQuality.low);
194+
195+
// Note that for images that skia handles lazily (ones created via
196+
// `createImageFromImageBitmap` or `instantiateImageCodecFromUrl`)
197+
// there is a skia bug that this just renders a black oval instead of
198+
// actually texturing it with the image.
199+
// See https://g-issues.skia.org/issues/338095525
200+
await drawOvalWithShader(const ui.Rect.fromLTRB(0, 100, 150, 200), ui.FilterQuality.medium);
201+
await drawOvalWithShader(const ui.Rect.fromLTRB(150, 100, 300, 200), ui.FilterQuality.high);
183202

184203
await drawPictureUsingCurrentRenderer(recorder.endRecording());
185204
await matchGoldenFile('${name}_image_shader_cubic_rotated.png', region: drawRegion);
186205
});
187206

188207
test('fragment_shader_sampler', () async {
189-
final ui.Image image = await imageGenerator();
208+
final ui.Image image = await generateImage();
190209

191210
final ui.FragmentProgram program = await renderer.createFragmentProgram('glitch_shader');
192211
final ui.FragmentShader shader = program.fragmentShader();
@@ -210,16 +229,61 @@ Future<void> testMain() async {
210229
await matchGoldenFile('${name}_fragment_shader_sampler.png', region: drawRegion);
211230
}, skip: isHtml); // HTML doesn't support fragment shaders
212231

232+
test('drawVertices with image shader', () async {
233+
final ui.Image image = await generateImage();
234+
235+
final Float64List matrix = Matrix4.rotationZ(pi / 6).toFloat64();
236+
final ui.ImageShader shader = ui.ImageShader(
237+
image,
238+
ui.TileMode.decal,
239+
ui.TileMode.decal,
240+
matrix,
241+
);
242+
243+
// Draw an octagon
244+
const List<ui.Offset> vertexValues = <ui.Offset>[
245+
ui.Offset(50, 0),
246+
ui.Offset(100, 0),
247+
ui.Offset(150, 50),
248+
ui.Offset(150, 100),
249+
ui.Offset(100, 150),
250+
ui.Offset(50, 150),
251+
ui.Offset(0, 100),
252+
ui.Offset(0, 50),
253+
];
254+
final ui.Vertices vertices = ui.Vertices(
255+
ui.VertexMode.triangles,
256+
vertexValues,
257+
textureCoordinates: vertexValues,
258+
indices: <int>[
259+
0, 1, 2, //
260+
0, 2, 3, //
261+
0, 3, 4, //
262+
0, 4, 5, //
263+
0, 5, 6, //
264+
0, 6, 7, //
265+
],
266+
);
267+
268+
final ui.PictureRecorder recorder = ui.PictureRecorder();
269+
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
270+
canvas.drawVertices(vertices, ui.BlendMode.srcOver, ui.Paint()..shader = shader);
271+
272+
await drawPictureUsingCurrentRenderer(recorder.endRecording());
273+
274+
await matchGoldenFile('${name}_drawVertices_imageShader.png', region: drawRegion);
275+
}, skip: isHtml); // https://github.com/flutter/flutter/issues/127454;
276+
213277
test('toByteData_rgba', () async {
214-
final ui.Image image = await imageGenerator();
278+
final ui.Image image = await generateImage();
215279

216280
final ByteData? rgbaData = await image.toByteData();
217281
expect(rgbaData, isNotNull);
218282
expect(rgbaData!.lengthInBytes, isNonZero);
219283
});
220284

221285
test('toByteData_png', () async {
222-
final ui.Image image = await imageGenerator();
286+
final ui.Image image = await generateImage();
223287

224288
final ByteData? pngData = await image.toByteData(format: ui.ImageByteFormat.png);
225289
expect(pngData, isNotNull);

0 commit comments

Comments
 (0)