未验证 提交 8f89bac4 编写于 作者: F Ferhat 提交者: GitHub

[web] Fixes incorrect transform when context save and transforms are deferred. (#16412)

* Fix transform order in clipStack replay
上级 31bf3e11
repository: https://github.com/flutter/goldens.git
revision: 43254f4abddc2542ece540f222545970caf12908
revision: 1637835646ef187884ceeb59011d70c463429876
......@@ -101,11 +101,7 @@ class _CanvasPool extends _SaveStackTracking {
_rootElement.append(_canvas);
_context = _canvas.context2D;
_contextHandle = ContextStateHandle(_context);
_initializeViewport();
if (requiresClearRect) {
// Now that the context is reset, clear old contents.
_context.clearRect(0, 0, _widthInBitmapPixels, _heightInBitmapPixels);
}
_initializeViewport(requiresClearRect);
_replayClipStack();
}
......@@ -136,20 +132,34 @@ class _CanvasPool extends _SaveStackTracking {
translate(transform.dx, transform.dy);
}
int _replaySingleSaveEntry(
int clipDepth, Matrix4 transform, List<_SaveClipEntry> clipStack) {
int _replaySingleSaveEntry(int clipDepth, Matrix4 prevTransform,
Matrix4 transform, List<_SaveClipEntry> clipStack) {
final html.CanvasRenderingContext2D ctx = _context;
if (!transform.isIdentity()) {
final double ratio = EngineWindow.browserDevicePixelRatio;
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
ctx.transform(transform[0], transform[1], transform[4], transform[5],
transform[12], transform[13]);
}
if (clipStack != null) {
for (int clipCount = clipStack.length;
clipDepth < clipCount;
clipDepth++) {
_SaveClipEntry clipEntry = clipStack[clipDepth];
Matrix4 clipTimeTransform = clipEntry.currentTransform;
// If transform for entry recording change since last element, update.
// Comparing only matrix3 elements since Canvas API restricted.
if (clipTimeTransform[0] != prevTransform[0] ||
clipTimeTransform[1] != prevTransform[1] ||
clipTimeTransform[4] != prevTransform[4] ||
clipTimeTransform[5] != prevTransform[5] ||
clipTimeTransform[12] != prevTransform[12] ||
clipTimeTransform[13] != prevTransform[13]) {
final double ratio = EngineWindow.browserDevicePixelRatio;
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
ctx.transform(
clipTimeTransform[0],
clipTimeTransform[1],
clipTimeTransform[4],
clipTimeTransform[5],
clipTimeTransform[12],
clipTimeTransform[13]);
prevTransform = clipTimeTransform;
}
if (clipEntry.rect != null) {
_clipRect(ctx, clipEntry.rect);
} else if (clipEntry.rrect != null) {
......@@ -160,6 +170,19 @@ class _CanvasPool extends _SaveStackTracking {
}
}
}
// If transform was changed between last clip operation and save call,
// update.
if (transform[0] != prevTransform[0] ||
transform[1] != prevTransform[1] ||
transform[4] != prevTransform[4] ||
transform[5] != prevTransform[5] ||
transform[12] != prevTransform[12] ||
transform[13] != prevTransform[13]) {
final double ratio = EngineWindow.browserDevicePixelRatio;
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
ctx.transform(transform[0], transform[1], transform[4], transform[5],
transform[12], transform[13]);
}
return clipDepth;
}
......@@ -167,16 +190,19 @@ class _CanvasPool extends _SaveStackTracking {
// Replay save/clip stack on this canvas now.
html.CanvasRenderingContext2D ctx = _context;
int clipDepth = 0;
Matrix4 prevTransform = Matrix4.identity();
for (int saveStackIndex = 0, len = _saveStack.length;
saveStackIndex < len;
saveStackIndex++) {
_SaveStackEntry saveEntry = _saveStack[saveStackIndex];
clipDepth = _replaySingleSaveEntry(
clipDepth, saveEntry.transform, saveEntry.clipStack);
clipDepth, prevTransform, saveEntry.transform, saveEntry.clipStack);
prevTransform = saveEntry.transform;
ctx.save();
++_saveContextCount;
}
_replaySingleSaveEntry(clipDepth, _currentTransform, _clipStack);
_replaySingleSaveEntry(
clipDepth, prevTransform, _currentTransform, _clipStack);
}
// Marks this pool for reuse.
......@@ -216,7 +242,7 @@ class _CanvasPool extends _SaveStackTracking {
/// Configures the canvas such that its coordinate system follows the scene's
/// coordinate system, and the pixel ratio is applied such that CSS pixels are
/// translated to bitmap pixels.
void _initializeViewport() {
void _initializeViewport(bool clearCanvas) {
html.CanvasRenderingContext2D ctx = context;
// Save the canvas state with top-level transforms so we can undo
// any clips later when we reuse the canvas.
......@@ -226,6 +252,9 @@ class _CanvasPool extends _SaveStackTracking {
// We always start with identity transform because the surrounding transform
// is applied on the DOM elements.
ctx.setTransform(1, 0, 0, 1, 0, 0);
if (clearCanvas) {
ctx.clearRect(0, 0, _widthInBitmapPixels, _heightInBitmapPixels);
}
// This scale makes sure that 1 CSS pixel is translated to the correct
// number of bitmap pixels.
......
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:html' as html;
import 'dart:js_util' as js_util;
import 'package:ui/ui.dart' hide TextStyle;
import 'package:ui/src/engine.dart' as engine;
import 'package:test/test.dart';
import 'package:web_engine_tester/golden_tester.dart';
/// Tests context save/restore.
void main() async {
const double screenWidth = 600.0;
const double screenHeight = 800.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);
rc.apply(engineCanvas);
// 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, maxDiffRate: 0.1);
} finally {
// The page is reused across tests, so remove the element after taking the
// Scuba screenshot.
sceneElement.remove();
}
}
setUp(() async {
debugEmulateFlutterTesterEnvironment = true;
await webOnlyInitializePlatform();
webOnlyFontCollection.debugRegisterTestFonts();
await webOnlyFontCollection.ensureFontsLoaded();
});
// Regression test for https://github.com/flutter/flutter/issues/49429
// Should clip with correct transform.
test('Clips image with oval clip path', () async {
final engine.RecordingCanvas rc =
engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
final Paint paint = Paint()
..color = Color(0xFF00FF00)
..style = PaintingStyle.fill;
rc.save();
final Path ovalPath = Path();
ovalPath.addOval(Rect.fromLTWH(100, 30, 200, 100));
rc.clipPath(ovalPath);
rc.translate(-500, -500);
rc.save();
rc.translate(500, 500);
rc.drawPath(ovalPath, paint);
// The line below was causing SaveClipStack to incorrectly set
// transform before path painting.
rc.translate(-1000, -1000);
rc.save();
rc.restore();
rc.restore();
rc.restore();
// The rectangle should paint without clipping since we restored
// context.
rc.drawRect(Rect.fromLTWH(0, 0, 4, 200), paint);
await _checkScreenshot(rc, 'context_save_restore_transform');
});
test('Should restore clip path', () async {
final engine.RecordingCanvas rc =
engine.RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
final Paint goodPaint = Paint()
..color = Color(0x8000FF00)
..style = PaintingStyle.fill;
final Paint badPaint = Paint()
..color = Color(0xFFFF0000)
..style = PaintingStyle.fill;
rc.save();
final Path ovalPath = Path();
ovalPath.addOval(Rect.fromLTWH(100, 30, 200, 100));
rc.clipPath(ovalPath);
rc.translate(-500, -500);
rc.save();
rc.restore();
// The rectangle should be clipped against oval.
rc.drawRect(Rect.fromLTWH(0, 0, 300, 300), badPaint);
rc.restore();
// The rectangle should paint without clipping since we restored
// context.
rc.drawRect(Rect.fromLTWH(0, 0, 200, 200), goodPaint);
await _checkScreenshot(rc, 'context_save_restore_clip');
});
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册