diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index f964b00e45867..7fc0ed2f62415 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -3124,6 +3124,8 @@ ORIGIN: ../../../flutter/impeller/entity/geometry/fill_path_geometry.cc + ../../ ORIGIN: ../../../flutter/impeller/entity/geometry/fill_path_geometry.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/geometry.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/geometry.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/geometry/line_geometry.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/geometry/line_geometry.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/point_field_geometry.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/point_field_geometry.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/geometry/rect_geometry.cc + ../../../flutter/LICENSE @@ -5881,6 +5883,8 @@ FILE: ../../../flutter/impeller/entity/geometry/fill_path_geometry.cc FILE: ../../../flutter/impeller/entity/geometry/fill_path_geometry.h FILE: ../../../flutter/impeller/entity/geometry/geometry.cc FILE: ../../../flutter/impeller/entity/geometry/geometry.h +FILE: ../../../flutter/impeller/entity/geometry/line_geometry.cc +FILE: ../../../flutter/impeller/entity/geometry/line_geometry.h FILE: ../../../flutter/impeller/entity/geometry/point_field_geometry.cc FILE: ../../../flutter/impeller/entity/geometry/point_field_geometry.h FILE: ../../../flutter/impeller/entity/geometry/rect_geometry.cc diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index 3b34d94ce911e..e937cc553418e 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -1978,6 +1978,70 @@ TEST_P(AiksTest, SolidStrokesRenderCorrectly) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } +TEST_P(AiksTest, DrawLinesRenderCorrectly) { + Canvas canvas; + canvas.Scale(GetContentScale()); + Paint paint; + paint.color = Color::Blue(); + paint.stroke_width = 10; + + auto draw = [&canvas](Paint& paint) { + for (auto cap : {Cap::kButt, Cap::kSquare, Cap::kRound}) { + paint.stroke_cap = cap; + Point origin = {100, 100}; + Point p0 = {50, 0}; + Point p1 = {150, 0}; + canvas.DrawLine({150, 100}, {250, 100}, paint); + for (int d = 15; d < 90; d += 15) { + Matrix m = Matrix::MakeRotationZ(Degrees(d)); + canvas.DrawLine(origin + m * p0, origin + m * p1, paint); + } + canvas.DrawLine({100, 150}, {100, 250}, paint); + canvas.DrawCircle({origin}, 35, paint); + + canvas.DrawLine({250, 250}, {250, 250}, paint); + + canvas.Translate({250, 0}); + } + canvas.Translate({-750, 250}); + }; + + std::vector colors = { + Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0}, + Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0}, + Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0}, + Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0}, + Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0}, + Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0}, + Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}}; + std::vector stops = { + 0.0, + (1.0 / 6.0) * 1, + (1.0 / 6.0) * 2, + (1.0 / 6.0) * 3, + (1.0 / 6.0) * 4, + (1.0 / 6.0) * 5, + 1.0, + }; + + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + + draw(paint); + + paint.color_source = ColorSource::MakeRadialGradient( + {100, 100}, 200, std::move(colors), std::move(stops), + Entity::TileMode::kMirror, {}); + draw(paint); + + paint.color_source = ColorSource::MakeImage( + texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, + Matrix::MakeTranslation({-150, 75})); + draw(paint); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + TEST_P(AiksTest, GradientStrokesRenderCorrectly) { // Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2 auto callback = [&](AiksContext& renderer) -> std::optional { diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index a0823f912de98..0bcb16580d2f2 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -226,6 +226,28 @@ bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, return true; } +void Canvas::DrawLine(const Point& p0, const Point& p1, const Paint& paint) { + if (paint.stroke_cap == Cap::kRound) { + auto path = PathBuilder{} + .AddLine((p0), (p1)) + .SetConvexity(Convexity::kConvex) + .TakePath(); + Paint stroke_paint = paint; + stroke_paint.style = Paint::Style::kStroke; + DrawPath(path, stroke_paint); + return; + } + + Entity entity; + entity.SetTransformation(GetCurrentTransformation()); + entity.SetClipDepth(GetClipDepth()); + entity.SetBlendMode(paint.blend_mode); + entity.SetContents(paint.WithFilters(paint.CreateContentsForGeometry( + Geometry::MakeLine(p0, p1, paint.stroke_width, paint.stroke_cap)))); + + GetCurrentPass().AddEntity(entity); +} + void Canvas::DrawRect(Rect rect, const Paint& paint) { if (paint.style == Paint::Style::kStroke) { DrawPath(PathBuilder{}.AddRect(rect).TakePath(), paint); diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h index 61b1be4cc4a7c..1bee70b25f587 100644 --- a/impeller/aiks/canvas.h +++ b/impeller/aiks/canvas.h @@ -103,6 +103,8 @@ class Canvas { void DrawPaint(const Paint& paint); + void DrawLine(const Point& p0, const Point& p1, const Paint& paint); + void DrawRect(Rect rect, const Paint& paint); void DrawRRect(Rect rect, Point corner_radii, const Paint& paint); diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index 2b3e02f513515..0d80da4b9ec3b 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -759,14 +759,8 @@ void DlDispatcher::drawPaint() { // |flutter::DlOpReceiver| void DlDispatcher::drawLine(const SkPoint& p0, const SkPoint& p1) { - auto path = - PathBuilder{} - .AddLine(skia_conversions::ToPoint(p0), skia_conversions::ToPoint(p1)) - .SetConvexity(Convexity::kConvex) - .TakePath(); - Paint paint = paint_; - paint.style = Paint::Style::kStroke; - canvas_.DrawPath(path, paint); + canvas_.DrawLine(skia_conversions::ToPoint(p0), skia_conversions::ToPoint(p1), + paint_); } // |flutter::DlOpReceiver| @@ -879,8 +873,7 @@ void DlDispatcher::drawPoints(PointMode mode, for (uint32_t i = 1; i < count; i += 2) { Point p0 = skia_conversions::ToPoint(points[i - 1]); Point p1 = skia_conversions::ToPoint(points[i]); - auto path = PathBuilder{}.AddLine(p0, p1).TakePath(); - canvas_.DrawPath(path, paint); + canvas_.DrawLine(p0, p1, paint); } break; case flutter::DlCanvas::PointMode::kPolygon: @@ -888,8 +881,7 @@ void DlDispatcher::drawPoints(PointMode mode, Point p0 = skia_conversions::ToPoint(points[0]); for (uint32_t i = 1; i < count; i++) { Point p1 = skia_conversions::ToPoint(points[i]); - auto path = PathBuilder{}.AddLine(p0, p1).TakePath(); - canvas_.DrawPath(path, paint); + canvas_.DrawLine(p0, p1, paint); p0 = p1; } } diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 2460a19178513..89d3071050b43 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -194,6 +194,8 @@ impeller_component("entity") { "geometry/fill_path_geometry.h", "geometry/geometry.cc", "geometry/geometry.h", + "geometry/line_geometry.cc", + "geometry/line_geometry.h", "geometry/point_field_geometry.cc", "geometry/point_field_geometry.h", "geometry/rect_geometry.cc", diff --git a/impeller/entity/geometry/geometry.cc b/impeller/entity/geometry/geometry.cc index 29d880a86c86b..d472ad5fb6d7a 100644 --- a/impeller/entity/geometry/geometry.cc +++ b/impeller/entity/geometry/geometry.cc @@ -8,6 +8,7 @@ #include "impeller/entity/geometry/cover_geometry.h" #include "impeller/entity/geometry/fill_path_geometry.h" +#include "impeller/entity/geometry/line_geometry.h" #include "impeller/entity/geometry/point_field_geometry.h" #include "impeller/entity/geometry/rect_geometry.h" #include "impeller/entity/geometry/stroke_path_geometry.h" @@ -144,6 +145,13 @@ std::unique_ptr Geometry::MakeRect(Rect rect) { return std::make_unique(rect); } +std::unique_ptr Geometry::MakeLine(Point p0, + Point p1, + Scalar width, + Cap cap) { + return std::make_unique(p0, p1, width, cap); +} + bool Geometry::CoversArea(const Matrix& transform, const Rect& rect) const { return false; } diff --git a/impeller/entity/geometry/geometry.h b/impeller/entity/geometry/geometry.h index c0743626953ae..5208d69bd9559 100644 --- a/impeller/entity/geometry/geometry.h +++ b/impeller/entity/geometry/geometry.h @@ -72,6 +72,11 @@ class Geometry { static std::unique_ptr MakeRect(Rect rect); + static std::unique_ptr MakeLine(Point p0, + Point p1, + Scalar width, + Cap cap); + static std::unique_ptr MakePointField(std::vector points, Scalar radius, bool round); diff --git a/impeller/entity/geometry/geometry_unittests.cc b/impeller/entity/geometry/geometry_unittests.cc index 55f958ac9eea2..cb892c1062ac8 100644 --- a/impeller/entity/geometry/geometry_unittests.cc +++ b/impeller/entity/geometry/geometry_unittests.cc @@ -36,5 +36,31 @@ TEST(EntityGeometryTest, FillPathGeometryCoversAreaNoInnerRect) { ASSERT_FALSE(geometry->CoversArea({}, Rect())); } +TEST(EntityGeometryTest, LineGeometryCoverage) { + { + auto geometry = Geometry::MakeLine({10, 10}, {20, 10}, 2, Cap::kButt); + EXPECT_EQ(geometry->GetCoverage({}), Rect::MakeLTRB(10, 9, 20, 11)); + EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(10, 9, 20, 11))); + } + + { + auto geometry = Geometry::MakeLine({10, 10}, {20, 10}, 2, Cap::kSquare); + EXPECT_EQ(geometry->GetCoverage({}), Rect::MakeLTRB(9, 9, 21, 11)); + EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(9, 9, 21, 11))); + } + + { + auto geometry = Geometry::MakeLine({10, 10}, {10, 20}, 2, Cap::kButt); + EXPECT_EQ(geometry->GetCoverage({}), Rect::MakeLTRB(9, 10, 11, 20)); + EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(9, 10, 11, 20))); + } + + { + auto geometry = Geometry::MakeLine({10, 10}, {10, 20}, 2, Cap::kSquare); + EXPECT_EQ(geometry->GetCoverage({}), Rect::MakeLTRB(9, 9, 11, 21)); + EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(9, 9, 11, 21))); + } +} + } // namespace testing } // namespace impeller diff --git a/impeller/entity/geometry/line_geometry.cc b/impeller/entity/geometry/line_geometry.cc new file mode 100644 index 0000000000000..c7984eb7aab7a --- /dev/null +++ b/impeller/entity/geometry/line_geometry.cc @@ -0,0 +1,147 @@ +// 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. + +#include "impeller/entity/geometry/line_geometry.h" + +namespace impeller { + +LineGeometry::LineGeometry(Point p0, Point p1, Scalar width, Cap cap) + : p0_(p0), p1_(p1), width_(width), cap_(cap) { + // Some of the code below is prepared to deal with things like coverage + // of a line with round caps, but more work is needed to deal with drawing + // the round end caps + FML_DCHECK(width >= 0); + FML_DCHECK(cap != Cap::kRound); +} + +LineGeometry::~LineGeometry() = default; + +bool LineGeometry::ComputeCorners(Point corners[4], + const Matrix& transform, + bool extend_endpoints) const { + auto determinant = transform.GetDeterminant(); + if (determinant == 0) { + return false; + } + + Scalar min_size = 1.0f / sqrt(std::abs(determinant)); + Scalar stroke_half_width = std::max(width_, min_size) * 0.5f; + + Point along = p1_ - p0_; + Scalar length = along.GetLength(); + if (length < kEhCloseEnough) { + if (!extend_endpoints) { + // We won't enclose any pixels unless the endpoints are extended + return false; + } + along = {stroke_half_width, 0}; + } else { + along *= stroke_half_width / length; + } + Point across = {along.y, -along.x}; + corners[0] = p0_ - across; + corners[1] = p1_ - across; + corners[2] = p0_ + across; + corners[3] = p1_ + across; + if (extend_endpoints) { + corners[0] -= along; + corners[1] += along; + corners[2] -= along; + corners[3] += along; + } + return true; +} + +GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { + auto& host_buffer = pass.GetTransientsBuffer(); + + Point corners[4]; + if (!ComputeCorners(corners, entity.GetTransformation(), + cap_ == Cap::kSquare)) { + return {}; + } + + return GeometryResult{ + .type = PrimitiveType::kTriangleStrip, + .vertex_buffer = + { + .vertex_buffer = host_buffer.Emplace(corners, 8 * sizeof(float), + alignof(float)), + .vertex_count = 4, + .index_type = IndexType::kNone, + }, + .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(), + .prevent_overdraw = false, + }; +} + +// |Geometry| +GeometryResult LineGeometry::GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { + auto& host_buffer = pass.GetTransientsBuffer(); + + auto uv_transform = + texture_coverage.GetNormalizingTransform() * effect_transform; + Point corners[4]; + if (!ComputeCorners(corners, entity.GetTransformation(), + cap_ == Cap::kSquare)) { + return {}; + } + + std::vector data(8); + for (auto i = 0u, j = 0u; i < 8; i += 2, j++) { + data[i] = corners[j]; + data[i + 1] = uv_transform * corners[j]; + } + + return GeometryResult{ + .type = PrimitiveType::kTriangleStrip, + .vertex_buffer = + { + .vertex_buffer = host_buffer.Emplace( + data.data(), 16 * sizeof(float), alignof(float)), + .vertex_count = 4, + .index_type = IndexType::kNone, + }, + .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(), + .prevent_overdraw = false, + }; +} + +GeometryVertexType LineGeometry::GetVertexType() const { + return GeometryVertexType::kPosition; +} + +std::optional LineGeometry::GetCoverage(const Matrix& transform) const { + Point corners[4]; + if (!ComputeCorners(corners, transform, cap_ != Cap::kButt)) { + return {}; + } + + for (int i = 0; i < 4; i++) { + corners[i] = transform * corners[i]; + } + return Rect::MakePointBounds(std::begin(corners), std::end(corners)); +} + +bool LineGeometry::CoversArea(const Matrix& transform, const Rect& rect) const { + if (!transform.IsTranslationScaleOnly() || !IsAxisAlignedRect()) { + return false; + } + auto coverage = GetCoverage(transform); + return coverage.has_value() ? coverage->Contains(rect) : false; +} + +bool LineGeometry::IsAxisAlignedRect() const { + return p0_.x == p1_.x || p0_.y == p1_.y; +} + +} // namespace impeller diff --git a/impeller/entity/geometry/line_geometry.h b/impeller/entity/geometry/line_geometry.h new file mode 100644 index 0000000000000..b7e99b428e03d --- /dev/null +++ b/impeller/entity/geometry/line_geometry.h @@ -0,0 +1,70 @@ +// 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. + +#pragma once + +#include "impeller/entity/geometry/geometry.h" + +namespace impeller { + +class LineGeometry : public Geometry { + public: + explicit LineGeometry(Point p0, Point p1, Scalar width, Cap cap); + + ~LineGeometry(); + + // |Geometry| + bool CoversArea(const Matrix& transform, const Rect& rect) const override; + + // |Geometry| + bool IsAxisAlignedRect() const override; + + private: + // Computes the 4 corners of a rectangle that defines the line and + // possibly extended endpoints which will be rendered under the given + // transform, and returns true if such a rectangle is defined. + // + // The coordinates will be generated in the original coordinate system + // of the line end points and the transform will only be used to determine + // the minimum line width. + // + // For kButt and kSquare end caps the ends should always be exteded as + // per that decoration, but for kRound caps the ends might be extended + // if the goal is to get a conservative bounds and might not be extended + // if the calling code is planning to draw the round caps on the ends. + // + // @return true if the transform and width were not degenerate + bool ComputeCorners(Point corners[4], + const Matrix& transform, + bool extend_endpoints) const; + + // |Geometry| + GeometryResult GetPositionBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) override; + + // |Geometry| + GeometryVertexType GetVertexType() const override; + + // |Geometry| + std::optional GetCoverage(const Matrix& transform) const override; + + // |Geometry| + GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) override; + + Point p0_; + Point p1_; + Scalar width_; + Cap cap_; + + LineGeometry(const LineGeometry&) = delete; + + LineGeometry& operator=(const LineGeometry&) = delete; +}; + +} // namespace impeller