From 8b1b0b4a44c30e3074afcf31d59354fadadb553d Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Sep 2019 15:39:53 -0700 Subject: [PATCH 01/10] Implement path.transform --- lib/web_ui/lib/src/engine.dart | 1 + lib/web_ui/lib/src/engine/bitmap_canvas.dart | 194 +--------------- .../lib/src/engine/recording_canvas.dart | 96 ++++++++ lib/web_ui/lib/src/engine/rrect_renderer.dart | 218 ++++++++++++++++++ lib/web_ui/lib/src/ui/canvas.dart | 8 +- 5 files changed, 326 insertions(+), 191 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/rrect_renderer.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index a6f9acf329ac2..947bf1c6aac16 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -57,6 +57,7 @@ part 'engine/platform_views.dart'; part 'engine/plugins.dart'; part 'engine/pointer_binding.dart'; part 'engine/recording_canvas.dart'; +part 'engine/rrect_renderer.dart'; part 'engine/semantics/accessibility.dart'; part 'engine/semantics/checkable.dart'; part 'engine/semantics/image.dart'; diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 381398b7396a6..0c1a9fd6fa147 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -454,202 +454,16 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { @override void drawRRect(ui.RRect rrect, ui.PaintData paint) { _applyPaint(paint); - _drawRRectPath(rrect); + _RRectToCanvasRenderer(ctx).render(rrect); _strokeOrFill(paint); } - void _drawRRectPath(ui.RRect inputRRect, {bool startNewPath = true}) { - // TODO(mdebbar): Backport the overlapping corners fix to houdini_painter.js - // To draw the rounded rectangle, perform the following steps: - // 0. Ensure border radius don't overlap - // 1. Flip left,right top,bottom since web doesn't support flipped - // coordinates with negative radii. - // 2. draw the line for the top - // 3. draw the arc for the top-right corner - // 4. draw the line for the right side - // 5. draw the arc for the bottom-right corner - // 6. draw the line for the bottom of the rectangle - // 7. draw the arc for the bottom-left corner - // 8. draw the line for the left side - // 9. draw the arc for the top-left corner - // - // After drawing, the current point will be the left side of the top of the - // rounded rectangle (after the corner). - // TODO(het): Confirm that this is the end point in Flutter for RRect - - // Ensure border radius curves never overlap - final ui.RRect rrect = inputRRect.scaleRadii(); - - double left = rrect.left; - double right = rrect.right; - double top = rrect.top; - double bottom = rrect.bottom; - if (left > right) { - left = right; - right = rrect.left; - } - if (top > bottom) { - top = bottom; - bottom = rrect.top; - } - final double trRadiusX = rrect.trRadiusX.abs(); - final double tlRadiusX = rrect.tlRadiusX.abs(); - final double trRadiusY = rrect.trRadiusY.abs(); - final double tlRadiusY = rrect.tlRadiusY.abs(); - final double blRadiusX = rrect.blRadiusX.abs(); - final double brRadiusX = rrect.brRadiusX.abs(); - final double blRadiusY = rrect.blRadiusY.abs(); - final double brRadiusY = rrect.brRadiusY.abs(); - - if (startNewPath) { - ctx.beginPath(); - } - - ctx.moveTo(left + trRadiusX, top); - - // Top side and top-right corner - ctx.lineTo(right - trRadiusX, top); - ctx.ellipse( - right - trRadiusX, - top + trRadiusY, - trRadiusX, - trRadiusY, - 0, - 1.5 * math.pi, - 2.0 * math.pi, - false, - ); - - // Right side and bottom-right corner - ctx.lineTo(right, bottom - brRadiusY); - ctx.ellipse( - right - brRadiusX, - bottom - brRadiusY, - brRadiusX, - brRadiusY, - 0, - 0, - 0.5 * math.pi, - false, - ); - - // Bottom side and bottom-left corner - ctx.lineTo(left + blRadiusX, bottom); - ctx.ellipse( - left + blRadiusX, - bottom - blRadiusY, - blRadiusX, - blRadiusY, - 0, - 0.5 * math.pi, - math.pi, - false, - ); - - // Left side and top-left corner - ctx.lineTo(left, top + tlRadiusY); - ctx.ellipse( - left + tlRadiusX, - top + tlRadiusY, - tlRadiusX, - tlRadiusY, - 0, - math.pi, - 1.5 * math.pi, - false, - ); - } - - void _drawRRectPathReverse(ui.RRect inputRRect, {bool startNewPath = true}) { - // Ensure border radius curves never overlap - final ui.RRect rrect = inputRRect.scaleRadii(); - - double left = rrect.left; - double right = rrect.right; - double top = rrect.top; - double bottom = rrect.bottom; - final double trRadiusX = rrect.trRadiusX.abs(); - final double tlRadiusX = rrect.tlRadiusX.abs(); - final double trRadiusY = rrect.trRadiusY.abs(); - final double tlRadiusY = rrect.tlRadiusY.abs(); - final double blRadiusX = rrect.blRadiusX.abs(); - final double brRadiusX = rrect.brRadiusX.abs(); - final double blRadiusY = rrect.blRadiusY.abs(); - final double brRadiusY = rrect.brRadiusY.abs(); - - if (left > right) { - left = right; - right = rrect.left; - } - if (top > bottom) { - top = bottom; - bottom = rrect.top; - } - // Draw the rounded rectangle, counterclockwise. - ctx.moveTo(right - trRadiusX, top); - - if (startNewPath) { - ctx.beginPath(); - } - - // Top side and top-left corner - ctx.lineTo(left + tlRadiusX, top); - ctx.ellipse( - left + tlRadiusX, - top + tlRadiusY, - tlRadiusX, - tlRadiusY, - 0, - 1.5 * math.pi, - 1 * math.pi, - true, - ); - - // Left side and bottom-left corner - ctx.lineTo(left, bottom - blRadiusY); - ctx.ellipse( - left + blRadiusX, - bottom - blRadiusY, - blRadiusX, - blRadiusY, - 0, - 1 * math.pi, - 0.5 * math.pi, - true, - ); - - // Bottom side and bottom-right corner - ctx.lineTo(right - brRadiusX, bottom); - ctx.ellipse( - right - brRadiusX, - bottom - brRadiusY, - brRadiusX, - brRadiusY, - 0, - 0.5 * math.pi, - 0 * math.pi, - true, - ); - - // Right side and top-right corner - ctx.lineTo(right, top + trRadiusY); - ctx.ellipse( - right - trRadiusX, - top + trRadiusY, - trRadiusX, - trRadiusY, - 0, - 0 * math.pi, - 1.5 * math.pi, - true, - ); - } - @override void drawDRRect(ui.RRect outer, ui.RRect inner, ui.PaintData paint) { _applyPaint(paint); - _drawRRectPath(outer); - _drawRRectPathReverse(inner, startNewPath: false); + _RRectRenderer renderer = _RRectToCanvasRenderer(ctx); + renderer.render(outer); + renderer.render(inner, startNewPath: false, reverse: true); _strokeOrFill(paint); } diff --git a/lib/web_ui/lib/src/engine/recording_canvas.dart b/lib/web_ui/lib/src/engine/recording_canvas.dart index db9a4927cb7f5..60fb36469a3a1 100644 --- a/lib/web_ui/lib/src/engine/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/recording_canvas.dart @@ -1120,6 +1120,9 @@ abstract class PathCommand { PathCommand shifted(ui.Offset offset); List serializeToCssPaint(); + + /// Transform the command and add to targetPath. + void transform(Float64List matrix4, ui.Path targetPath); } class MoveTo extends PathCommand { @@ -1138,6 +1141,15 @@ class MoveTo extends PathCommand { return [1, x, y]; } + @override + void transform(Float64List matrix4, ui.Path targetPath) { + final double transformedX = (matrix4[0] * x) + (matrix4[4] * y) + + matrix4[12]; + final double transformedY = (matrix4[1] * x) + (matrix4[5] * y) + + matrix4[13]; + targetPath.moveTo(transformedX, transformedY); + } + @override String toString() { if (assertionsEnabled) { @@ -1164,6 +1176,15 @@ class LineTo extends PathCommand { return [2, x, y]; } + @override + void transform(Float64List matrix4, ui.Path targetPath) { + final double transformedX = (matrix4[0] * x) + (matrix4[4] * y) + + matrix4[12]; + final double transformedY = (matrix4[1] * x) + (matrix4[5] * y) + + matrix4[13]; + targetPath.lineTo(transformedX, transformedY); + } + @override String toString() { if (assertionsEnabled) { @@ -1239,6 +1260,20 @@ class QuadraticCurveTo extends PathCommand { return [4, x1, y1, x2, y2]; } + @override + void transform(Float64List matrix4, ui.Path targetPath) { + final double transformedX1 = (matrix4[0] * x1) + (matrix4[4] * y1) + + matrix4[12]; + final double transformedY1 = (matrix4[1] * x1) + (matrix4[5] * y1) + + matrix4[13]; + final double transformedX2 = (matrix4[0] * x2) + (matrix4[4] * y2) + + matrix4[12]; + final double transformedY2 = (matrix4[1] * x2) + (matrix4[5] * y2) + + matrix4[13]; + targetPath.quadraticBezierTo(transformedX1, transformedY1, + transformedX2, transformedY2); + } + @override String toString() { if (assertionsEnabled) { @@ -1271,6 +1306,24 @@ class BezierCurveTo extends PathCommand { return [5, x1, y1, x2, y2, x3, y3]; } + @override + void transform(Float64List matrix4, ui.Path targetPath) { + final double s0 = matrix4[0]; + final double s1 = matrix4[1]; + final double s4 = matrix4[4]; + final double s5 = matrix4[5]; + final double s12 = matrix4[12]; + final double s13 = matrix4[13]; + final double transformedX1 = (s0 * x1) + (s4 * y1) + s12; + final double transformedY1 = (s1 * x1) + (s5 * y1) + s13; + final double transformedX2 = (s0 * x2) + (s4 * y2) + s12; + final double transformedY2 = (s1 * x2) + (s5 * y2) + s13; + final double transformedX3 = (s0 * x3) + (s4 * y3) + s12; + final double transformedY3 = (s1 * x3) + (s5 * y3) + s13; + targetPath.cubicTo(transformedX1, transformedY1, + transformedX2, transformedY2, transformedX3, transformedY3); + } + @override String toString() { if (assertionsEnabled) { @@ -1295,6 +1348,38 @@ class RectCommand extends PathCommand { return RectCommand(x + offset.dx, y + offset.dy, width, height); } + @override + void transform(Float64List matrix4, ui.Path targetPath) { + final double s0 = matrix4[0]; + final double s1 = matrix4[1]; + final double s4 = matrix4[4]; + final double s5 = matrix4[5]; + final double s12 = matrix4[12]; + final double s13 = matrix4[13]; + final double transformedX1 = (s0 * x) + (s4 * y) + s12; + final double transformedY1 = (s1 * x) + (s5 * y) + s13; + final double x2 = x + width; + final double y2 = y + height; + final double transformedX2 = (s0 * x2) + (s4 * y) + s12; + final double transformedY2 = (s1 * x2) + (s5 * y) + s13; + final double transformedX3 = (s0 * x2) + (s4 * y2) + s12; + final double transformedY3 = (s1 * x2) + (s5 * y2) + s13; + final double transformedX4 = (s0 * x) + (s4 * y2) + s12; + final double transformedY4 = (s1 * x) + (s5 * y2) + s13; + if (transformedY1 == transformedY2 && transformedY3 == transformedY4 && + transformedX1 == transformedX4 && transformedX2 == transformedX3) { + // It is still a rectangle. + targetPath.addRect(ui.Rect.fromLTRB(transformedX1, transformedY1, + transformedX3, transformedY3)); + } else { + targetPath.moveTo(transformedX1, transformedY1); + targetPath.lineTo(transformedX2, transformedY2); + targetPath.lineTo(transformedX3, transformedY3); + targetPath.lineTo(transformedX4, transformedY4); + targetPath.close(); + } + } + @override List serializeToCssPaint() { return [6, x, y, width, height]; @@ -1326,6 +1411,12 @@ class RRectCommand extends PathCommand { } @override + void transform(Float64List matrix4, ui.Path targetPath) { + final Path roundRectPath = Path(); + _RRectToPathRenderer(roundRectPath).render(rrect); + } + + @override String toString() { if (assertionsEnabled) { return '$rrect'; @@ -1348,6 +1439,11 @@ class CloseCommand extends PathCommand { return [8]; } + @override + void transform(Float64List matrix4, ui.Path targetPath) { + targetPath.close(); + } + @override String toString() { if (assertionsEnabled) { diff --git a/lib/web_ui/lib/src/engine/rrect_renderer.dart b/lib/web_ui/lib/src/engine/rrect_renderer.dart new file mode 100644 index 0000000000000..f3c9b93068a35 --- /dev/null +++ b/lib/web_ui/lib/src/engine/rrect_renderer.dart @@ -0,0 +1,218 @@ +// 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. + +part of engine; + +/// Renders an RRect using path primitives. +abstract class _RRectRenderer { + // TODO(mdebbar): Backport the overlapping corners fix to houdini_painter.js + // To draw the rounded rectangle, perform the following steps: + // 0. Ensure border radius don't overlap + // 1. Flip left,right top,bottom since web doesn't support flipped + // coordinates with negative radii. + // 2. draw the line for the top + // 3. draw the arc for the top-right corner + // 4. draw the line for the right side + // 5. draw the arc for the bottom-right corner + // 6. draw the line for the bottom of the rectangle + // 7. draw the arc for the bottom-left corner + // 8. draw the line for the left side + // 9. draw the arc for the top-left corner + // + // After drawing, the current point will be the left side of the top of the + // rounded rectangle (after the corner). + // TODO(het): Confirm that this is the end point in Flutter for RRect + + void render(ui.RRect inputRRect, + {bool startNewPath = true, bool reverse = false}) { + // Ensure border radius curves never overlap + final ui.RRect rrect = inputRRect.scaleRadii(); + + double left = rrect.left; + double right = rrect.right; + double top = rrect.top; + double bottom = rrect.bottom; + if (left > right) { + left = right; + right = rrect.left; + } + if (top > bottom) { + top = bottom; + bottom = rrect.top; + } + final double trRadiusX = rrect.trRadiusX.abs(); + final double tlRadiusX = rrect.tlRadiusX.abs(); + final double trRadiusY = rrect.trRadiusY.abs(); + final double tlRadiusY = rrect.tlRadiusY.abs(); + final double blRadiusX = rrect.blRadiusX.abs(); + final double brRadiusX = rrect.brRadiusX.abs(); + final double blRadiusY = rrect.blRadiusY.abs(); + final double brRadiusY = rrect.brRadiusY.abs(); + + if (!reverse) { + if (startNewPath) { + beginPath(); + } + + moveTo(left + trRadiusX, top); + + // Top side and top-right corner + lineTo(right - trRadiusX, top); + ellipse( + right - trRadiusX, + top + trRadiusY, + trRadiusX, + trRadiusY, + 0, + 1.5 * math.pi, + 2.0 * math.pi, + false, + ); + + // Right side and bottom-right corner + lineTo(right, bottom - brRadiusY); + ellipse( + right - brRadiusX, + bottom - brRadiusY, + brRadiusX, + brRadiusY, + 0, + 0, + 0.5 * math.pi, + false, + ); + + // Bottom side and bottom-left corner + lineTo(left + blRadiusX, bottom); + ellipse( + left + blRadiusX, + bottom - blRadiusY, + blRadiusX, + blRadiusY, + 0, + 0.5 * math.pi, + math.pi, + false, + ); + + // Left side and top-left corner + lineTo(left, top + tlRadiusY); + ellipse( + left + tlRadiusX, + top + tlRadiusY, + tlRadiusX, + tlRadiusY, + 0, + math.pi, + 1.5 * math.pi, + false, + ); + } else { + // Draw the rounded rectangle, counterclockwise. + moveTo(right - trRadiusX, top); + + if (startNewPath) { + beginPath(); + } + + // Top side and top-left corner + lineTo(left + tlRadiusX, top); + ellipse( + left + tlRadiusX, + top + tlRadiusY, + tlRadiusX, + tlRadiusY, + 0, + 1.5 * math.pi, + 1 * math.pi, + true, + ); + + // Left side and bottom-left corner + lineTo(left, bottom - blRadiusY); + ellipse( + left + blRadiusX, + bottom - blRadiusY, + blRadiusX, + blRadiusY, + 0, + 1 * math.pi, + 0.5 * math.pi, + true, + ); + + // Bottom side and bottom-right corner + lineTo(right - brRadiusX, bottom); + ellipse( + right - brRadiusX, + bottom - brRadiusY, + brRadiusX, + brRadiusY, + 0, + 0.5 * math.pi, + 0 * math.pi, + true, + ); + + // Right side and top-right corner + lineTo(right, top + trRadiusY); + ellipse( + right - trRadiusX, + top + trRadiusY, + trRadiusX, + trRadiusY, + 0, + 0 * math.pi, + 1.5 * math.pi, + true, + ); + } + } + + void beginPath(); + void moveTo(double x, double y); + void lineTo(double x, double y); + void ellipse(double centerX, double centerY, double radiusX, double radiusY, + double rotation, double startAngle, double endAngle, bool antiClockwise); +} + +/// Renders RRect to a 2d canvas. +class _RRectToCanvasRenderer extends _RRectRenderer { + final html.CanvasRenderingContext2D context; + _RRectToCanvasRenderer(this.context); + void beginPath() { + context.beginPath(); + } + void moveTo(double x, double y) { + context.moveTo(x, y); + } + void lineTo(double x, double y) { + context.lineTo(x, y); + } + void ellipse(double centerX, double centerY, double radiusX, double radiusY, + double rotation, double startAngle, double endAngle, bool antiClockwise) { + context.ellipse(centerX, centerY, radiusX, radiusY, rotation, startAngle, + endAngle, antiClockwise); + } +} + +/// Renders RRect to a path. +class _RRectToPathRenderer extends _RRectRenderer { + final ui.Path path; + _RRectToPathRenderer(this.path); + void beginPath() { + } + void moveTo(double x, double y) { + path.moveTo(x, y); + } + void lineTo(double x, double y) { + path.lineTo(x, y); + } + void ellipse(double centerX, double centerY, double radiusX, double radiusY, + double rotation, double startAngle, double endAngle, bool antiClockwise) { + path.addArc(ui.Rect.fromLTRB(centerX - radiusX, centerY - radiusY, + centerX + radiusX, centerY + radiusY), startAngle, + antiClockwise ? startAngle - endAngle : endAngle - startAngle); + } +} diff --git a/lib/web_ui/lib/src/ui/canvas.dart b/lib/web_ui/lib/src/ui/canvas.dart index aec846a4cfc4f..ee176ce7d924e 100644 --- a/lib/web_ui/lib/src/ui/canvas.dart +++ b/lib/web_ui/lib/src/ui/canvas.dart @@ -1753,7 +1753,13 @@ class Path { /// subpath transformed by the given matrix. Path transform(Float64List matrix4) { assert(engine.matrix4IsValid(matrix4)); - throw UnimplementedError(); + final Path transformedPath = Path(); + for (final engine.Subpath subpath in subpaths) { + for (final engine.PathCommand cmd in subpath.commands) { + cmd.transform(matrix4, transformedPath); + } + } + return transformedPath; } /// Computes the bounding rectangle for this path. From 122774be69a1f0ab1ad580e2676fa7df2bd2238b Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 24 Sep 2019 16:44:46 -0700 Subject: [PATCH 02/10] Add arcToBezier for Ellipse.transform --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 3 +- .../lib/src/engine/recording_canvas.dart | 95 ++++++++++++++++--- 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 0c1a9fd6fa147..6beddc120c50e 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -700,7 +700,8 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { break; case PathCommandTypes.rRect: final RRectCommand rrectCommand = command; - _drawRRectPath(rrectCommand.rrect, startNewPath: false); + _RRectToCanvasRenderer(ctx).render(rrectCommand.rrect, + startNewPath: false); break; case PathCommandTypes.rect: final RectCommand rectCommand = command; diff --git a/lib/web_ui/lib/src/engine/recording_canvas.dart b/lib/web_ui/lib/src/engine/recording_canvas.dart index 60fb36469a3a1..50a9f7a49c49b 100644 --- a/lib/web_ui/lib/src/engine/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/recording_canvas.dart @@ -7,8 +7,16 @@ part of engine; /// Enable this to print every command applied by a canvas. const bool _debugDumpPaintCommands = false; -// Similar to [Offset.distance] -double _getDistance(double x, double y) => math.sqrt(x * x + y * y); +// Returns the squared length of the x, y (of a border radius) +// It normalizes x, y values before working with them, by +// assuming anything < 0 to be 0, because flutter may pass +// negative radii (which Skia assumes to be 0), see: +// https://skia.org/user/api/SkRRect_Reference#SkRRect_inset +double _measureBorderRadius(double x, double y) { + double clampedX = x < 0 ? 0 : x; + double clampedY = y < 0 ? 0 : y; + return clampedX * clampedX + clampedY * clampedY; +} /// Records canvas commands to be applied to a [EngineCanvas]. /// @@ -231,8 +239,8 @@ class RecordingCanvas { } void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) { - // Ensure inner is fully contained within outer, by comparing its - // defining points (including its border radius) + // Check the inner bounds are contained within the outer bounds + // see: https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkCanvas.cpp?l=1787-1789 ui.Rect innerRect = inner.outerRect; ui.Rect outerRect = outer.outerRect; if (outerRect == innerRect || outerRect.intersect(innerRect) != innerRect) { @@ -243,15 +251,15 @@ class RecordingCanvas { final ui.RRect scaledOuter = outer.scaleRadii(); final ui.RRect scaledInner = inner.scaleRadii(); - final double outerTl = _getDistance(scaledOuter.tlRadiusX, scaledOuter.tlRadiusY); - final double outerTr = _getDistance(scaledOuter.trRadiusX, scaledOuter.trRadiusY); - final double outerBl = _getDistance(scaledOuter.blRadiusX, scaledOuter.blRadiusY); - final double outerBr = _getDistance(scaledOuter.brRadiusX, scaledOuter.brRadiusY); + final double outerTl = _measureBorderRadius(scaledOuter.tlRadiusX, scaledOuter.tlRadiusY); + final double outerTr = _measureBorderRadius(scaledOuter.trRadiusX, scaledOuter.trRadiusY); + final double outerBl = _measureBorderRadius(scaledOuter.blRadiusX, scaledOuter.blRadiusY); + final double outerBr = _measureBorderRadius(scaledOuter.brRadiusX, scaledOuter.brRadiusY); - final double innerTl = _getDistance(scaledInner.tlRadiusX, scaledInner.tlRadiusY); - final double innerTr = _getDistance(scaledInner.trRadiusX, scaledInner.trRadiusY); - final double innerBl = _getDistance(scaledInner.blRadiusX, scaledInner.blRadiusY); - final double innerBr = _getDistance(scaledInner.brRadiusX, scaledInner.brRadiusY); + final double innerTl = _measureBorderRadius(scaledInner.tlRadiusX, scaledInner.tlRadiusY); + final double innerTr = _measureBorderRadius(scaledInner.trRadiusX, scaledInner.trRadiusY); + final double innerBl = _measureBorderRadius(scaledInner.blRadiusX, scaledInner.blRadiusY); + final double innerBr = _measureBorderRadius(scaledInner.brRadiusX, scaledInner.brRadiusY); if (innerTl > outerTl || innerTr > outerTr || innerBl > outerBl || innerBr > outerBr) { return; // Some inner radius is overlapping some outer radius @@ -1230,6 +1238,67 @@ class Ellipse extends PathCommand { ]; } + @override + void transform(Float64List matrix4, ui.Path targetPath) { + // TODO(flutter_web): Implement rotation. + _drawArcWithBezier(x, y, radiusX, radiusY, + startAngle, + anticlockwise ? startAngle - endAngle : endAngle - startAngle, + targetPath); + } + + void _drawArcWithBezier(double centerX, double centerY, double radiusX, double radiusY, + double startAngle, double sweep, ui.Path targetPath) { + double ratio = sweep.abs() / (math.pi / 2.0); + if ((1.0 - ratio).abs() < 0.0000001) { + ratio = 1.0; + } + final int segments = math.max(ratio.ceil(), 1); + final double anglePerSegment = sweep / segments; + double angle = startAngle; + for (int segment = 0; segment < segments; segment++) { + _drawArcSegment(targetPath, centerX, centerY, radiusX, radiusY, angle, anglePerSegment, segment == 0); + angle += anglePerSegment; + } + } + + void _drawArcSegment(ui.Path path, double cx, double cy, double rx, double ry, double startAngle, double sweep, + bool startPath) { + final double s = (sweep == 1.5707963267948966) + ? 0.551915024494 + : sweep == -1.5707963267948966 ? -0.551915024494 : 4 / 3 * math.tan(sweep / 4); + + final double x1 = math.cos(startAngle); + final double y1 = math.sin(startAngle); + final double endAngle = startAngle + sweep; + double x2 = math.cos(endAngle); + double y2 = math.sin(endAngle); + + double x = x1 - y1 * s; + double y = y1 + x1 * s; + x *= rx; + y *= ry; + final double cpx1 = x + cx; + final double cpy1 = y + cy; + + x = x2 + y2 * s; + y = y2 - x2 * s; + x *= rx; + y *= ry; + + final double cpx2 = x + cx; + final double cpy2 = y + cy; + + x2 *= rx; + y2 *= ry; + x2 += cx; + y2 += cy; + if (startPath) { + path.moveTo(cx + x1 * rx, cy + y1 * ry); + } + path.cubicTo(cpx1, cpy1, cpx2, cpy2, x2, y2); + } + @override String toString() { if (assertionsEnabled) { @@ -1412,7 +1481,7 @@ class RRectCommand extends PathCommand { @override void transform(Float64List matrix4, ui.Path targetPath) { - final Path roundRectPath = Path(); + final ui.Path roundRectPath = ui.Path(); _RRectToPathRenderer(roundRectPath).render(rrect); } From 09427557f455673cdbf4484e00e773b6ff6b6120 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 25 Sep 2019 13:12:55 -0700 Subject: [PATCH 03/10] Wire up canvas.addPath with matrix --- lib/web_ui/lib/src/ui/canvas.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/ui/canvas.dart b/lib/web_ui/lib/src/ui/canvas.dart index ee176ce7d924e..183ae02f35a23 100644 --- a/lib/web_ui/lib/src/ui/canvas.dart +++ b/lib/web_ui/lib/src/ui/canvas.dart @@ -1584,12 +1584,15 @@ class Path { if (dx == 0.0 && dy == 0.0) { subpaths.addAll(path.subpaths); } else { - throw UnimplementedError('Cannot add path with non-zero offset'); + subpaths.addAll(path.transform( + engine.Matrix4.translationValues(dx, dy, 0.0).storage).subpaths); } } void _addPathWithMatrix(Path path, double dx, double dy, Float64List matrix) { - throw UnimplementedError('Cannot add path with transform matrix'); + final engine.Matrix4 transform = engine.Matrix4.fromFloat64List(matrix); + transform.translate(dx, dy); + subpaths.addAll(path.transform(transform.storage).subpaths); } /// Adds the given path to this path by extending the current segment of this From 6bac0ee95d4fc3eb2959e03b96cb006440cfad46 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 26 Sep 2019 12:44:58 -0700 Subject: [PATCH 04/10] Add rotation support to arctobezier --- .../lib/src/engine/recording_canvas.dart | 104 +++++++++++------- lib/web_ui/lib/src/ui/canvas.dart | 14 +-- 2 files changed, 69 insertions(+), 49 deletions(-) diff --git a/lib/web_ui/lib/src/engine/recording_canvas.dart b/lib/web_ui/lib/src/engine/recording_canvas.dart index 50a9f7a49c49b..f71a565a3cb55 100644 --- a/lib/web_ui/lib/src/engine/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/recording_canvas.dart @@ -1240,15 +1240,15 @@ class Ellipse extends PathCommand { @override void transform(Float64List matrix4, ui.Path targetPath) { - // TODO(flutter_web): Implement rotation. - _drawArcWithBezier(x, y, radiusX, radiusY, + _drawArcWithBezier(x, y, radiusX, radiusY, rotation, startAngle, anticlockwise ? startAngle - endAngle : endAngle - startAngle, targetPath); } - void _drawArcWithBezier(double centerX, double centerY, double radiusX, double radiusY, - double startAngle, double sweep, ui.Path targetPath) { + void _drawArcWithBezier(double centerX, double centerY, + double radiusX, double radiusY, double rotation, double startAngle, + double sweep, ui.Path targetPath) { double ratio = sweep.abs() / (math.pi / 2.0); if ((1.0 - ratio).abs() < 0.0000001) { ratio = 1.0; @@ -1257,46 +1257,64 @@ class Ellipse extends PathCommand { final double anglePerSegment = sweep / segments; double angle = startAngle; for (int segment = 0; segment < segments; segment++) { - _drawArcSegment(targetPath, centerX, centerY, radiusX, radiusY, angle, anglePerSegment, segment == 0); + _drawArcSegment(targetPath, centerX, centerY, radiusX, radiusY, rotation, + angle, anglePerSegment, segment == 0); angle += anglePerSegment; } } - void _drawArcSegment(ui.Path path, double cx, double cy, double rx, double ry, double startAngle, double sweep, - bool startPath) { - final double s = (sweep == 1.5707963267948966) - ? 0.551915024494 - : sweep == -1.5707963267948966 ? -0.551915024494 : 4 / 3 * math.tan(sweep / 4); + void _drawArcSegment(ui.Path path, double centerX, double centerY, + double radiusX, double radiusY, double rotation, double startAngle, + double sweep, bool startPath) { + final double s = 4 / 3 * math.tan(sweep / 4); + // Rotate unit vector to startAngle and endAngle to use for computing start + // and end points of segment. final double x1 = math.cos(startAngle); final double y1 = math.sin(startAngle); final double endAngle = startAngle + sweep; - double x2 = math.cos(endAngle); - double y2 = math.sin(endAngle); - - double x = x1 - y1 * s; - double y = y1 + x1 * s; - x *= rx; - y *= ry; - final double cpx1 = x + cx; - final double cpy1 = y + cy; - - x = x2 + y2 * s; - y = y2 - x2 * s; - x *= rx; - y *= ry; - - final double cpx2 = x + cx; - final double cpy2 = y + cy; - - x2 *= rx; - y2 *= ry; - x2 += cx; - y2 += cy; + final double x2 = math.cos(endAngle); + final double y2 = math.sin(endAngle); + + // Compute scaled curve control points. + final double cpx1 = (x1 - y1 * s) * radiusX; + final double cpy1 = (y1 + x1 * s) * radiusY; + final double cpx2 = (x2 + y2 * s) * radiusX; + final double cpy2 = (y2 - x2 * s) * radiusY; + + final double endPointX = centerX + x2 * radiusX; + final double endPointY = centerY + y2 * radiusY; + + final double rotationRad = rotation * math.pi / 180.0; + final double cosR = math.cos(rotationRad); + final double sinR = math.sin(rotationRad); if (startPath) { - path.moveTo(cx + x1 * rx, cy + y1 * ry); + final double scaledX1 = x1 * radiusX; + final double scaledY1 = y1 * radiusY; + if (rotation == 0.0) { + path.moveTo(centerX + scaledX1, centerY + scaledY1); + } else { + final double rotatedStartX = (scaledX1 * cosR) + (scaledY1 * sinR); + final double rotatedStartY = (scaledY1 * cosR) - (scaledX1 * sinR); + path.moveTo(centerX + rotatedStartX, centerY + rotatedStartY); + } + } + if (rotation == 0.0) { + path.cubicTo(centerX + cpx1, centerY + cpy1, + centerX + cpx2, centerY + cpy2, + endPointX, endPointY); + } else { + final double rotatedCpx1 = centerX + (cpx1 * cosR) + (cpy1 * sinR); + final double rotatedCpy1 = centerY + (cpy1 * cosR) - (cpx1 * sinR); + final double rotatedCpx2 = centerX + (cpx2 * cosR) + (cpy2 * sinR); + final double rotatedCpy2 = centerY + (cpy2 * cosR) - (cpx2 * sinR); + final double rotatedEndX = centerX + ((endPointX - centerX) * cosR) + + ((endPointY - centerY) * sinR); + final double rotatedEndY = centerY + ((endPointY - centerY) * cosR) + - ((endPointX - centerX) * sinR); + path.cubicTo(rotatedCpx1, rotatedCpy1, rotatedCpx2, rotatedCpy2, + rotatedEndX, rotatedEndY); } - path.cubicTo(cpx1, cpy1, cpx2, cpy2, x2, y2); } @override @@ -1331,14 +1349,16 @@ class QuadraticCurveTo extends PathCommand { @override void transform(Float64List matrix4, ui.Path targetPath) { - final double transformedX1 = (matrix4[0] * x1) + (matrix4[4] * y1) - + matrix4[12]; - final double transformedY1 = (matrix4[1] * x1) + (matrix4[5] * y1) - + matrix4[13]; - final double transformedX2 = (matrix4[0] * x2) + (matrix4[4] * y2) - + matrix4[12]; - final double transformedY2 = (matrix4[1] * x2) + (matrix4[5] * y2) - + matrix4[13]; + final double m0 = matrix4[0]; + final double m1 = matrix4[1]; + final double m4 = matrix4[4]; + final double m5 = matrix4[5]; + final double m12 = matrix4[12]; + final double m13 = matrix4[13]; + final double transformedX1 = (m0 * x1) + (m4 * y1) + m12; + final double transformedY1 = (m1 * x1) + (m5 * y1) + m13; + final double transformedX2 = (m0 * x2) + (m4 * y2) + m12; + final double transformedY2 = (m1 * x2) + (m5 * y2) + m13; targetPath.quadraticBezierTo(transformedX1, transformedY1, transformedX2, transformedY2); } diff --git a/lib/web_ui/lib/src/ui/canvas.dart b/lib/web_ui/lib/src/ui/canvas.dart index 183ae02f35a23..96642a60f8185 100644 --- a/lib/web_ui/lib/src/ui/canvas.dart +++ b/lib/web_ui/lib/src/ui/canvas.dart @@ -1745,20 +1745,20 @@ class Path { /// subpath translated by the given offset. Path shift(Offset offset) { assert(engine.offsetIsValid(offset)); - final List shiftedSubpaths = []; - for (final engine.Subpath subpath in subpaths) { - shiftedSubpaths.add(subpath.shift(offset)); + final List shiftedSubPaths = []; + for (final engine.Subpath subPath in subpaths) { + shiftedSubPaths.add(subPath.shift(offset)); } - return Path._clone(shiftedSubpaths, fillType); + return Path._clone(shiftedSubPaths, fillType); } /// Returns a copy of the path with all the segments of every - /// subpath transformed by the given matrix. + /// sub path transformed by the given matrix. Path transform(Float64List matrix4) { assert(engine.matrix4IsValid(matrix4)); final Path transformedPath = Path(); - for (final engine.Subpath subpath in subpaths) { - for (final engine.PathCommand cmd in subpath.commands) { + for (final engine.Subpath subPath in subpaths) { + for (final engine.PathCommand cmd in subPath.commands) { cmd.transform(matrix4, transformedPath); } } From 1439299bb727dffe315679e0e8c038b7770b0ec3 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 3 Oct 2019 14:56:09 -0700 Subject: [PATCH 05/10] Add common transform helper --- .../lib/src/engine/recording_canvas.dart | 30 +-- .../engine/path_transform_test.dart | 230 ++++++++++++++++++ 2 files changed, 246 insertions(+), 14 deletions(-) create mode 100644 lib/web_ui/test/golden_tests/engine/path_transform_test.dart diff --git a/lib/web_ui/lib/src/engine/recording_canvas.dart b/lib/web_ui/lib/src/engine/recording_canvas.dart index f71a565a3cb55..5fcc806b10c6b 100644 --- a/lib/web_ui/lib/src/engine/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/recording_canvas.dart @@ -1131,6 +1131,11 @@ abstract class PathCommand { /// Transform the command and add to targetPath. void transform(Float64List matrix4, ui.Path targetPath); + + /// Helper method for implementing transforms. + static ui.Offset _transformOffset(double x, double y, Float64List matrix4) => + ui.Offset((matrix4[0] * x) + (matrix4[4] * y) + matrix4[12], + (matrix4[1] * x) + (matrix4[5] * y) + matrix4[13]); } class MoveTo extends PathCommand { @@ -1151,11 +1156,8 @@ class MoveTo extends PathCommand { @override void transform(Float64List matrix4, ui.Path targetPath) { - final double transformedX = (matrix4[0] * x) + (matrix4[4] * y) - + matrix4[12]; - final double transformedY = (matrix4[1] * x) + (matrix4[5] * y) - + matrix4[13]; - targetPath.moveTo(transformedX, transformedY); + final ui.Offset offset = PathCommand._transformOffset(x, y, matrix4); + targetPath.moveTo(offset.dx, offset.dy); } @override @@ -1186,11 +1188,8 @@ class LineTo extends PathCommand { @override void transform(Float64List matrix4, ui.Path targetPath) { - final double transformedX = (matrix4[0] * x) + (matrix4[4] * y) - + matrix4[12]; - final double transformedY = (matrix4[1] * x) + (matrix4[5] * y) - + matrix4[13]; - targetPath.lineTo(transformedX, transformedY); + final ui.Offset offset = PathCommand._transformOffset(x, y, matrix4); + targetPath.lineTo(offset.dx, offset.dy); } @override @@ -1240,15 +1239,17 @@ class Ellipse extends PathCommand { @override void transform(Float64List matrix4, ui.Path targetPath) { + final ui.Path bezierPath = ui.Path(); _drawArcWithBezier(x, y, radiusX, radiusY, rotation, startAngle, anticlockwise ? startAngle - endAngle : endAngle - startAngle, - targetPath); + matrix4, bezierPath); + targetPath.addPath(bezierPath, ui.Offset.zero, matrix4: matrix4); } void _drawArcWithBezier(double centerX, double centerY, double radiusX, double radiusY, double rotation, double startAngle, - double sweep, ui.Path targetPath) { + double sweep, Float64List matrix4, ui.Path targetPath) { double ratio = sweep.abs() / (math.pi / 2.0); if ((1.0 - ratio).abs() < 0.0000001) { ratio = 1.0; @@ -1258,14 +1259,14 @@ class Ellipse extends PathCommand { double angle = startAngle; for (int segment = 0; segment < segments; segment++) { _drawArcSegment(targetPath, centerX, centerY, radiusX, radiusY, rotation, - angle, anglePerSegment, segment == 0); + angle, anglePerSegment, segment == 0, matrix4); angle += anglePerSegment; } } void _drawArcSegment(ui.Path path, double centerX, double centerY, double radiusX, double radiusY, double rotation, double startAngle, - double sweep, bool startPath) { + double sweep, bool startPath, Float64List matrix4) { final double s = 4 / 3 * math.tan(sweep / 4); // Rotate unit vector to startAngle and endAngle to use for computing start @@ -1503,6 +1504,7 @@ class RRectCommand extends PathCommand { void transform(Float64List matrix4, ui.Path targetPath) { final ui.Path roundRectPath = ui.Path(); _RRectToPathRenderer(roundRectPath).render(rrect); + targetPath.addPath(roundRectPath, ui.Offset.zero, matrix4: matrix4); } @override diff --git a/lib/web_ui/test/golden_tests/engine/path_transform_test.dart b/lib/web_ui/test/golden_tests/engine/path_transform_test.dart new file mode 100644 index 0000000000000..c504a39eaa5b0 --- /dev/null +++ b/lib/web_ui/test/golden_tests/engine/path_transform_test.dart @@ -0,0 +1,230 @@ +// 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:math' as math; +import 'dart:typed_data'; + +import 'package:ui/ui.dart' hide TextStyle; +import 'package:ui/src/engine.dart'; +import 'package:test/test.dart'; + +import '../../matchers.dart'; +import 'package:web_engine_tester/golden_tester.dart'; + +void main() async { + const double screenWidth = 600.0; + const double screenHeight = 800.0; + const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight); + final Paint testPaint = Paint()..color = const Color(0xFFFF0000); + + // Commit a recording canvas to a bitmap, and compare with the expected + Future _checkScreenshot(RecordingCanvas rc, String fileName, + { Rect region = const Rect.fromLTWH(0, 0, 500, 500), bool write = false + }) async { + + final EngineCanvas engineCanvas = BitmapCanvas(screenRect); + rc.apply(engineCanvas); + + // Wrap in 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, write: true); + } 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(); + }); + + test('Should draw transformed line.', () async { + final RecordingCanvas rc = + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + final Path path = Path(); + path.moveTo(0, 0); + path.lineTo(300, 200); + rc.drawPath( + path, + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..color = const Color(0xFF404000)); + final Path transformedPath = Path(); + final Matrix4 testMatrixTranslateRotate = + Matrix4.rotationZ(math.pi * 30.0 / 180.0) + ..translate(100,20); + transformedPath.addPath(path, Offset.zero, + matrix4: testMatrixTranslateRotate.storage); + rc.drawPath( + transformedPath, + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..color = const Color.fromRGBO(0, 128, 255, 1.0)); + await _checkScreenshot(rc, 'path_transform_with_line'); + }); + + test('Should draw transformed line.', () async { + final RecordingCanvas rc = + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + final Path path = Path(); + path.addRect(Rect.fromLTRB(50, 40, 300, 100)); + rc.drawPath( + path, + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..color = const Color(0xFF404000)); + final Path transformedPath = Path(); + final Matrix4 testMatrixTranslateRotate = + Matrix4.rotationZ(math.pi * 30.0 / 180.0) + ..translate(100,20); + transformedPath.addPath(path, Offset.zero, + matrix4: testMatrixTranslateRotate.storage); + rc.drawPath( + transformedPath, + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..color = const Color.fromRGBO(0, 128, 255, 1.0)); + await _checkScreenshot(rc, 'path_transform_with_rect'); + }); + + test('Should draw transformed quadratic curve.', () async { + final RecordingCanvas rc = + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + final Path path = Path(); + path.moveTo(100, 100); + path.quadraticBezierTo(100, 300, 400,300); + rc.drawPath( + path, + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..color = const Color(0xFF404000)); + final Path transformedPath = Path(); + final Matrix4 testMatrixTranslateRotate = + Matrix4.rotationZ(math.pi * 30.0 / 180.0) + ..translate(100, -80); + transformedPath.addPath(path, Offset.zero, + matrix4: testMatrixTranslateRotate.storage); + rc.drawPath( + transformedPath, + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..color = const Color.fromRGBO(0, 128, 255, 1.0)); + await _checkScreenshot(rc, 'path_transform_with_quadratic_curve'); + }); + + test('Should draw transformed conic.', () async { + final RecordingCanvas rc = + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + const double yStart = 20; + + const Offset p0 = Offset(25, yStart + 25); + const Offset pc = Offset(60, yStart + 150); + const Offset p2 = Offset(100, yStart + 50); + + final Path path = Path(); + path.moveTo(p0.dx, p0.dy); + path.conicTo(pc.dx, pc.dy, p2.dx, p2.dy, 0.5); + path.close(); + path.moveTo(p0.dx, p0.dy + 100); + path.conicTo(pc.dx, pc.dy + 100, p2.dx, p2.dy + 100, 10); + path.close(); + + rc.drawPath( + path, + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..color = const Color(0xFF404000)); + final Path transformedPath = Path(); + final Matrix4 testMatrixTranslateRotate = + Matrix4.rotationZ(math.pi * 30.0 / 180.0) + ..translate(100, -80); + transformedPath.addPath(path, Offset.zero, + matrix4: testMatrixTranslateRotate.storage); + rc.drawPath( + transformedPath, + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..color = const Color.fromRGBO(0, 128, 255, 1.0)); + await _checkScreenshot(rc, 'path_transform_with_conic'); + }); + + test('Should draw transformed arc.', () async { + final RecordingCanvas rc = + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + const double yStart = 20; + + final Path path = Path(); + path.moveTo(350, 280); + path.arcToPoint(Offset(450,90), radius: Radius.elliptical(200, 50), + rotation: -math.pi/6.0, largeArc: true, clockwise: true); + path.close(); + + rc.drawPath( + path, + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..color = const Color(0xFF404000)); + + final Path transformedPath = Path(); + final Matrix4 testMatrixTranslateRotate = + Matrix4.rotationZ(math.pi * 30.0 / 180.0) + ..translate(100, 10); + transformedPath.addPath(path, Offset.zero, + matrix4: testMatrixTranslateRotate.storage); + rc.drawPath( + transformedPath, + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..color = const Color.fromRGBO(0, 128, 255, 1.0)); + await _checkScreenshot(rc, 'path_transform_with_arc'); + }); + + test('Should draw transformed rrect.', () async { + final RecordingCanvas rc = + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + const double yStart = 20; + + final Path path = Path(); + path.addRRect(RRect.fromLTRBR(50, 50, 300, 200, Radius.elliptical(4, 8))); + + rc.drawPath( + path, + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..color = const Color(0xFF404000)); + + final Path transformedPath = Path(); + final Matrix4 testMatrixTranslateRotate = + Matrix4.rotationZ(math.pi * 30.0 / 180.0) + ..translate(100, -80); + transformedPath.addPath(path, Offset.zero, + matrix4: testMatrixTranslateRotate.storage); + rc.drawPath( + transformedPath, + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..color = const Color.fromRGBO(0, 128, 255, 1.0)); + await _checkScreenshot(rc, 'path_transform_with_rrect'); + }); +} From ba30d298b22ddd0e178dc8f4e587ae3f9d796b14 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 3 Oct 2019 15:52:17 -0700 Subject: [PATCH 06/10] disable golden write --- lib/web_ui/test/golden_tests/engine/path_transform_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/test/golden_tests/engine/path_transform_test.dart b/lib/web_ui/test/golden_tests/engine/path_transform_test.dart index c504a39eaa5b0..812fcf4ac984c 100644 --- a/lib/web_ui/test/golden_tests/engine/path_transform_test.dart +++ b/lib/web_ui/test/golden_tests/engine/path_transform_test.dart @@ -32,7 +32,7 @@ void main() async { try { sceneElement.append(engineCanvas.rootElement); html.document.body.append(sceneElement); - await matchGoldenFile('$fileName.png', region: region, write: true); + await matchGoldenFile('$fileName.png', region: region); } finally { // The page is reused across tests, so remove the element after taking the // Scuba screenshot. From 4b12b6d3523a2c263930f46be0bd626dd8ec0934 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 4 Oct 2019 09:16:39 -0700 Subject: [PATCH 07/10] Updates goldens commit --- lib/web_ui/dev/goldens_lock.yaml | 2 +- lib/web_ui/test/golden_tests/engine/path_transform_test.dart | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index 1b72c1367e6bf..d525275393493 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: dd993a32c23c5c542f083134467e7cda09cac975 +revision: eed583fe936db83ded66fa706fd2650d4e9db038 diff --git a/lib/web_ui/test/golden_tests/engine/path_transform_test.dart b/lib/web_ui/test/golden_tests/engine/path_transform_test.dart index 812fcf4ac984c..5952753c4e374 100644 --- a/lib/web_ui/test/golden_tests/engine/path_transform_test.dart +++ b/lib/web_ui/test/golden_tests/engine/path_transform_test.dart @@ -74,6 +74,7 @@ void main() async { await _checkScreenshot(rc, 'path_transform_with_line'); }); + test('Should draw transformed line.', () async { final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); From 89712f0fa3ea35474282e230e21df7e90372b111 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 4 Oct 2019 12:17:48 -0700 Subject: [PATCH 08/10] dartfmt --- .../engine/path_transform_test.dart | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/lib/web_ui/test/golden_tests/engine/path_transform_test.dart b/lib/web_ui/test/golden_tests/engine/path_transform_test.dart index 5952753c4e374..6b87c0af7f8ed 100644 --- a/lib/web_ui/test/golden_tests/engine/path_transform_test.dart +++ b/lib/web_ui/test/golden_tests/engine/path_transform_test.dart @@ -21,9 +21,8 @@ void main() async { // Commit a recording canvas to a bitmap, and compare with the expected Future _checkScreenshot(RecordingCanvas rc, String fileName, - { Rect region = const Rect.fromLTWH(0, 0, 500, 500), bool write = false - }) async { - + {Rect region = const Rect.fromLTWH(0, 0, 500, 500), + bool write = false}) async { final EngineCanvas engineCanvas = BitmapCanvas(screenRect); rc.apply(engineCanvas); @@ -61,8 +60,7 @@ void main() async { ..color = const Color(0xFF404000)); final Path transformedPath = Path(); final Matrix4 testMatrixTranslateRotate = - Matrix4.rotationZ(math.pi * 30.0 / 180.0) - ..translate(100,20); + Matrix4.rotationZ(math.pi * 30.0 / 180.0)..translate(100, 20); transformedPath.addPath(path, Offset.zero, matrix4: testMatrixTranslateRotate.storage); rc.drawPath( @@ -74,10 +72,9 @@ void main() async { await _checkScreenshot(rc, 'path_transform_with_line'); }); - test('Should draw transformed line.', () async { final RecordingCanvas rc = - RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); final Path path = Path(); path.addRect(Rect.fromLTRB(50, 40, 300, 100)); rc.drawPath( @@ -88,8 +85,7 @@ void main() async { ..color = const Color(0xFF404000)); final Path transformedPath = Path(); final Matrix4 testMatrixTranslateRotate = - Matrix4.rotationZ(math.pi * 30.0 / 180.0) - ..translate(100,20); + Matrix4.rotationZ(math.pi * 30.0 / 180.0)..translate(100, 20); transformedPath.addPath(path, Offset.zero, matrix4: testMatrixTranslateRotate.storage); rc.drawPath( @@ -103,10 +99,10 @@ void main() async { test('Should draw transformed quadratic curve.', () async { final RecordingCanvas rc = - RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); final Path path = Path(); path.moveTo(100, 100); - path.quadraticBezierTo(100, 300, 400,300); + path.quadraticBezierTo(100, 300, 400, 300); rc.drawPath( path, Paint() @@ -115,8 +111,7 @@ void main() async { ..color = const Color(0xFF404000)); final Path transformedPath = Path(); final Matrix4 testMatrixTranslateRotate = - Matrix4.rotationZ(math.pi * 30.0 / 180.0) - ..translate(100, -80); + Matrix4.rotationZ(math.pi * 30.0 / 180.0)..translate(100, -80); transformedPath.addPath(path, Offset.zero, matrix4: testMatrixTranslateRotate.storage); rc.drawPath( @@ -130,7 +125,7 @@ void main() async { test('Should draw transformed conic.', () async { final RecordingCanvas rc = - RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); const double yStart = 20; const Offset p0 = Offset(25, yStart + 25); @@ -153,8 +148,7 @@ void main() async { ..color = const Color(0xFF404000)); final Path transformedPath = Path(); final Matrix4 testMatrixTranslateRotate = - Matrix4.rotationZ(math.pi * 30.0 / 180.0) - ..translate(100, -80); + Matrix4.rotationZ(math.pi * 30.0 / 180.0)..translate(100, -80); transformedPath.addPath(path, Offset.zero, matrix4: testMatrixTranslateRotate.storage); rc.drawPath( @@ -168,13 +162,16 @@ void main() async { test('Should draw transformed arc.', () async { final RecordingCanvas rc = - RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); const double yStart = 20; final Path path = Path(); path.moveTo(350, 280); - path.arcToPoint(Offset(450,90), radius: Radius.elliptical(200, 50), - rotation: -math.pi/6.0, largeArc: true, clockwise: true); + path.arcToPoint(Offset(450, 90), + radius: Radius.elliptical(200, 50), + rotation: -math.pi / 6.0, + largeArc: true, + clockwise: true); path.close(); rc.drawPath( @@ -186,8 +183,7 @@ void main() async { final Path transformedPath = Path(); final Matrix4 testMatrixTranslateRotate = - Matrix4.rotationZ(math.pi * 30.0 / 180.0) - ..translate(100, 10); + Matrix4.rotationZ(math.pi * 30.0 / 180.0)..translate(100, 10); transformedPath.addPath(path, Offset.zero, matrix4: testMatrixTranslateRotate.storage); rc.drawPath( @@ -201,7 +197,7 @@ void main() async { test('Should draw transformed rrect.', () async { final RecordingCanvas rc = - RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); const double yStart = 20; final Path path = Path(); @@ -216,8 +212,7 @@ void main() async { final Path transformedPath = Path(); final Matrix4 testMatrixTranslateRotate = - Matrix4.rotationZ(math.pi * 30.0 / 180.0) - ..translate(100, -80); + Matrix4.rotationZ(math.pi * 30.0 / 180.0)..translate(100, -80); transformedPath.addPath(path, Offset.zero, matrix4: testMatrixTranslateRotate.storage); rc.drawPath( From 173a9d6db44447dde375deee42da2439cad962d9 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 4 Oct 2019 13:04:18 -0700 Subject: [PATCH 09/10] update licenses for new file --- ci/licenses_golden/licenses_flutter | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 2f76b8683b20b..e400c9a0fa059 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -387,6 +387,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/plugins.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_binding.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/recording_canvas.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/rrect_renderer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/accessibility.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/checkable.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/image.dart From 5c794867fe941145316b9f2bde72959b8773b4f8 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 4 Oct 2019 13:19:28 -0700 Subject: [PATCH 10/10] dartfmt --- lib/web_ui/lib/src/engine/rrect_renderer.dart | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/web_ui/lib/src/engine/rrect_renderer.dart b/lib/web_ui/lib/src/engine/rrect_renderer.dart index f3c9b93068a35..f463f2c5bad4f 100644 --- a/lib/web_ui/lib/src/engine/rrect_renderer.dart +++ b/lib/web_ui/lib/src/engine/rrect_renderer.dart @@ -184,12 +184,15 @@ class _RRectToCanvasRenderer extends _RRectRenderer { void beginPath() { context.beginPath(); } + void moveTo(double x, double y) { context.moveTo(x, y); } + void lineTo(double x, double y) { context.lineTo(x, y); } + void ellipse(double centerX, double centerY, double radiusX, double radiusY, double rotation, double startAngle, double endAngle, bool antiClockwise) { context.ellipse(centerX, centerY, radiusX, radiusY, rotation, startAngle, @@ -201,18 +204,21 @@ class _RRectToCanvasRenderer extends _RRectRenderer { class _RRectToPathRenderer extends _RRectRenderer { final ui.Path path; _RRectToPathRenderer(this.path); - void beginPath() { - } + void beginPath() {} void moveTo(double x, double y) { path.moveTo(x, y); } + void lineTo(double x, double y) { path.lineTo(x, y); } + void ellipse(double centerX, double centerY, double radiusX, double radiusY, double rotation, double startAngle, double endAngle, bool antiClockwise) { - path.addArc(ui.Rect.fromLTRB(centerX - radiusX, centerY - radiusY, - centerX + radiusX, centerY + radiusY), startAngle, + path.addArc( + ui.Rect.fromLTRB(centerX - radiusX, centerY - radiusY, + centerX + radiusX, centerY + radiusY), + startAngle, antiClockwise ? startAngle - endAngle : endAngle - startAngle); } }