[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);