From 0579a5c8bad8a2863f546f90bdcb818ebc40b734 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 5 May 2020 19:04:48 -0700 Subject: [PATCH] Fix path bounds for multiple rects. Implement winding rules --- lib/web_ui/dev/goldens_lock.yaml | 2 +- lib/web_ui/lib/src/engine/canvas_pool.dart | 24 ++++++- lib/web_ui/lib/src/engine/surface/path.dart | 16 ++--- .../src/engine/surface/recording_canvas.dart | 27 +++---- .../engine/canvas_winding_rule_test.dart | 72 +++++++++++++++++++ lib/web_ui/test/path_test.dart | 10 +++ 6 files changed, 127 insertions(+), 24 deletions(-) create mode 100644 lib/web_ui/test/golden_tests/engine/canvas_winding_rule_test.dart diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index 44bd90cbc64e6..3b5f134f32411 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: f64d8957ae281d1558647f0591ff9742e6135385 +revision: 0c1a793bfd49e30fccdd7ad64329a114a9cb32f7 diff --git a/lib/web_ui/lib/src/engine/canvas_pool.dart b/lib/web_ui/lib/src/engine/canvas_pool.dart index adaa1d1d9c8eb..6e7e17340d8ab 100644 --- a/lib/web_ui/lib/src/engine/canvas_pool.dart +++ b/lib/web_ui/lib/src/engine/canvas_pool.dart @@ -505,8 +505,14 @@ class _CanvasPool extends _SaveStackTracking { /// 'Runs' the given [path] by applying all of its commands to the canvas. void _runPath(html.CanvasRenderingContext2D ctx, SurfacePath path) { ctx.beginPath(); - for (Subpath subpath in path.subpaths) { - for (PathCommand command in subpath.commands) { + final List subpaths = path.subpaths; + final int subpathCount = subpaths.length; + for (int subPathIndex = 0; subPathIndex < subpathCount; subPathIndex++) { + final Subpath subpath = subpaths[subPathIndex]; + final List commands = subpath.commands; + final int commandCount = commands.length; + for (int c = 0; c < commandCount; c++) { + final PathCommand command = commands[c]; switch (command.type) { case PathCommandTypes.bezierCurveTo: final BezierCurveTo curve = command; @@ -591,7 +597,7 @@ class _CanvasPool extends _SaveStackTracking { void drawPath(ui.Path path, ui.PaintingStyle style) { _runPath(context, path); - contextHandle.paint(style); + contextHandle.paintPath(style, path.fillType); } void drawShadow(ui.Path path, ui.Color color, double elevation, @@ -760,6 +766,18 @@ class ContextStateHandle { } } + void paintPath(ui.PaintingStyle style, ui.PathFillType pathFillType) { + if (style == ui.PaintingStyle.stroke) { + context.stroke(); + } else { + if (pathFillType == ui.PathFillType.nonZero) { + context.fill(); + } else { + context.fill('evenodd'); + } + } + } + void reset() { context.fillStyle = ''; // Read back fillStyle/strokeStyle values from context so that input such diff --git a/lib/web_ui/lib/src/engine/surface/path.dart b/lib/web_ui/lib/src/engine/surface/path.dart index 2e700008c315c..1b83e0be11d2c 100644 --- a/lib/web_ui/lib/src/engine/surface/path.dart +++ b/lib/web_ui/lib/src/engine/surface/path.dart @@ -1005,22 +1005,22 @@ class SurfacePath implements ui.Path { break; case PathCommandTypes.rect: final RectCommand cmd = op; - left = cmd.x; + minX = cmd.x; double width = cmd.width; if (cmd.width < 0) { - left -= width; + minX -= width; width = -width; } - double top = cmd.y; + minY = cmd.y; double height = cmd.height; if (cmd.height < 0) { - top -= height; + minY -= height; height = -height; } - curX = minX = left; - maxX = left + width; - curY = minY = top; - maxY = top + height; + curX = minX; + maxX = minX + width; + curY = minY; + maxY = minY + height; break; case PathCommandTypes.rRect: final RRectCommand cmd = op; diff --git a/lib/web_ui/lib/src/engine/surface/recording_canvas.dart b/lib/web_ui/lib/src/engine/surface/recording_canvas.dart index a2b9c19667c49..5bce4c3157e77 100644 --- a/lib/web_ui/lib/src/engine/surface/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/surface/recording_canvas.dart @@ -440,19 +440,22 @@ class RecordingCanvas { return; } } - _hasArbitraryPaint = true; - _didDraw = true; - ui.Rect pathBounds = path.getBounds(); - final double paintSpread = _getPaintSpread(paint); - if (paintSpread != 0.0) { - pathBounds = pathBounds.inflate(paintSpread); + SurfacePath sPath = path; + if (sPath.subpaths.isNotEmpty) { + _hasArbitraryPaint = true; + _didDraw = true; + ui.Rect pathBounds = sPath.getBounds(); + final double paintSpread = _getPaintSpread(paint); + if (paintSpread != 0.0) { + pathBounds = pathBounds.inflate(paintSpread); + } + // Clone path so it can be reused for subsequent draw calls. + final ui.Path clone = SurfacePath._shallowCopy(path); + final PaintDrawPath command = PaintDrawPath(clone, paint.paintData); + _paintBounds.grow(pathBounds, command); + clone.fillType = sPath.fillType; + _commands.add(command); } - // Clone path so it can be reused for subsequent draw calls. - final ui.Path clone = SurfacePath._shallowCopy(path); - final PaintDrawPath command = PaintDrawPath(clone, paint.paintData); - _paintBounds.grow(pathBounds, command); - clone.fillType = path.fillType; - _commands.add(command); } void drawImage(ui.Image image, ui.Offset offset, SurfacePaint paint) { diff --git a/lib/web_ui/test/golden_tests/engine/canvas_winding_rule_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_winding_rule_test.dart new file mode 100644 index 0000000000000..556705953bc23 --- /dev/null +++ b/lib/web_ui/test/golden_tests/engine/canvas_winding_rule_test.dart @@ -0,0 +1,72 @@ +// 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. + +// @dart = 2.6 +import 'dart:html' as html; + +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart'; +import 'package:test/test.dart'; + +import 'package:web_engine_tester/golden_tester.dart'; + +void main() async { + final Rect region = Rect.fromLTWH(0, 0, 500, 500); + + BitmapCanvas canvas; + + setUp(() { + canvas = BitmapCanvas(region); + }); + + tearDown(() { + canvas.rootElement.remove(); + }); + + test('draws paths using nonzero and evenodd winding rules', () async { + paintPaths(canvas); + html.document.body.append(canvas.rootElement); + await matchGoldenFile('canvas_path_winding.png', region: region); + }); + +} + +void paintPaths(BitmapCanvas canvas) { + canvas.drawRect(Rect.fromLTRB(0, 0, 500, 500), + SurfacePaintData() + ..color = Color(0xFFFFFFFF) + ..style = PaintingStyle.fill); // white + + SurfacePaint paintFill = SurfacePaint() + ..style = PaintingStyle.fill + ..color = Color(0xFF00B0FF); + SurfacePaint paintStroke = SurfacePaint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2 + ..color = Color(0xFFE00000); + Path path1 = Path() + ..fillType = PathFillType.evenOdd + ..moveTo(50, 0) + ..lineTo(21, 90) + ..lineTo(98, 35) + ..lineTo(2, 35) + ..lineTo(79, 90) + ..close() + ..addRect(Rect.fromLTWH(20, 100, 200, 50)) + ..addRect(Rect.fromLTWH(40, 120, 160, 10)); + Path path2 = Path() + ..fillType = PathFillType.nonZero + ..moveTo(50, 200) + ..lineTo(21, 290) + ..lineTo(98, 235) + ..lineTo(2, 235) + ..lineTo(79, 290) + ..close() + ..addRect(Rect.fromLTWH(20, 300, 200, 50)) + ..addRect(Rect.fromLTWH(40, 320, 160, 10)); + canvas.drawPath(path1, paintFill.paintData); + canvas.drawPath(path2, paintFill.paintData); + canvas.drawPath(path1, paintStroke.paintData); + canvas.drawPath(path2, paintStroke.paintData); +} diff --git a/lib/web_ui/test/path_test.dart b/lib/web_ui/test/path_test.dart index 91aa622359012..aeb9c5cec8999 100644 --- a/lib/web_ui/test/path_test.dart +++ b/lib/web_ui/test/path_test.dart @@ -128,6 +128,16 @@ void main() { expect(path.getBounds(), const Rect.fromLTRB(50, 60, 50, 60)); }); + test('Should compute bounds for multiple addRect calls', () { + final Path emptyPath = Path(); + expect(emptyPath.getBounds(), Rect.zero); + + final SurfacePath path = SurfacePath(); + path.addRect(Rect.fromLTWH(0, 0, 270, 45)); + path.addRect(Rect.fromLTWH(134.5, 0, 1, 45)); + expect(path.getBounds(), const Rect.fromLTRB(0, 0, 270, 45)); + }); + test('Should compute bounds for lines', () { final SurfacePath path = SurfacePath(); path.moveTo(25, 30);