mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] Fixes incorrect transform when context save and transforms are deferred. (flutter/engine#16412)
* Fix transform order in clipStack replay
This commit is contained in:
parent
cae9061d83
commit
a00b502efa
@ -1,2 +1,2 @@
|
||||
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.
|
||||
|
||||
@ -0,0 +1,100 @@
|
||||
// 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');
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user