Fix html version of drawImageNine (#25605)
diff --git a/lib/web_ui/lib/src/engine/html/canvas.dart b/lib/web_ui/lib/src/engine/html/canvas.dart
index 6d275b7..675e730 100644
--- a/lib/web_ui/lib/src/engine/html/canvas.dart
+++ b/lib/web_ui/lib/src/engine/html/canvas.dart
@@ -287,6 +287,47 @@
_canvas.drawImageRect(image, src, dst, paint as SurfacePaint);
}
+ // Return a list of slice coordinates based on the size of the nine-slice parameters in
+ // one dimension. Each set of slice coordinates contains a begin/end pair for each of the
+ // source (image) and dest (screen) in the order (src0, dst0, src1, dst1).
+ // The area from src0 => src1 of the image is painted on the screen from dst0 => dst1
+ // The slices for each dimension are generated independently.
+ List<double> _initSlices(double img0, double imgC0, double imgC1, double img1, double dst0, double dst1) {
+ final double imageDim = img1 - img0;
+ final double destDim = dst1 - dst0;
+
+ if (imageDim == destDim) {
+ // If the src and dest are the same size then we do not need scaling
+ // We return 4 values for a single slice
+ return <double>[ img0, dst0, img1, dst1 ];
+ }
+
+ final double edge0Dim = imgC0 - img0;
+ final double edge1Dim = img1 - imgC1;
+ final double edgesDim = edge0Dim + edge1Dim;
+
+ if (edgesDim >= destDim) {
+ // the center portion has disappeared, leaving only the edges to scale to a common
+ // center position in the destination
+ // this produces only 2 slices which is 8 values
+ double dstC = dst0 + destDim * edge0Dim / edgesDim;
+ return <double>[
+ img0, dst0, imgC0, dstC,
+ imgC1, dstC, img1, dst1,
+ ];
+ }
+
+ // center portion is nonEmpty and only that part is scaled
+ // we need 3 slices which is 12 values
+ final double dstC0 = dst0 + edge0Dim;
+ final double dstC1 = dst1 - edge1Dim;
+ return <double>[
+ img0, dst0, imgC0, dstC0,
+ imgC0, dstC0, imgC1, dstC1,
+ imgC1, dstC1, img1, dst1
+ ];
+ }
+
@override
void drawImageNine(
ui.Image image, ui.Rect center, ui.Rect dst, ui.Paint paint) {
@@ -296,147 +337,44 @@
assert(rectIsValid(dst));
assert(paint != null); // ignore: unnecessary_null_comparison
- // Assert you can fit the scaled part of the image (exluding the
- // center source).
- assert(image.width - center.width < dst.width);
- assert(image.height - center.height < dst.height);
+ if (dst.isEmpty)
+ return;
- // The four unscaled corner rectangles in the from the src.
- final ui.Rect srcTopLeft = ui.Rect.fromLTWH(
- 0,
+ final List<double> hSlices = _initSlices(
0,
center.left,
- center.top,
- );
- final ui.Rect srcTopRight = ui.Rect.fromLTWH(
center.right,
- 0,
- image.width - center.right,
- center.top,
- );
- final ui.Rect srcBottomLeft = ui.Rect.fromLTWH(
- 0,
- center.bottom,
- center.left,
- image.height - center.bottom,
- );
- final ui.Rect srcBottomRight = ui.Rect.fromLTWH(
- center.right,
- center.bottom,
- image.width - center.right,
- image.height - center.bottom,
- );
-
- final ui.Rect dstTopLeft = srcTopLeft.shift(dst.topLeft);
-
- // The center rectangle in the dst region
- final ui.Rect dstCenter = ui.Rect.fromLTWH(
- dstTopLeft.right,
- dstTopLeft.bottom,
- dst.width - (srcTopLeft.width + srcTopRight.width),
- dst.height - (srcTopLeft.height + srcBottomLeft.height),
- );
-
- drawImageRect(image, srcTopLeft, dstTopLeft, paint);
-
- final ui.Rect dstTopRight = ui.Rect.fromLTWH(
- dstCenter.right,
- dst.top,
- srcTopRight.width,
- srcTopRight.height,
- );
- drawImageRect(image, srcTopRight, dstTopRight, paint);
-
- final ui.Rect dstBottomLeft = ui.Rect.fromLTWH(
+ image.width.toDouble(),
dst.left,
- dstCenter.bottom,
- srcBottomLeft.width,
- srcBottomLeft.height,
+ dst.right,
);
- drawImageRect(image, srcBottomLeft, dstBottomLeft, paint);
-
- final ui.Rect dstBottomRight = ui.Rect.fromLTWH(
- dstCenter.right,
- dstCenter.bottom,
- srcBottomRight.width,
- srcBottomRight.height,
- );
- drawImageRect(image, srcBottomRight, dstBottomRight, paint);
-
- // Draw the top center rectangle.
- drawImageRect(
- image,
- ui.Rect.fromLTRB(
- srcTopLeft.right,
- srcTopLeft.top,
- srcTopRight.left,
- srcTopRight.bottom,
- ),
- ui.Rect.fromLTRB(
- dstTopLeft.right,
- dstTopLeft.top,
- dstTopRight.left,
- dstTopRight.bottom,
- ),
- paint,
+ final List<double> vSlices = _initSlices(
+ 0,
+ center.top,
+ center.bottom,
+ image.height.toDouble(),
+ dst.top,
+ dst.bottom,
);
- // Draw the middle left rectangle.
- drawImageRect(
- image,
- ui.Rect.fromLTRB(
- srcTopLeft.left,
- srcTopLeft.bottom,
- srcBottomLeft.right,
- srcBottomLeft.top,
- ),
- ui.Rect.fromLTRB(
- dstTopLeft.left,
- dstTopLeft.bottom,
- dstBottomLeft.right,
- dstBottomLeft.top,
- ),
- paint,
- );
-
- // Draw the center rectangle.
- drawImageRect(image, center, dstCenter, paint);
-
- // Draw the middle right rectangle.
- drawImageRect(
- image,
- ui.Rect.fromLTRB(
- srcTopRight.left,
- srcTopRight.bottom,
- srcBottomRight.right,
- srcBottomRight.top,
- ),
- ui.Rect.fromLTRB(
- dstTopRight.left,
- dstTopRight.bottom,
- dstBottomRight.right,
- dstBottomRight.top,
- ),
- paint,
- );
-
- // Draw the bottom center rectangle.
- drawImageRect(
- image,
- ui.Rect.fromLTRB(
- srcBottomLeft.right,
- srcBottomLeft.top,
- srcBottomRight.left,
- srcBottomRight.bottom,
- ),
- ui.Rect.fromLTRB(
- dstBottomLeft.right,
- dstBottomLeft.top,
- dstBottomRight.left,
- dstBottomRight.bottom,
- ),
- paint,
- );
+ for (int yi = 0; yi < vSlices.length; yi += 4) {
+ double srcY0 = vSlices[yi];
+ double dstY0 = vSlices[yi + 1];
+ double srcY1 = vSlices[yi + 2];
+ double dstY1 = vSlices[yi + 3];
+ for (int xi = 0; xi < hSlices.length; xi += 4) {
+ double srcX0 = hSlices[xi];
+ double dstX0 = hSlices[xi + 1];
+ double srcX1 = hSlices[xi + 2];
+ double dstX1 = hSlices[xi + 3];
+ drawImageRect(
+ image,
+ ui.Rect.fromLTRB(srcX0, srcY0, srcX1, srcY1),
+ ui.Rect.fromLTRB(dstX0, dstY0, dstX1, dstY1),
+ paint,
+ );
+ }
+ }
}
@override
diff --git a/lib/web_ui/test/golden_tests/engine/canvas_draw_image_golden_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_draw_image_golden_test.dart
index 2137367..9075089 100644
--- a/lib/web_ui/test/golden_tests/engine/canvas_draw_image_golden_test.dart
+++ b/lib/web_ui/test/golden_tests/engine/canvas_draw_image_golden_test.dart
@@ -398,6 +398,39 @@
}
});
+ // Regression test for https://github.com/flutter/flutter/issues/78068
+ // Tests for correct behavior when using drawImageNine with a destination
+ // size that is too small to render the center portion of the original image.
+ test('Paints nine slice image', () async {
+ Rect region = const Rect.fromLTWH(0, 0, 100, 100);
+ EnginePictureRecorder recorder = EnginePictureRecorder();
+ final Canvas canvas = Canvas(recorder, region);
+ Image testImage = createNineSliceImage();
+ canvas.clipRect(Rect.fromLTWH(0, 0, 100, 100));
+ // The testImage is 60x60 and the center slice is 20x20 so the edges
+ // of the image are 40x40. Drawing into a destination that is smaller
+ // than that will not provide enough room to draw the center portion.
+ canvas.drawImageNine(testImage, Rect.fromLTWH(20, 20, 20, 20),
+ Rect.fromLTWH(20, 20, 36, 36), Paint());
+ Picture picture = recorder.endRecording();
+
+ final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
+ builder.addPicture(Offset(0, 0), picture);
+
+ // Wrap in <flt-scene> so that our CSS selectors kick in.
+ final html.Element sceneElement = html.Element.tag('flt-scene');
+ try {
+ sceneElement.append(builder.build().webOnlyRootElement);
+ html.document.body.append(sceneElement);
+ await matchGoldenFile('draw_nine_slice_empty_center.png',
+ region: region, maxDiffRatePercent: 0);
+ } finally {
+ // The page is reused across tests, so remove the element after taking the
+ // Scuba screenshot.
+ sceneElement.remove();
+ }
+ });
+
// Regression test for https://github.com/flutter/flutter/issues/61691
//
// The bug in bitmap_canvas.dart was that when we transformed and clipped