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

[web] Add support for path transform (#12794)

* Implement path.transform
上级 5d61a019
......@@ -390,6 +390,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
......
......@@ -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';
......
......@@ -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);
}
......@@ -886,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;
......
......@@ -1128,6 +1128,14 @@ abstract class PathCommand {
PathCommand shifted(ui.Offset offset);
List<dynamic> serializeToCssPaint();
/// 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 {
......@@ -1146,6 +1154,12 @@ class MoveTo extends PathCommand {
return <dynamic>[1, x, y];
}
@override
void transform(Float64List matrix4, ui.Path targetPath) {
final ui.Offset offset = PathCommand._transformOffset(x, y, matrix4);
targetPath.moveTo(offset.dx, offset.dy);
}
@override
String toString() {
if (assertionsEnabled) {
......@@ -1172,6 +1186,12 @@ class LineTo extends PathCommand {
return <dynamic>[2, x, y];
}
@override
void transform(Float64List matrix4, ui.Path targetPath) {
final ui.Offset offset = PathCommand._transformOffset(x, y, matrix4);
targetPath.lineTo(offset.dx, offset.dy);
}
@override
String toString() {
if (assertionsEnabled) {
......@@ -1217,6 +1237,87 @@ 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,
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, Float64List matrix4, 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, rotation,
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, Float64List matrix4) {
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;
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) {
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);
}
}
@override
String toString() {
if (assertionsEnabled) {
......@@ -1247,6 +1348,22 @@ class QuadraticCurveTo extends PathCommand {
return <dynamic>[4, x1, y1, x2, y2];
}
@override
void transform(Float64List matrix4, ui.Path targetPath) {
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);
}
@override
String toString() {
if (assertionsEnabled) {
......@@ -1279,6 +1396,24 @@ class BezierCurveTo extends PathCommand {
return <dynamic>[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) {
......@@ -1303,6 +1438,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<dynamic> serializeToCssPaint() {
return <dynamic>[6, x, y, width, height];
......@@ -1334,6 +1501,13 @@ class RRectCommand extends PathCommand {
}
@override
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
String toString() {
if (assertionsEnabled) {
return '$rrect';
......@@ -1356,6 +1530,11 @@ class CloseCommand extends PathCommand {
return <dynamic>[8];
}
@override
void transform(Float64List matrix4, ui.Path targetPath) {
targetPath.close();
}
@override
String toString() {
if (assertionsEnabled) {
......
// 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);
}
}
......@@ -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
......@@ -1742,18 +1745,24 @@ class Path {
/// subpath translated by the given offset.
Path shift(Offset offset) {
assert(engine.offsetIsValid(offset));
final List<engine.Subpath> shiftedSubpaths = <engine.Subpath>[];
for (final engine.Subpath subpath in subpaths) {
shiftedSubpaths.add(subpath.shift(offset));
final List<engine.Subpath> shiftedSubPaths = <engine.Subpath>[];
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));
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.
......
// 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<void> _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 <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);
} 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');
});
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册