Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,16 @@ TEST_P(AiksTest, CanRenderCurvedStrokes) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, CanRenderThickCurvedStrokes) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I have some advice? Not entirely sure why this produce clipped golden
image
locally it runs fine

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is just due to the max size of golden images. So if you draw the donut a bit smaller it should be able to capture the whole thing.

Canvas canvas;
Paint paint;
paint.color = Color::Red();
paint.stroke_width = 100.0;
paint.style = Paint::Style::kStroke;
canvas.DrawPath(PathBuilder{}.AddCircle({100, 100}, 50).TakePath(), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, CanRenderClips) {
Canvas canvas;
Paint paint;
Expand Down Expand Up @@ -1233,6 +1243,37 @@ TEST_P(AiksTest, CanRenderRoundedRectWithNonUniformRadii) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, CanRenderStrokePathThatEndsAtSharpTurn) {
Canvas canvas;

Paint paint;
paint.color = Color::Red();
paint.style = Paint::Style::kStroke;
paint.stroke_width = 200;

Rect rect = {100, 100, 200, 200};
PathBuilder builder;
builder.AddArc(rect, Degrees(0), Degrees(90), false);

canvas.DrawPath(builder.TakePath(), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, CanRenderStrokePathWithCubicLine) {
Canvas canvas;

Paint paint;
paint.color = Color::Red();
paint.style = Paint::Style::kStroke;
paint.stroke_width = 20;

PathBuilder builder;
builder.AddCubicCurve({0, 200}, {50, 400}, {350, 0}, {400, 200});

canvas.DrawPath(builder.TakePath(), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, CanRenderDifferencePaths) {
Canvas canvas;

Expand Down Expand Up @@ -2064,6 +2105,22 @@ TEST_P(AiksTest, DrawRectStrokesRenderCorrectly) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, DrawRectStrokesWithBevelJoinRenderCorrectly) {
Canvas canvas;
Paint paint;
paint.color = Color::Red();
paint.style = Paint::Style::kStroke;
paint.stroke_width = 10;
paint.stroke_join = Join::kBevel;

canvas.Translate({100, 100});
canvas.DrawPath(
PathBuilder{}.AddRect(Rect::MakeSize(Size{100, 100})).TakePath(),
{paint});

ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, SaveLayerDrawsBehindSubsequentEntities) {
// Compare with https://fiddle.skia.org/c/9e03de8567ffb49e7e83f53b64bcf636
Canvas canvas;
Expand Down
22 changes: 22 additions & 0 deletions impeller/core/formats.h
Original file line number Diff line number Diff line change
Expand Up @@ -325,11 +325,33 @@ enum class IndexType {
kNone,
};

/// Decides how backend draws pixels based on input vertices.
enum class PrimitiveType {
/// Draws a triage for each separate set of three vertices.
///
/// Vertices [A, B, C, D, E, F] will produce triages
/// [ABC, DEF].
kTriangle,

/// Draws a triage for every adjacent three vertices.
///
/// Vertices [A, B, C, D, E, F] will produce triages
/// [ABC, BCD, CDE, DEF].
kTriangleStrip,

/// Draws a line for each separate set of two vertices.
///
/// Vertices [A, B, C] will produce discontinued line
/// [AB, BC].
kLine,

/// Draws a continuous line that connect every input vertices
///
/// Vertices [A, B, C] will produce one continuous line
/// [ABC].
kLineStrip,

/// Draws a point at each input vertex.
kPoint,
// Triangle fans are implementation dependent and need extra extensions
// checks. Hence, they are not supported here.
Expand Down
138 changes: 112 additions & 26 deletions impeller/entity/geometry/stroke_path_geometry.cc
Original file line number Diff line number Diff line change
Expand Up @@ -242,14 +242,99 @@ StrokePathGeometry::CreateSolidStrokeVertices(
Point offset;
Point previous_offset; // Used for computing joins.

auto compute_offset = [&polyline, &offset, &previous_offset,
&stroke_width](size_t point_i) {
// Computes offset by calculating the direction from point_i - 1 to point_i if
// point_i is within `contour_start_point_i` and `contour_end_point_i`;
// Otherwise, it uses direction from contour.
auto compute_offset = [&polyline, &offset, &previous_offset, &stroke_width](
const size_t point_i,
const size_t contour_start_point_i,
const size_t contour_end_point_i,
const Path::PolylineContour& contour) {
Point direction;
if (point_i >= contour_end_point_i) {
direction = contour.end_direction;
} else if (point_i <= contour_start_point_i) {
direction = -contour.start_direction;
} else {
direction =
(polyline.points[point_i] - polyline.points[point_i - 1]).Normalize();
}
previous_offset = offset;
Point direction =
(polyline.points[point_i] - polyline.points[point_i - 1]).Normalize();
offset = Vector2{-direction.y, direction.x} * stroke_width * 0.5;
};

auto add_vertices_for_linear_component =
[&vtx_builder, &offset, &previous_offset, &vtx, &polyline,
&compute_offset, scaled_miter_limit, scale, &join_proc](
const size_t component_start_index, const size_t component_end_index,
const size_t contour_start_point_i, const size_t contour_end_point_i,
const Path::PolylineContour& contour) {
auto is_last_component =
component_start_index ==
contour.components.back().component_start_index;

for (size_t point_i = component_start_index;
point_i < component_end_index; point_i++) {
auto is_end_of_component = point_i == component_end_index - 1;
vtx.position = polyline.points[point_i] + offset;
vtx_builder.AppendVertex(vtx);
vtx.position = polyline.points[point_i] - offset;
vtx_builder.AppendVertex(vtx);

// For line components, two additional points need to be appended
// prior to appending a join connecting the next component.
vtx.position = polyline.points[point_i + 1] + offset;
vtx_builder.AppendVertex(vtx);
vtx.position = polyline.points[point_i + 1] - offset;
vtx_builder.AppendVertex(vtx);

compute_offset(point_i + 2, contour_start_point_i,
contour_end_point_i, contour);
if (!is_last_component && is_end_of_component) {
// Generate join from the current line to the next line.
join_proc(vtx_builder, polyline.points[point_i + 1],
previous_offset, offset, scaled_miter_limit, scale);
}
}
};

auto add_vertices_for_curve_component =
[&vtx_builder, &offset, &previous_offset, &vtx, &polyline,
&compute_offset, scaled_miter_limit, scale, &join_proc](
const size_t component_start_index, const size_t component_end_index,
const size_t contour_start_point_i, const size_t contour_end_point_i,
const Path::PolylineContour& contour) {
auto is_last_component =
component_start_index ==
contour.components.back().component_start_index;

for (size_t point_i = component_start_index;
point_i < component_end_index; point_i++) {
auto is_end_of_component = point_i == component_end_index - 1;

vtx.position = polyline.points[point_i] + offset;
vtx_builder.AppendVertex(vtx);
vtx.position = polyline.points[point_i] - offset;
vtx_builder.AppendVertex(vtx);

compute_offset(point_i + 2, contour_start_point_i,
contour_end_point_i, contour);
// For curve components, the polyline is detailed enough such that
// it can avoid worrying about joins altogether.
if (is_end_of_component) {
vtx.position = polyline.points[point_i + 1] + offset;
vtx_builder.AppendVertex(vtx);
vtx.position = polyline.points[point_i + 1] - offset;
vtx_builder.AppendVertex(vtx);
// Generate join from the current line to the next line.
if (!is_last_component) {
join_proc(vtx_builder, polyline.points[point_i + 1],
previous_offset, offset, scaled_miter_limit, scale);
}
}
}
};

for (size_t contour_i = 0; contour_i < polyline.contours.size();
contour_i++) {
auto contour = polyline.contours[contour_i];
Expand All @@ -270,8 +355,8 @@ StrokePathGeometry::CreateSolidStrokeVertices(
break;
}

// The first point's offset is always the same as the second point.
compute_offset(contour_start_point_i + 1);
compute_offset(contour_start_point_i, contour_start_point_i,
contour_end_point_i, contour);
const Point contour_first_offset = offset;

if (contour_i > 0) {
Expand Down Expand Up @@ -305,30 +390,31 @@ StrokePathGeometry::CreateSolidStrokeVertices(
scale, true);
}

// Generate contour geometry.
for (size_t point_i = contour_start_point_i + 1;
point_i < contour_end_point_i; point_i++) {
// Generate line rect.
vtx.position = polyline.points[point_i - 1] + offset;
vtx_builder.AppendVertex(vtx);
vtx.position = polyline.points[point_i - 1] - offset;
vtx_builder.AppendVertex(vtx);
vtx.position = polyline.points[point_i] + offset;
vtx_builder.AppendVertex(vtx);
vtx.position = polyline.points[point_i] - offset;
vtx_builder.AppendVertex(vtx);

if (point_i < contour_end_point_i - 1) {
compute_offset(point_i + 1);

// Generate join from the current line to the next line.
join_proc(vtx_builder, polyline.points[point_i], previous_offset,
offset, scaled_miter_limit, scale);
for (size_t contour_component_i = 0;
contour_component_i < contour.components.size();
contour_component_i++) {
auto component = contour.components[contour_component_i];
auto is_last_component =
contour_component_i == contour.components.size() - 1;

auto component_start_index = component.component_start_index;
auto component_end_index =
is_last_component ? contour_end_point_i - 1
: contour.components[contour_component_i + 1]
.component_start_index;
if (component.is_curve) {
add_vertices_for_curve_component(
component_start_index, component_end_index, contour_start_point_i,
contour_end_point_i, contour);
} else {
add_vertices_for_linear_component(
component_start_index, component_end_index, contour_start_point_i,
contour_end_point_i, contour);
}
}

// Generate end cap or join.
if (!polyline.contours[contour_i].is_closed) {
if (!contour.is_closed) {
auto cap_offset =
Vector2(-contour.end_direction.y, contour.end_direction.x) *
stroke_width * 0.5; // Clockwise normal
Expand Down
22 changes: 19 additions & 3 deletions impeller/geometry/path.cc
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,10 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const {
return Vector2(0, -1);
};

std::vector<PolylineContour::Component> components;
std::optional<size_t> previous_path_component_index;
auto end_contour = [&polyline, &previous_path_component_index,
&get_path_component]() {
&get_path_component, &components]() {
// Whenever a contour has ended, extract the exact end direction from the
// last component.
if (polyline.contours.empty()) {
Expand All @@ -339,6 +340,8 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const {

auto& contour = polyline.contours.back();
contour.end_direction = Vector2(0, 1);
contour.components = components;
components.clear();

size_t previous_index = previous_path_component_index.value();
while (!std::holds_alternative<std::monostate>(
Expand All @@ -363,14 +366,26 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const {
const auto& component = components_[component_i];
switch (component.type) {
case ComponentType::kLinear:
components.push_back({
.component_start_index = polyline.points.size() - 1,
.is_curve = false,
});
collect_points(linears_[component.index].CreatePolyline());
previous_path_component_index = component_i;
break;
case ComponentType::kQuadratic:
components.push_back({
.component_start_index = polyline.points.size() - 1,
.is_curve = true,
});
collect_points(quads_[component.index].CreatePolyline(scale));
previous_path_component_index = component_i;
break;
case ComponentType::kCubic:
components.push_back({
.component_start_index = polyline.points.size() - 1,
.is_curve = true,
});
collect_points(cubics_[component.index].CreatePolyline(scale));
previous_path_component_index = component_i;
break;
Expand All @@ -386,13 +401,14 @@ Path::Polyline Path::CreatePolyline(Scalar scale) const {
const auto& contour = contours_[component.index];
polyline.contours.push_back({.start_index = polyline.points.size(),
.is_closed = contour.is_closed,
.start_direction = start_direction});
.start_direction = start_direction,
.components = components});
previous_contour_point = std::nullopt;
collect_points({contour.destination});
break;
}
end_contour();
}
end_contour();
return polyline;
}

Expand Down
15 changes: 15 additions & 0 deletions impeller/geometry/path.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,17 @@ class Path {
};

struct PolylineContour {
struct Component {
size_t component_start_index;
/// Denotes whether this component is a curve.
///
/// This is set to true when this component is generated from
/// QuadraticComponent or CubicPathComponent.
bool is_curve;
};
/// Index that denotes the first point of this contour.
size_t start_index;

/// Denotes whether the last point of this contour is connected to the first
/// point of this contour or not.
bool is_closed;
Expand All @@ -71,6 +80,12 @@ class Path {
Vector2 start_direction;
/// The direction of the contour's end cap.
Vector2 end_direction;

/// Distinct components in this contour.
///
/// If this contour is generated from multiple path components, each
/// path component forms a component in this vector.
std::vector<Component> components;
};

/// One or more contours represented as a series of points and indices in
Expand Down
6 changes: 3 additions & 3 deletions impeller/geometry/path_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,9 @@ PathBuilder& PathBuilder::AddRect(Rect rect) {
auto tr = rect.origin + Point{rect.size.width, 0.0};

MoveTo(tl);
prototype_.AddLinearComponent(tl, tr)
.AddLinearComponent(tr, br)
.AddLinearComponent(br, bl);
LineTo(tr);
LineTo(br);
LineTo(bl);
Close();

return *this;
Expand Down
Loading