未验证 提交 713aa25d 编写于 作者: F Ferhat 提交者: GitHub

[web] Optimize oval path clipping (#25520)

上级 9c09042f
......@@ -574,10 +574,8 @@ class BitmapCanvas extends EngineCanvas {
void _applyFilter(html.Element element, SurfacePaintData paint) {
if (paint.maskFilter != null) {
final bool isStroke = paint.style == ui.PaintingStyle.stroke;
String cssColor = paint.color == null
? '#000000'
: colorToCssString(
paint.color)!;
String cssColor =
paint.color == null ? '#000000' : colorToCssString(paint.color)!;
final double sigma = paint.maskFilter!.webOnlySigma;
if (browserEngine == BrowserEngine.webkit && !isStroke) {
// A bug in webkit leaves artifacts when this element is animated
......@@ -1195,14 +1193,33 @@ List<html.Element> _clipContent(List<_SaveClipEntry> clipStack,
..height = '${roundRect.bottom - clipOffsetY}px';
setElementTransform(curElement, newClipTransform.storage);
} else if (entry.path != null) {
curElement.style
..transform = matrix4ToCssTransform(newClipTransform)
..transformOrigin = '0 0 0';
String svgClipPath =
createSvgClipDef(curElement as html.HtmlElement, entry.path!);
final html.Element clipElement =
html.Element.html(svgClipPath, treeSanitizer: _NullTreeSanitizer());
clipDefs.add(clipElement);
// Clipping optimization when we know that the path is an oval.
// We use a div with border-radius set to 50% with a size that is
// set to path bounds and set overflow to hidden.
final SurfacePath surfacePath = entry.path as SurfacePath;
if (surfacePath.pathRef.isOval != -1) {
final ui.Rect ovalBounds = surfacePath.getBounds();
final double clipOffsetX = ovalBounds.left;
final double clipOffsetY = ovalBounds.top;
newClipTransform = newClipTransform.clone()
..translate(clipOffsetX, clipOffsetY);
curElement.style
..overflow = 'hidden'
..width = '${ovalBounds.width}px'
..height = '${ovalBounds.height}px'
..borderRadius = '50%';
setElementTransform(curElement, newClipTransform.storage);
} else {
// Abitrary path clipping.
curElement.style
..transform = matrix4ToCssTransform(newClipTransform)
..transformOrigin = '0 0 0';
String svgClipPath =
createSvgClipDef(curElement as html.HtmlElement, entry.path!);
final html.Element clipElement =
html.Element.html(svgClipPath, treeSanitizer: _NullTreeSanitizer());
clipDefs.add(clipElement);
}
}
// Reverse the transform of the clipping element so children can use
// effective transform to render.
......
......@@ -11,39 +11,17 @@ import 'package:test/test.dart';
import 'package:ui/ui.dart' hide TextStyle;
import 'package:ui/src/engine.dart' as engine;
import 'package:web_engine_tester/golden_tester.dart';
import 'screenshot.dart';
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() async {
const double screenWidth = 600.0;
const double screenHeight = 800.0;
const double screenWidth = 500.0;
const double screenHeight = 500.0;
const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight);
// Commit a recording canvas to a bitmap, and compare with the expected
Future<void> _checkScreenshot(engine.RecordingCanvas rc, String fileName,
{Rect region = const Rect.fromLTWH(0, 0, 500, 500)}) async {
final engine.EngineCanvas engineCanvas = engine.BitmapCanvas(screenRect,
engine.RenderStrategy());
rc.endRecording();
rc.apply(engineCanvas, screenRect);
// Wrap in <flt-scene> so that our CSS selectors kick in.
final html.Element sceneElement = html.Element.tag('flt-scene');
try {
sceneElement.append(engineCanvas.rootElement);
html.document.body.append(sceneElement);
await matchGoldenFile('$fileName.png', region: region, maxDiffRatePercent: 0.0);
} finally {
// The page is reused across tests, so remove the element after taking the
// Scuba screenshot.
sceneElement.remove();
}
}
setUp(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
......@@ -55,7 +33,7 @@ void testMain() async {
// Should clip image with oval.
test('Clips image with oval clip path', () async {
final engine.RecordingCanvas rc =
engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
rc.save();
Image testImage = createTestImage();
double testWidth = testImage.width.toDouble();
......@@ -66,13 +44,14 @@ void testMain() async {
rc.drawImageRect(testImage, Rect.fromLTRB(0, 0, testWidth, testHeight),
Rect.fromLTWH(100, 30, testWidth, testHeight), Paint());
rc.restore();
await _checkScreenshot(rc, 'image_clipped_by_oval');
await canvasScreenshot(rc, 'image_clipped_by_oval',
region: screenRect);
});
// Regression test for https://github.com/flutter/flutter/issues/48683
test('Clips triangle with oval clip path', () async {
final engine.RecordingCanvas rc =
engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
rc.save();
double testWidth = 200;
double testHeight = 150;
......@@ -90,7 +69,61 @@ void testMain() async {
..color = Color(0xFF00FF00)
..style = PaintingStyle.fill);
rc.restore();
await _checkScreenshot(rc, 'triangle_clipped_by_oval');
await canvasScreenshot(rc, 'triangle_clipped_by_oval',
region: screenRect);
});
// Regression test for https://github.com/flutter/flutter/issues/78782
test('Clips on Safari when clip bounds off screen', () async {
final engine.RecordingCanvas rc =
engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
rc.save();
double testWidth = 200;
double testHeight = 150;
final Path paintPath = new Path();
paintPath.addRect(Rect.fromLTWH(-50, 0, testWidth, testHeight));
paintPath.close();
rc.drawPath(paintPath,
Paint()
..color = Color(0xFF000000)
..style = PaintingStyle.stroke);
final Path path = Path();
path.moveTo(-200, 0);
path.lineTo(100, 75);
path.lineTo(-200, 150);
path.close();
rc.clipPath(path);
rc.drawImageRect(createTestImage(), Rect.fromLTRB(0, 0, testWidth, testHeight),
Rect.fromLTWH(-50, 0, testWidth, testHeight), Paint());
rc.restore();
await canvasScreenshot(rc, 'image_clipped_by_triangle_off_screen');
});
// Tests oval clipping using border radius 50%.
test('Clips against oval', () async {
final engine.RecordingCanvas rc =
engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));
rc.save();
double testWidth = 200;
double testHeight = 150;
final Path paintPath = new Path();
paintPath.addRect(Rect.fromLTWH(-50, 0, testWidth, testHeight));
paintPath.close();
rc.drawPath(paintPath,
Paint()
..color = Color(0xFF000000)
..style = PaintingStyle.stroke);
final Path path = Path();
path.addOval(Rect.fromLTRB(-200, 0, 100, 150));
rc.clipPath(path);
rc.drawImageRect(createTestImage(), Rect.fromLTRB(0, 0, testWidth, testHeight),
Rect.fromLTWH(-50, 0, testWidth, testHeight), Paint());
rc.restore();
await canvasScreenshot(rc, 'image_clipped_by_oval_path');
});
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册