compensate for DPR in canvas blur (#18484)
* compensate for DPR in canvas blur
diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml
index 906f324..36047fa 100644
--- a/lib/web_ui/dev/goldens_lock.yaml
+++ b/lib/web_ui/dev/goldens_lock.yaml
@@ -1,2 +1,2 @@
repository: https://github.com/flutter/goldens.git
-revision: 04c9d19f106cf0b1bf409bf691bd5334950553ea
+revision: f80e019e225915e220f0f37884c8cd1b942d9bc2
diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart
index 831307f..0f94c43 100644
--- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart
+++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart
@@ -973,7 +973,9 @@
'WebKit (Safari) does not support `filter` canvas property.',
);
if (maskFilter != null) {
- return 'blur(${maskFilter.webOnlySigma}px)';
+ // Multiply by device-pixel ratio because the canvas' pixel width and height
+ // are larger than its CSS width and height by device-pixel ratio.
+ return 'blur(${maskFilter.webOnlySigma * window.devicePixelRatio}px)';
} else {
return 'none';
}
diff --git a/lib/web_ui/lib/src/engine/surface/painting.dart b/lib/web_ui/lib/src/engine/surface/painting.dart
index 5a14d93..cce4564 100644
--- a/lib/web_ui/lib/src/engine/surface/painting.dart
+++ b/lib/web_ui/lib/src/engine/surface/painting.dart
@@ -246,4 +246,45 @@
..strokeJoin = strokeJoin
..strokeCap = strokeCap;
}
+
+ @override
+ String toString() {
+ if (!assertionsEnabled) {
+ return super.toString();
+ } else {
+ final StringBuffer buffer = StringBuffer('SurfacePaintData(');
+ if (blendMode != null) {
+ buffer.write('blendMode = $blendMode; ');
+ }
+ if (style != null) {
+ buffer.write('style = $style; ');
+ }
+ if (strokeWidth != null) {
+ buffer.write('strokeWidth = $strokeWidth; ');
+ }
+ if (strokeCap != null) {
+ buffer.write('strokeCap = $strokeCap; ');
+ }
+ if (strokeJoin != null) {
+ buffer.write('strokeJoin = $strokeJoin; ');
+ }
+ if (color != null) {
+ buffer.write('color = ${colorToCssString(color)}; ');
+ }
+ if (shader != null) {
+ buffer.write('shader = $shader; ');
+ }
+ if (maskFilter != null) {
+ buffer.write('maskFilter = $maskFilter; ');
+ }
+ if (filterQuality != null) {
+ buffer.write('filterQuality = $filterQuality; ');
+ }
+ if (colorFilter != null) {
+ buffer.write('colorFilter = $colorFilter; ');
+ }
+ buffer.write('isAntiAlias = $isAntiAlias)');
+ return buffer.toString();
+ }
+ }
}
diff --git a/lib/web_ui/test/golden_tests/engine/canvas_mask_filter_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_mask_filter_test.dart
index 77b86bf..a808a57 100644
--- a/lib/web_ui/test/golden_tests/engine/canvas_mask_filter_test.dart
+++ b/lib/web_ui/test/golden_tests/engine/canvas_mask_filter_test.dart
@@ -7,7 +7,7 @@
import 'dart:math' as math;
import 'dart:typed_data';
-import 'package:ui/ui.dart' hide TextStyle;
+import 'package:ui/ui.dart' as ui;
import 'package:ui/src/engine.dart';
import 'package:test/test.dart';
@@ -15,7 +15,7 @@
void main() async {
// Commit a recording canvas to a bitmap, and compare with the expected
- Future<void> _checkScreenshot(RecordingCanvas rc, String fileName, Rect screenRect) async {
+ Future<void> _checkScreenshot(RecordingCanvas rc, String fileName, ui.Rect screenRect, {bool write = false}) async {
final EngineCanvas engineCanvas = BitmapCanvas(screenRect);
rc.endRecording();
@@ -26,7 +26,7 @@
try {
sceneElement.append(engineCanvas.rootElement);
html.document.body.append(sceneElement);
- await matchGoldenFile('$fileName.png', region: screenRect, maxDiffRatePercent: 0.0);
+ await matchGoldenFile('$fileName.png', region: screenRect, maxDiffRatePercent: 0.0, write: write);
} finally {
// The page is reused across tests, so remove the element after taking the
// Scuba screenshot.
@@ -35,10 +35,10 @@
}
setUp(() async {
- debugEmulateFlutterTesterEnvironment = true;
- await webOnlyInitializePlatform();
- webOnlyFontCollection.debugRegisterTestFonts();
- await webOnlyFontCollection.ensureFontsLoaded();
+ ui.debugEmulateFlutterTesterEnvironment = true;
+ await ui.webOnlyInitializePlatform();
+ ui.webOnlyFontCollection.debugRegisterTestFonts();
+ await ui.webOnlyFontCollection.ensureFontsLoaded();
});
tearDown(() {
@@ -52,37 +52,37 @@
test('renders MaskFilter.blur in $browser', () async {
const double screenWidth = 800.0;
const double screenHeight = 150.0;
- const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
+ const ui.Rect screenRect = ui.Rect.fromLTWH(0, 0, screenWidth, screenHeight);
ContextStateHandle.debugEmulateWebKitMaskFilter = isSafariMode;
final RecordingCanvas rc = RecordingCanvas(screenRect);
rc.translate(0, 75);
final SurfacePaint paint = SurfacePaint()
- ..maskFilter = MaskFilter.blur(BlurStyle.normal, 5);
+ ..maskFilter = ui.MaskFilter.blur(ui.BlurStyle.normal, 5);
rc.translate(50, 0);
rc.drawRect(
- Rect.fromCircle(center: Offset.zero, radius: 30),
+ ui.Rect.fromCircle(center: ui.Offset.zero, radius: 30),
paint,
);
rc.translate(100, 0);
- paint.color = Color(0xFF00FF00);
+ paint.color = ui.Color(0xFF00FF00);
rc.drawRRect(
- RRect.fromRectAndRadius(
- Rect.fromCircle(center: Offset.zero, radius: 30),
- Radius.circular(20),
+ ui.RRect.fromRectAndRadius(
+ ui.Rect.fromCircle(center: ui.Offset.zero, radius: 30),
+ ui.Radius.circular(20),
),
paint,
);
rc.translate(100, 0);
- paint.color = Color(0xFF0000FF);
- rc.drawCircle(Offset.zero, 30, paint);
+ paint.color = ui.Color(0xFF0000FF);
+ rc.drawCircle(ui.Offset.zero, 30, paint);
rc.translate(100, 0);
- paint.color = Color(0xFF00FFFF);
+ paint.color = ui.Color(0xFF00FFFF);
rc.drawPath(
SurfacePath()
..moveTo(-20, 0)
@@ -94,39 +94,39 @@
);
rc.translate(100, 0);
- paint.color = Color(0xFFFF00FF);
+ paint.color = ui.Color(0xFFFF00FF);
rc.drawOval(
- Rect.fromCenter(center: Offset.zero, width: 40, height: 100),
+ ui.Rect.fromCenter(center: ui.Offset.zero, width: 40, height: 100),
paint,
);
rc.translate(100, 0);
- paint.color = Color(0xFF888800);
+ paint.color = ui.Color(0xFF888800);
paint.strokeWidth = 5;
rc.drawLine(
- Offset(-20, -50),
- Offset(20, 50),
+ ui.Offset(-20, -50),
+ ui.Offset(20, 50),
paint,
);
rc.translate(100, 0);
- paint.color = Color(0xFF888888);
+ paint.color = ui.Color(0xFF888888);
rc.drawDRRect(
- RRect.fromRectAndRadius(
- Rect.fromCircle(center: Offset.zero, radius: 35),
- Radius.circular(20),
+ ui.RRect.fromRectAndRadius(
+ ui.Rect.fromCircle(center: ui.Offset.zero, radius: 35),
+ ui.Radius.circular(20),
),
- RRect.fromRectAndRadius(
- Rect.fromCircle(center: Offset.zero, radius: 15),
- Radius.circular(7),
+ ui.RRect.fromRectAndRadius(
+ ui.Rect.fromCircle(center: ui.Offset.zero, radius: 15),
+ ui.Radius.circular(7),
),
paint,
);
rc.translate(100, 0);
- paint.color = Color(0xFF6500C9);
+ paint.color = ui.Color(0xFF6500C9);
rc.drawRawPoints(
- PointMode.points,
+ ui.PointMode.points,
Float32List.fromList([-10, -10, -10, 10, 10, -10, 10, 10]),
paint,
);
@@ -137,31 +137,31 @@
test('renders transformed MaskFilter.blur in $browser', () async {
const double screenWidth = 300.0;
const double screenHeight = 300.0;
- const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
+ const ui.Rect screenRect = ui.Rect.fromLTWH(0, 0, screenWidth, screenHeight);
ContextStateHandle.debugEmulateWebKitMaskFilter = isSafariMode;
final RecordingCanvas rc = RecordingCanvas(screenRect);
rc.translate(150, 150);
final SurfacePaint paint = SurfacePaint()
- ..maskFilter = MaskFilter.blur(BlurStyle.normal, 5);
+ ..maskFilter = ui.MaskFilter.blur(ui.BlurStyle.normal, 5);
- final List<Color> colors = <Color>[
- Color(0xFF000000),
- Color(0xFF00FF00),
- Color(0xFF0000FF),
- Color(0xFF00FFFF),
- Color(0xFFFF00FF),
- Color(0xFF888800),
- Color(0xFF888888),
- Color(0xFF6500C9),
+ final List<ui.Color> colors = <ui.Color>[
+ ui.Color(0xFF000000),
+ ui.Color(0xFF00FF00),
+ ui.Color(0xFF0000FF),
+ ui.Color(0xFF00FFFF),
+ ui.Color(0xFFFF00FF),
+ ui.Color(0xFF888800),
+ ui.Color(0xFF888888),
+ ui.Color(0xFF6500C9),
];
- for (Color color in colors) {
+ for (ui.Color color in colors) {
paint.color = color;
rc.rotate(math.pi / 4);
rc.drawRect(
- Rect.fromCircle(center: const Offset(90, 0), radius: 20),
+ ui.Rect.fromCircle(center: const ui.Offset(90, 0), radius: 20),
paint,
);
}
@@ -172,4 +172,26 @@
testMaskFilterBlur(isSafariMode: false);
testMaskFilterBlur(isSafariMode: true);
+
+ for (int testDpr in <int>[1, 2, 4]) {
+ test('MaskFilter.blur blurs correctly for device-pixel ratio $testDpr', () async {
+ window.debugOverrideDevicePixelRatio(testDpr.toDouble());
+ const ui.Rect screenRect = ui.Rect.fromLTWH(0, 0, 150, 150);
+
+ final RecordingCanvas rc = RecordingCanvas(screenRect);
+ rc.translate(0, 75);
+
+ final SurfacePaint paint = SurfacePaint()
+ ..maskFilter = ui.MaskFilter.blur(ui.BlurStyle.normal, 5);
+
+ rc.translate(75, 0);
+ rc.drawRect(
+ ui.Rect.fromCircle(center: ui.Offset.zero, radius: 30),
+ paint,
+ );
+
+ await _checkScreenshot(rc, 'mask_filter_blur_dpr_$testDpr', screenRect);
+ window.debugOverrideDevicePixelRatio(1.0);
+ });
+ }
}