Skip to content

Commit 890f016

Browse files
authored
[web] Implement ClipOp.difference (flutter#21901)
1 parent 815a1f3 commit 890f016

File tree

10 files changed

+119
-15
lines changed

10 files changed

+119
-15
lines changed

lib/web_ui/dev/goldens_lock.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
repository: https://github.com/flutter/goldens.git
2-
revision: 672510dc52daa5b059081f6990582bccdb4ea48f
2+
revision: 1556280d6f1d70fac9ddff9b38639757e105b4b0

lib/web_ui/lib/src/engine/bitmap_canvas.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,18 @@ class BitmapCanvas extends EngineCanvas {
271271
}
272272

273273
@override
274-
void clipRect(ui.Rect rect) {
275-
_canvasPool.clipRect(rect);
274+
void clipRect(ui.Rect rect, ui.ClipOp op) {
275+
if (op == ui.ClipOp.difference) {
276+
// Create 2 rectangles inside each other that represents
277+
// clip area difference using even-odd fill rule.
278+
final SurfacePath path = new SurfacePath();
279+
path.fillType = ui.PathFillType.evenOdd;
280+
path.addRect(ui.Rect.fromLTWH(0, 0, _bounds.width, _bounds.height));
281+
path.addRect(rect);
282+
_canvasPool.clipPath(path);
283+
} else {
284+
_canvasPool.clipRect(rect);
285+
}
276286
}
277287

278288
@override
@@ -466,7 +476,7 @@ class BitmapCanvas extends EngineCanvas {
466476
} else {
467477
if (requiresClipping) {
468478
save();
469-
clipRect(dst);
479+
clipRect(dst, ui.ClipOp.intersect);
470480
}
471481
double targetLeft = dst.left;
472482
double targetTop = dst.top;

lib/web_ui/lib/src/engine/canvas_pool.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,13 @@ class _CanvasPool extends _SaveStackTracking {
210210
} else if (clipEntry.rrect != null) {
211211
_clipRRect(ctx, clipEntry.rrect!);
212212
} else if (clipEntry.path != null) {
213-
_runPath(ctx, clipEntry.path as SurfacePath);
214-
ctx.clip();
213+
final SurfacePath path = clipEntry.path as SurfacePath;
214+
_runPath(ctx, path);
215+
if (path.fillType == ui.PathFillType.nonZero) {
216+
ctx.clip();
217+
} else {
218+
ctx.clip('evenodd');
219+
}
215220
}
216221
}
217222
}
@@ -443,7 +448,11 @@ class _CanvasPool extends _SaveStackTracking {
443448
if (_canvas != null) {
444449
html.CanvasRenderingContext2D ctx = context;
445450
_runPath(ctx, path as SurfacePath);
446-
ctx.clip();
451+
if (path.fillType == ui.PathFillType.nonZero) {
452+
ctx.clip();
453+
} else {
454+
ctx.clip('evenodd');
455+
}
447456
}
448457
}
449458

lib/web_ui/lib/src/engine/dom_canvas.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking {
2828
}
2929

3030
@override
31-
void clipRect(ui.Rect rect) {
31+
void clipRect(ui.Rect rect, ui.ClipOp op) {
3232
throw UnimplementedError();
3333
}
3434

lib/web_ui/lib/src/engine/engine_canvas.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ abstract class EngineCanvas {
3333

3434
void transform(Float32List matrix4);
3535

36-
void clipRect(ui.Rect rect);
36+
void clipRect(ui.Rect rect, ui.ClipOp clipOp);
3737

3838
void clipRRect(ui.RRect rrect);
3939

@@ -222,7 +222,7 @@ mixin SaveStackTracking on EngineCanvas {
222222
///
223223
/// Classes that override this method must call `super.clipRect()`.
224224
@override
225-
void clipRect(ui.Rect rect) {
225+
void clipRect(ui.Rect rect, ui.ClipOp op) {
226226
_clipStack ??= <_SaveClipEntry>[];
227227
_clipStack!.add(_SaveClipEntry.rect(rect, _currentTransform.clone()));
228228
}

lib/web_ui/lib/src/engine/html/recording_canvas.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ class RecordingCanvas {
275275

276276
void clipRect(ui.Rect rect, ui.ClipOp clipOp) {
277277
assert(!_recordingEnded);
278-
final PaintClipRect command = PaintClipRect(rect, clipOp);
278+
final DrawCommand command = PaintClipRect(rect, clipOp);
279279
switch (clipOp) {
280280
case ui.ClipOp.intersect:
281281
_paintBounds.clipRect(rect, command);
@@ -810,7 +810,7 @@ class PaintClipRect extends DrawCommand {
810810

811811
@override
812812
void apply(EngineCanvas canvas) {
813-
canvas.clipRect(rect);
813+
canvas.clipRect(rect, clipOp);
814814
}
815815

816816
@override

lib/web_ui/test/golden_tests/engine/canvas_golden_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,9 @@ void testMain() async {
157157

158158
canvas = BitmapCanvas(canvasSize);
159159
canvas.debugChildOverdraw = true;
160-
canvas.clipRect(outerClip);
160+
canvas.clipRect(outerClip, ClipOp.intersect);
161161
canvas.drawParagraph(paragraph, const Offset(8.5, 8.5));
162-
canvas.clipRect(innerClip);
162+
canvas.clipRect(innerClip, ClipOp.intersect);
163163
canvas.drawParagraph(paragraph, Offset(8.5, 8.5 + innerClip.top));
164164

165165
expect(
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// @dart = 2.6
6+
import 'package:test/bootstrap/browser.dart';
7+
import 'package:test/test.dart';
8+
import 'package:ui/ui.dart';
9+
import 'package:ui/src/engine.dart';
10+
import 'screenshot.dart';
11+
12+
void main() {
13+
internalBootstrapBrowserTest(() => testMain);
14+
}
15+
16+
void testMain() async {
17+
setUp(() async {
18+
debugEmulateFlutterTesterEnvironment = true;
19+
});
20+
21+
/// Regression test for https://github.com/flutter/flutter/issues/64734.
22+
test('Clips using difference', () async {
23+
final Rect region = const Rect.fromLTRB(0, 0, 400, 300);
24+
final RecordingCanvas canvas = RecordingCanvas(region);
25+
final Rect titleRect = Rect.fromLTWH(20, 0, 50, 20);
26+
final Paint paint = Paint()
27+
..style = PaintingStyle.stroke
28+
..color = const Color(0xff000000)
29+
..strokeWidth = 1;
30+
canvas.save();
31+
try {
32+
final Rect borderRect = Rect.fromLTRB(0, 10, region.width, region.height);
33+
canvas.clipRect(titleRect, ClipOp.difference);
34+
canvas.drawRect(borderRect, paint);
35+
} finally {
36+
canvas.restore();
37+
}
38+
canvas..drawRect(titleRect, paint);
39+
await canvasScreenshot(canvas, 'clip_op_difference',
40+
region: const Rect.fromLTRB(0, 0, 420, 360));
41+
});
42+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// @dart = 2.6
6+
import 'dart:html' as html;
7+
import 'package:ui/ui.dart' as ui;
8+
import 'package:ui/src/engine.dart';
9+
import 'package:web_engine_tester/golden_tester.dart';
10+
import 'package:test/test.dart';
11+
12+
/// Commit a recording canvas to a bitmap, and compare with the expected.
13+
Future<void> canvasScreenshot(RecordingCanvas rc, String fileName,
14+
{ui.Rect region = const ui.Rect.fromLTWH(0, 0, 600, 800),
15+
double maxDiffRatePercent = 0.0, bool write: false}) async {
16+
final EngineCanvas engineCanvas = BitmapCanvas(region);
17+
18+
rc.endRecording();
19+
rc.apply(engineCanvas, region);
20+
21+
// Wrap in <flt-scene> so that our CSS selectors kick in.
22+
final html.Element sceneElement = html.Element.tag('flt-scene');
23+
try {
24+
sceneElement.append(engineCanvas.rootElement);
25+
html.document.body.append(sceneElement);
26+
await matchGoldenFile('$fileName.png',
27+
region: region, maxDiffRatePercent: maxDiffRatePercent, write: write);
28+
} finally {
29+
// The page is reused across tests, so remove the element after taking the
30+
// Scuba screenshot.
31+
sceneElement.remove();
32+
}
33+
}
34+
35+
/// Configures the test to use bundled Roboto and Ahem fonts to avoid golden
36+
/// screenshot differences due to differences in the preinstalled system fonts.
37+
void setUpStableTestFonts() {
38+
setUp(() async {
39+
await ui.webOnlyInitializePlatform();
40+
ui.webOnlyFontCollection.debugRegisterTestFonts();
41+
await ui.webOnlyFontCollection.ensureFontsLoaded();
42+
});
43+
}

lib/web_ui/test/mock_engine_canvas.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class MockEngineCanvas implements EngineCanvas {
9999
}
100100

101101
@override
102-
void clipRect(Rect rect) {
102+
void clipRect(Rect rect, ClipOp op) {
103103
_called('clipRect', arguments: rect);
104104
}
105105

0 commit comments

Comments
 (0)