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

Implement Canvas drawPoints API for Html backend (#15870)

上级 cb5e7fe1
repository: https://github.com/flutter/goldens.git
revision: 30ef2668489dab3191a5df251330aedb9b0c239a
revision: 3db2bb2329e7277c34389a92507eacaab774c8e8
......@@ -570,6 +570,25 @@ class BitmapCanvas extends EngineCanvas {
_canvasPool.currentTransform, vertices, blendMode, paint);
}
@override
void drawPoints(ui.PointMode pointMode, Float32List points,
double strokeWidth, ui.Color color) {
ContextStateHandle contextHandle = _canvasPool.contextHandle;
contextHandle
..lineWidth = strokeWidth
..blendMode = ui.BlendMode.srcOver
..strokeCap = ui.StrokeCap.round
..strokeJoin = ui.StrokeJoin.round
..filter = '';
final String cssColor = colorToCssString(color);
if (pointMode == ui.PointMode.points) {
contextHandle.fillStyle = cssColor;
} else {
contextHandle.strokeStyle = cssColor;
}
_canvasPool.drawPoints(pointMode, points, strokeWidth / 2.0);
}
@override
void endOfPaint() {
assert(_saveCount == 0);
......
......@@ -229,7 +229,8 @@ class _CanvasPool extends _SaveStackTracking {
// This scale makes sure that 1 CSS pixel is translated to the correct
// number of bitmap pixels.
ctx.scale(EngineWindow.browserDevicePixelRatio, EngineWindow.browserDevicePixelRatio);
ctx.scale(EngineWindow.browserDevicePixelRatio,
EngineWindow.browserDevicePixelRatio);
}
void resetTransform() {
......@@ -397,6 +398,38 @@ class _CanvasPool extends _SaveStackTracking {
ctx.stroke();
}
void drawPoints(ui.PointMode pointMode, Float32List points, double radius) {
html.CanvasRenderingContext2D ctx = context;
final int len = points.length;
switch (pointMode) {
case ui.PointMode.points:
for (int i = 0; i < len; i += 2) {
final double x = points[i];
final double y = points[i + 1];
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2.0 * math.pi);
ctx.fill();
}
break;
case ui.PointMode.lines:
ctx.beginPath();
for (int i = 0; i < (len - 2); i += 4) {
ctx.moveTo(points[i], points[i + 1]);
ctx.lineTo(points[i + 2], points[i + 3]);
ctx.stroke();
}
break;
case ui.PointMode.polygon:
ctx.beginPath();
ctx.moveTo(points[0], points[1]);
for (int i = 2; i < len; i += 2) {
ctx.lineTo(points[i], points[i + 1]);
}
ctx.stroke();
break;
}
}
/// 'Runs' the given [path] by applying all of its commands to the canvas.
void _runPath(html.CanvasRenderingContext2D ctx, SurfacePath path) {
ctx.beginPath();
......
......@@ -104,8 +104,8 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking {
..transformOrigin = '0 0 0'
..transform = effectiveTransform;
final String cssColor = paint.color == null ? '#000000'
: colorToCssString(paint.color);
final String cssColor =
paint.color == null ? '#000000' : colorToCssString(paint.color);
if (paint.maskFilter != null) {
style.filter = 'blur(${paint.maskFilter.webOnlySigma}px)';
......@@ -181,6 +181,12 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking {
throw UnimplementedError();
}
@override
void drawPoints(ui.PointMode pointMode, Float32List points,
double strokeWidth, ui.Color color) {
throw UnimplementedError();
}
@override
void endOfPaint() {
// No reuse of elements yet to handle here. Noop.
......
......@@ -69,6 +69,9 @@ abstract class EngineCanvas {
void drawVertices(
ui.Vertices vertices, ui.BlendMode blendMode, SurfacePaintData paint);
void drawPoints(ui.PointMode pointMode, Float32List points,
double strokeWidth, ui.Color color);
/// Extension of Canvas API to mark the end of a stream of painting commands
/// to enable re-use/dispose optimizations.
void endOfPaint();
......
......@@ -234,6 +234,12 @@ class HoudiniCanvas extends EngineCanvas with SaveElementStackTracking {
// TODO(flutter_web): implement.
}
@override
void drawPoints(ui.PointMode pointMode, Float32List points,
double strokeWidth, ui.Color color) {
// TODO(flutter_web): implement.
}
@override
void endOfPaint() {}
}
......
......@@ -253,17 +253,28 @@ class RecordingCanvas {
final ui.RRect scaledOuter = outer.scaleRadii();
final ui.RRect scaledInner = inner.scaleRadii();
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 = _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) {
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 =
_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
}
......@@ -323,7 +334,8 @@ class RecordingCanvas {
_commands.add(PaintDrawImage(image, offset, paint.paintData));
}
void drawImageRect(ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaint paint) {
void drawImageRect(
ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaint paint) {
_hasArbitraryPaint = true;
_didDraw = true;
_paintBounds.grow(dst);
......@@ -358,18 +370,33 @@ class RecordingCanvas {
_commands.add(PaintDrawShadow(path, color, elevation, transparentOccluder));
}
void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode,
SurfacePaint paint) {
void drawVertices(
ui.Vertices vertices, ui.BlendMode blendMode, SurfacePaint paint) {
_hasArbitraryPaint = true;
_didDraw = true;
_growPaintBoundsByPoints(vertices.positions, 0);
_commands.add(PaintVertices(vertices, blendMode, paint.paintData));
}
void drawRawPoints(
ui.PointMode pointMode, Float32List points, ui.Paint paint) {
if (paint.strokeWidth == null) {
return;
}
_hasArbitraryPaint = true;
_didDraw = true;
final Float32List positions = vertices.positions;
assert(positions.length >= 2);
_growPaintBoundsByPoints(points, paint.strokeWidth);
_commands
.add(PaintPoints(pointMode, points, paint.strokeWidth, paint.color));
}
void _growPaintBoundsByPoints(Float32List points, double thickness) {
double minValueX, maxValueX, minValueY, maxValueY;
minValueX = maxValueX = positions[0];
minValueY = maxValueY = positions[1];
for (int i = 2, len = positions.length; i < len; i += 2) {
final double x = positions[i];
final double y = positions[i + 1];
minValueX = maxValueX = points[0];
minValueY = maxValueY = points[1];
for (int i = 2, len = points.length; i < len; i += 2) {
final double x = points[i];
final double y = points[i + 1];
if (x.isNaN || y.isNaN) {
// Follows skia implementation that sets bounds to empty
// and aborts.
......@@ -380,8 +407,9 @@ class RecordingCanvas {
minValueY = math.min(minValueY, y);
maxValueY = math.max(maxValueY, y);
}
_paintBounds.growLTRB(minValueX, minValueY, maxValueX, maxValueY);
_commands.add(PaintVertices(vertices, blendMode, paint.paintData));
final double distance = thickness / 2.0;
_paintBounds.growLTRB(minValueX - distance, minValueY - distance,
maxValueX + distance, maxValueY + distance);
}
int _saveCount = 1;
......@@ -677,7 +705,8 @@ class PaintDrawColor extends PaintCommand {
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
serializedCommands.add(<dynamic>[11, colorToCssString(color), blendMode.index]);
serializedCommands
.add(<dynamic>[11, colorToCssString(color), blendMode.index]);
}
}
......@@ -766,6 +795,33 @@ class PaintVertices extends PaintCommand {
}
}
class PaintPoints extends PaintCommand {
final Float32List points;
final ui.PointMode pointMode;
final double strokeWidth;
final ui.Color color;
PaintPoints(this.pointMode, this.points, this.strokeWidth, this.color);
@override
void apply(EngineCanvas canvas) {
canvas.drawPoints(pointMode, points, strokeWidth, color);
}
@override
String toString() {
if (assertionsEnabled) {
return 'drawPoints($pointMode, $points, $strokeWidth, $color)';
} else {
return super.toString();
}
}
@override
void serializeToCssPaint(List<List<dynamic>> serializedCommands) {
throw UnimplementedError();
}
}
class PaintDrawRect extends PaintCommand {
final ui.Rect rect;
final SurfacePaintData paint;
......@@ -1185,8 +1241,8 @@ abstract class PathCommand {
/// 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]);
ui.Offset((matrix4[0] * x) + (matrix4[4] * y) + matrix4[12],
(matrix4[1] * x) + (matrix4[5] * y) + matrix4[13]);
}
class MoveTo extends PathCommand {
......@@ -1291,16 +1347,29 @@ 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,
_drawArcWithBezier(
x,
y,
radiusX,
radiusY,
rotation,
startAngle,
anticlockwise ? startAngle - endAngle : endAngle - startAngle,
matrix4, bezierPath);
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) {
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;
......@@ -1315,9 +1384,17 @@ class Ellipse extends PathCommand {
}
}
void _drawArcSegment(ui.Path path, double centerX, double centerY,
double radiusX, double radiusY, double rotation, double startAngle,
double sweep, bool startPath, Float64List matrix4) {
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
......@@ -1352,18 +1429,19 @@ class Ellipse extends PathCommand {
}
}
if (rotation == 0.0) {
path.cubicTo(centerX + cpx1, centerY + cpy1,
centerX + cpx2, centerY + cpy2,
endPointX, endPointY);
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);
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);
}
......@@ -1411,8 +1489,8 @@ class QuadraticCurveTo extends PathCommand {
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);
targetPath.quadraticBezierTo(
transformedX1, transformedY1, transformedX2, transformedY2);
}
@override
......@@ -1461,8 +1539,8 @@ class BezierCurveTo extends PathCommand {
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);
targetPath.cubicTo(transformedX1, transformedY1, transformedX2,
transformedY2, transformedX3, transformedY3);
}
@override
......@@ -1507,11 +1585,13 @@ class RectCommand extends PathCommand {
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) {
if (transformedY1 == transformedY2 &&
transformedY3 == transformedY4 &&
transformedX1 == transformedX4 &&
transformedX2 == transformedX3) {
// It is still a rectangle.
targetPath.addRect(ui.Rect.fromLTRB(transformedX1, transformedY1,
transformedX3, transformedY3));
targetPath.addRect(ui.Rect.fromLTRB(
transformedX1, transformedY1, transformedX3, transformedY3));
} else {
targetPath.moveTo(transformedX1, transformedY1);
targetPath.lineTo(transformedX2, transformedY2);
......@@ -1558,7 +1638,7 @@ class RRectCommand extends PathCommand {
targetPath.addPath(roundRectPath, ui.Offset.zero, matrix4: matrix4);
}
@override
@override
String toString() {
if (assertionsEnabled) {
return '$rrect';
......
......@@ -420,3 +420,17 @@ String canonicalizeFontFamily(String fontFamily) {
}
return '"$fontFamily", $_fallbackFontFamily, sans-serif';
}
/// Converts a list of [Offset] to a typed array of floats.
Float32List offsetListToFloat32List(List<ui.Offset> offsetList) {
if (offsetList == null) {
return null;
}
final int length = offsetList.length;
final floatList = Float32List(length * 2);
for (int i = 0, destIndex = 0; i < length; i++, destIndex += 2) {
floatList[destIndex] = offsetList[i].dx;
floatList[destIndex + 1] = offsetList[i].dy;
}
return floatList;
}
......@@ -76,8 +76,9 @@ class Vertices {
_mode = mode,
_colors = _int32ListFromColors(colors),
_indices = indices != null ? Uint16List.fromList(indices) : null,
_positions = _offsetListToInt32List(positions),
_textureCoordinates = _offsetListToInt32List(textureCoordinates) {
_positions = engine.offsetListToFloat32List(positions),
_textureCoordinates =
engine.offsetListToFloat32List(textureCoordinates) {
engine.initWebGl();
}
......@@ -125,19 +126,6 @@ class Vertices {
engine.initWebGl();
}
static Float32List _offsetListToInt32List(List<Offset> offsetList) {
if (offsetList == null) {
return null;
}
final int length = offsetList.length;
final floatList = Float32List(length * 2);
for (int i = 0, destIndex = 0; i < length; i++, destIndex += 2) {
floatList[destIndex] = offsetList[i].dx;
floatList[destIndex + 1] = offsetList[i].dy;
}
return floatList;
}
static Int32List _int32ListFromColors(List<Color> colors) {
Int32List list = Int32List(colors.length);
for (int i = 0, len = colors.length; i < len; i++) {
......@@ -950,7 +938,8 @@ class Canvas {
assert(pointMode != null);
assert(points != null);
assert(paint != null);
throw UnimplementedError();
final Float32List pointList = engine.offsetListToFloat32List(points);
drawRawPoints(pointMode, pointList, paint);
}
/// Draws a sequence of points according to the given [PointMode].
......@@ -969,7 +958,7 @@ class Canvas {
if (points.length % 2 != 0) {
throw ArgumentError('"points" must have an even number of values.');
}
throw UnimplementedError();
_canvas.drawRawPoints(pointMode, points, paint);
}
void drawVertices(Vertices vertices, BlendMode blendMode, Paint paint) {
......
// 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:typed_data';
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, 400, 600);
BitmapCanvas canvas;
setUp(() {
canvas = BitmapCanvas(region);
});
tearDown(() {
canvas.rootElement.remove();
});
test('draws points in all 3 modes', () async {
final double strokeWidth = 2.0;
final Color color = Color(0xFF0000FF);
final Float32List points = offsetListToFloat32List(<Offset>[
Offset(10, 10),
Offset(50, 10),
Offset(70, 70),
Offset(170, 70)
]);
canvas.drawPoints(PointMode.points, points, strokeWidth, color);
final Float32List points2 = offsetListToFloat32List(<Offset>[
Offset(10, 110),
Offset(50, 110),
Offset(70, 170),
Offset(170, 170)
]);
canvas.drawPoints(PointMode.lines, points2, strokeWidth, color);
final Float32List points3 = offsetListToFloat32List(<Offset>[
Offset(10, 210),
Offset(50, 210),
Offset(70, 270),
Offset(170, 270)
]);
canvas.drawPoints(PointMode.polygon, points3, strokeWidth, color);
html.document.body.append(canvas.rootElement);
await matchGoldenFile('canvas_draw_points.png', region: region);
}, timeout: const Timeout(Duration(seconds: 10)));
}
......@@ -229,6 +229,17 @@ class MockEngineCanvas implements EngineCanvas {
});
}
@override
void drawPoints(PointMode pointMode, Float32List points, double strokeWidth,
Color color) {
_called('drawPoints', arguments: <String, dynamic>{
'pointMode': pointMode,
'points': points,
'strokeWidth': strokeWidth,
'color': color,
});
}
@override
void endOfPaint() {
_called('endOfPaint', arguments: <String, dynamic>{});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册