diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index e82bd545692e0..f813e1b9bdcde 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: 30ef2668489dab3191a5df251330aedb9b0c239a +revision: 3db2bb2329e7277c34389a92507eacaab774c8e8 diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 50ef094b68226..c7bd0d19b5360 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -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); diff --git a/lib/web_ui/lib/src/engine/canvas_pool.dart b/lib/web_ui/lib/src/engine/canvas_pool.dart index 77c3d35384786..228b626362461 100644 --- a/lib/web_ui/lib/src/engine/canvas_pool.dart +++ b/lib/web_ui/lib/src/engine/canvas_pool.dart @@ -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(); diff --git a/lib/web_ui/lib/src/engine/dom_canvas.dart b/lib/web_ui/lib/src/engine/dom_canvas.dart index 78eb02cec2cca..1382c623db6eb 100644 --- a/lib/web_ui/lib/src/engine/dom_canvas.dart +++ b/lib/web_ui/lib/src/engine/dom_canvas.dart @@ -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. diff --git a/lib/web_ui/lib/src/engine/engine_canvas.dart b/lib/web_ui/lib/src/engine/engine_canvas.dart index 169a17c521134..7a5185f07635c 100644 --- a/lib/web_ui/lib/src/engine/engine_canvas.dart +++ b/lib/web_ui/lib/src/engine/engine_canvas.dart @@ -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(); diff --git a/lib/web_ui/lib/src/engine/houdini_canvas.dart b/lib/web_ui/lib/src/engine/houdini_canvas.dart index 26e7e07da44dc..2bb41da5d9da9 100644 --- a/lib/web_ui/lib/src/engine/houdini_canvas.dart +++ b/lib/web_ui/lib/src/engine/houdini_canvas.dart @@ -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() {} } diff --git a/lib/web_ui/lib/src/engine/surface/recording_canvas.dart b/lib/web_ui/lib/src/engine/surface/recording_canvas.dart index c65df17eade58..00b3fdae2b2a5 100644 --- a/lib/web_ui/lib/src/engine/surface/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/surface/recording_canvas.dart @@ -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> serializedCommands) { - serializedCommands.add([11, colorToCssString(color), blendMode.index]); + serializedCommands + .add([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> 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'; diff --git a/lib/web_ui/lib/src/engine/util.dart b/lib/web_ui/lib/src/engine/util.dart index e823ae406b2ae..52356d05fbadb 100644 --- a/lib/web_ui/lib/src/engine/util.dart +++ b/lib/web_ui/lib/src/engine/util.dart @@ -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 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; +} diff --git a/lib/web_ui/lib/src/ui/canvas.dart b/lib/web_ui/lib/src/ui/canvas.dart index 53e0f2eb77a7d..df63a6d387dbe 100644 --- a/lib/web_ui/lib/src/ui/canvas.dart +++ b/lib/web_ui/lib/src/ui/canvas.dart @@ -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 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 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) { diff --git a/lib/web_ui/test/golden_tests/engine/canvas_draw_points_test.dart b/lib/web_ui/test/golden_tests/engine/canvas_draw_points_test.dart new file mode 100644 index 0000000000000..80355661ea6ac --- /dev/null +++ b/lib/web_ui/test/golden_tests/engine/canvas_draw_points_test.dart @@ -0,0 +1,56 @@ +// 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(10, 10), + Offset(50, 10), + Offset(70, 70), + Offset(170, 70) + ]); + canvas.drawPoints(PointMode.points, points, strokeWidth, color); + final Float32List points2 = offsetListToFloat32List([ + Offset(10, 110), + Offset(50, 110), + Offset(70, 170), + Offset(170, 170) + ]); + canvas.drawPoints(PointMode.lines, points2, strokeWidth, color); + final Float32List points3 = offsetListToFloat32List([ + 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))); +} diff --git a/lib/web_ui/test/mock_engine_canvas.dart b/lib/web_ui/test/mock_engine_canvas.dart index 4513a861d1912..a771080e16683 100644 --- a/lib/web_ui/test/mock_engine_canvas.dart +++ b/lib/web_ui/test/mock_engine_canvas.dart @@ -229,6 +229,17 @@ class MockEngineCanvas implements EngineCanvas { }); } + @override + void drawPoints(PointMode pointMode, Float32List points, double strokeWidth, + Color color) { + _called('drawPoints', arguments: { + 'pointMode': pointMode, + 'points': points, + 'strokeWidth': strokeWidth, + 'color': color, + }); + } + @override void endOfPaint() { _called('endOfPaint', arguments: {});