[Impeller] move Aiks path unittests to DL (flutter/engine#53799)

Move more impeller tests to display list format. This allows our integration tests to also test the display list -> aiks interop and be future compatible with the new canvas backend that removes AIKS.

Part of https://github.com/flutter/flutter/issues/142054
This commit is contained in:
Jonah Williams 2024-07-12 16:50:05 -07:00 committed by GitHub
parent e2b3f88cac
commit 50c6b6f326
7 changed files with 479 additions and 464 deletions

View File

@ -127,7 +127,6 @@
../../../flutter/impeller/aiks/aiks_blend_unittests.cc
../../../flutter/impeller/aiks/aiks_blur_unittests.cc
../../../flutter/impeller/aiks/aiks_gradient_unittests.cc
../../../flutter/impeller/aiks/aiks_path_unittests.cc
../../../flutter/impeller/aiks/aiks_unittests.cc
../../../flutter/impeller/aiks/aiks_unittests.h
../../../flutter/impeller/aiks/canvas_unittests.cc

View File

@ -14,6 +14,15 @@ struct DlColor {
constexpr DlColor() : argb_(0xFF000000) {}
constexpr explicit DlColor(uint32_t argb) : argb_(argb) {}
/// @brief Construct a 32 bit color from a floating point A, R, G, and B color
/// channel.
constexpr explicit DlColor(SkScalar a, SkScalar r, SkScalar g, SkScalar b)
: argb_(static_cast<uint8_t>(std::round(a * 255)) << 24 | //
static_cast<uint8_t>(std::round(r * 255)) << 16 | //
static_cast<uint8_t>(std::round(g * 255)) << 8 | //
static_cast<uint8_t>(std::round(b * 255)) << 0 //
) {}
static constexpr uint8_t toAlpha(SkScalar opacity) { return toC(opacity); }
static constexpr SkScalar toOpacity(uint8_t alpha) { return toF(alpha); }

View File

@ -78,7 +78,6 @@ template("aiks_unittests_component") {
"aiks_blend_unittests.cc",
"aiks_blur_unittests.cc",
"aiks_gradient_unittests.cc",
"aiks_path_unittests.cc",
"aiks_unittests.cc",
"aiks_unittests.h",
]

View File

@ -1,460 +0,0 @@
// 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 "flutter/impeller/aiks/aiks_unittests.h"
#include "impeller/aiks/canvas.h"
#include "impeller/geometry/path_builder.h"
#include "impeller/playground/widgets.h"
#include "third_party/imgui/imgui.h"
////////////////////////////////////////////////////////////////////////////////
// This is for tests of Canvas that are interested the results of rendering
// paths.
////////////////////////////////////////////////////////////////////////////////
namespace impeller {
namespace testing {
TEST_P(AiksTest, CanRenderStrokes) {
Canvas canvas;
Paint paint;
paint.color = Color::Red();
paint.stroke_width = 20.0;
paint.style = Paint::Style::kStroke;
canvas.DrawPath(PathBuilder{}.AddLine({200, 100}, {800, 100}).TakePath(),
paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, CanRenderCurvedStrokes) {
Canvas canvas;
Paint paint;
paint.color = Color::Red();
paint.stroke_width = 25.0;
paint.style = Paint::Style::kStroke;
canvas.DrawPath(PathBuilder{}.AddCircle({500, 500}, 250).TakePath(), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, CanRenderThickCurvedStrokes) {
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, CanRenderThinCurvedStrokes) {
Canvas canvas;
Paint paint;
paint.color = Color::Red();
// Impeller doesn't support hairlines yet, but size this guarantees
// the smallest possible stroke width.
paint.stroke_width = 0.01;
paint.style = Paint::Style::kStroke;
canvas.DrawPath(PathBuilder{}.AddCircle({100, 100}, 50).TakePath(), paint);
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 = Rect::MakeXYWH(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, CanRenderQuadraticStrokeWithInstantTurn) {
Canvas canvas;
Paint paint;
paint.color = Color::Red();
paint.style = Paint::Style::kStroke;
paint.stroke_cap = Cap::kRound;
paint.stroke_width = 50;
// Should draw a diagonal pill shape. If flat on either end, the stroke is
// rendering wrong.
PathBuilder builder;
builder.MoveTo({250, 250});
builder.QuadraticCurveTo({100, 100}, {250, 250});
canvas.DrawPath(builder.TakePath(), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, CanRenderDifferencePaths) {
Canvas canvas;
Paint paint;
paint.color = Color::Red();
PathBuilder builder;
PathBuilder::RoundingRadii radii;
radii.top_left = {50, 25};
radii.top_right = {25, 50};
radii.bottom_right = {50, 25};
radii.bottom_left = {25, 50};
builder.AddRoundedRect(Rect::MakeXYWH(100, 100, 200, 200), radii);
builder.AddCircle({200, 200}, 50);
auto path = builder.TakePath(FillType::kOdd);
canvas.DrawImage(
std::make_shared<Image>(CreateTextureForFixture("boston.jpg")), {10, 10},
Paint{});
canvas.DrawPath(path, paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
// Regression test for https://github.com/flutter/flutter/issues/134816.
//
// It should be possible to draw 3 lines, and not have an implicit close path.
TEST_P(AiksTest, CanDrawAnOpenPath) {
Canvas canvas;
// Starting at (50, 50), draw lines from:
// 1. (50, height)
// 2. (width, height)
// 3. (width, 50)
PathBuilder builder;
builder.MoveTo({50, 50});
builder.LineTo({50, 100});
builder.LineTo({100, 100});
builder.LineTo({100, 50});
Paint paint;
paint.color = Color::Red();
paint.style = Paint::Style::kStroke;
paint.stroke_width = 10;
canvas.DrawPath(builder.TakePath(), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, CanDrawAnOpenPathThatIsntARect) {
Canvas canvas;
// Draw a stroked path that is explicitly closed to verify
// It doesn't become a rectangle.
PathBuilder builder;
builder.MoveTo({50, 50});
builder.LineTo({520, 120});
builder.LineTo({300, 310});
builder.LineTo({100, 50});
builder.Close();
Paint paint;
paint.color = Color::Red();
paint.style = Paint::Style::kStroke;
paint.stroke_width = 10;
canvas.DrawPath(builder.TakePath(), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, SolidStrokesRenderCorrectly) {
// Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
static Color color = Color::Black().WithAlpha(0.5);
static float scale = 3;
static bool add_circle_clip = true;
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::ColorEdit4("Color", reinterpret_cast<float*>(&color));
ImGui::SliderFloat("Scale", &scale, 0, 6);
ImGui::Checkbox("Circle clip", &add_circle_clip);
ImGui::End();
}
Canvas canvas;
canvas.Scale(GetContentScale());
Paint paint;
paint.color = Color::White();
canvas.DrawPaint(paint);
paint.color = color;
paint.style = Paint::Style::kStroke;
paint.stroke_width = 10;
Path path = PathBuilder{}
.MoveTo({20, 20})
.QuadraticCurveTo({60, 20}, {60, 60})
.Close()
.MoveTo({60, 20})
.QuadraticCurveTo({60, 60}, {20, 60})
.TakePath();
canvas.Scale(Vector2(scale, scale));
if (add_circle_clip) {
static PlaygroundPoint circle_clip_point_a(Point(60, 300), 20,
Color::Red());
static PlaygroundPoint circle_clip_point_b(Point(600, 300), 20,
Color::Red());
auto [handle_a, handle_b] =
DrawPlaygroundLine(circle_clip_point_a, circle_clip_point_b);
auto screen_to_canvas = canvas.GetCurrentTransform().Invert();
Point point_a = screen_to_canvas * handle_a * GetContentScale();
Point point_b = screen_to_canvas * handle_b * GetContentScale();
Point middle = (point_a + point_b) / 2;
auto radius = point_a.GetDistance(middle);
canvas.ClipPath(PathBuilder{}.AddCircle(middle, radius).TakePath());
}
for (auto join : {Join::kBevel, Join::kRound, Join::kMiter}) {
paint.stroke_join = join;
for (auto cap : {Cap::kButt, Cap::kSquare, Cap::kRound}) {
paint.stroke_cap = cap;
canvas.DrawPath(path, paint);
canvas.Translate({80, 0});
}
canvas.Translate({-240, 60});
}
return canvas.EndRecordingAsPicture();
};
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<Color> 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<Scalar> 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, DrawRectStrokesRenderCorrectly) {
Canvas canvas;
Paint paint;
paint.color = Color::Red();
paint.style = Paint::Style::kStroke;
paint.stroke_width = 10;
canvas.Translate({100, 100});
canvas.DrawPath(
PathBuilder{}.AddRect(Rect::MakeSize(Size{100, 100})).TakePath(),
{paint});
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, CanDrawMultiContourConvexPath) {
PathBuilder builder = {};
for (auto i = 0; i < 10; i++) {
if (i % 2 == 0) {
builder.AddCircle(Point(100 + 50 * i, 100 + 50 * i), 100);
} else {
builder.MoveTo({100.f + 50.f * i - 100, 100.f + 50.f * i});
builder.LineTo({100.f + 50.f * i, 100.f + 50.f * i - 100});
builder.LineTo({100.f + 50.f * i - 100, 100.f + 50.f * i - 100});
builder.Close();
}
}
builder.SetConvexity(Convexity::kConvex);
Canvas canvas;
canvas.DrawPath(builder.TakePath(), {.color = Color::Red().WithAlpha(0.4)});
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, ArcWithZeroSweepAndBlur) {
Canvas canvas;
canvas.Scale(GetContentScale());
Paint paint;
paint.color = Color::Red();
std::vector<Color> colors = {Color{1.0, 0.0, 0.0, 1.0},
Color{0.0, 0.0, 0.0, 1.0}};
std::vector<Scalar> stops = {0.0, 1.0};
paint.color_source = ColorSource::MakeSweepGradient(
{100, 100}, Degrees(45), Degrees(135), std::move(colors),
std::move(stops), Entity::TileMode::kMirror, {});
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Sigma(20),
};
PathBuilder builder;
builder.AddArc(Rect::MakeXYWH(10, 10, 100, 100), Degrees(0), Degrees(0),
false);
canvas.DrawPath(builder.TakePath(), paint);
// Check that this empty picture can be created without crashing.
canvas.EndRecordingAsPicture();
}
TEST_P(AiksTest, CanRenderClips) {
Canvas canvas;
Paint paint;
paint.color = Color::Fuchsia();
canvas.ClipPath(
PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 500, 500)).TakePath());
canvas.DrawPath(PathBuilder{}.AddCircle({500, 500}, 250).TakePath(), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, CanRenderOverlappingMultiContourPath) {
Canvas canvas;
Paint paint;
paint.color = Color::Red();
PathBuilder::RoundingRadii radii;
radii.top_left = {50, 50};
radii.top_right = {50, 50};
radii.bottom_right = {50, 50};
radii.bottom_left = {50, 50};
const Scalar kTriangleHeight = 100;
canvas.Translate(Vector2(200, 200));
// Form a path similar to the Material drop slider value indicator. Both
// shapes should render identically side-by-side.
{
auto path =
PathBuilder{}
.MoveTo({0, kTriangleHeight})
.LineTo({-kTriangleHeight / 2.0f, 0})
.LineTo({kTriangleHeight / 2.0f, 0})
.Close()
.AddRoundedRect(
Rect::MakeXYWH(-kTriangleHeight / 2.0f, -kTriangleHeight / 2.0f,
kTriangleHeight, kTriangleHeight),
radii)
.TakePath();
canvas.DrawPath(path, paint);
}
canvas.Translate(Vector2(100, 0));
{
auto path =
PathBuilder{}
.MoveTo({0, kTriangleHeight})
.LineTo({-kTriangleHeight / 2.0f, 0})
.LineTo({0, -10})
.LineTo({kTriangleHeight / 2.0f, 0})
.Close()
.AddRoundedRect(
Rect::MakeXYWH(-kTriangleHeight / 2.0f, -kTriangleHeight / 2.0f,
kTriangleHeight, kTriangleHeight),
radii)
.TakePath();
canvas.DrawPath(path, paint);
}
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
} // namespace testing
} // namespace impeller

View File

@ -2570,5 +2570,4 @@ TEST_P(AiksTest, SetContentsWithRegion) {
// █ - aiks_blend_unittests.cc
// █ - aiks_blur_unittests.cc
// █ - aiks_gradient_unittests.cc
// █ - aiks_path_unittests.cc
// █████████████████████████████████████████████████████████████████████████████

View File

@ -476,7 +476,12 @@ void Canvas::DrawRect(const Rect& rect, const Paint& paint) {
}
void Canvas::DrawOval(const Rect& rect, const Paint& paint) {
if (rect.IsSquare()) {
// TODO(jonahwilliams): This additional condition avoids an assert in the
// stroke circle geometry generator. I need to verify the condition that this
// assert prevents.
if (rect.IsSquare() && (paint.style == Paint::Style::kFill ||
(paint.style == Paint::Style::kStroke &&
paint.stroke_width < rect.GetWidth()))) {
// Circles have slightly less overhead and can do stroking
DrawCircle(rect.GetCenter(), rect.GetWidth() * 0.5f, paint);
return;

View File

@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "display_list/dl_sampling_options.h"
#include "display_list/dl_tile_mode.h"
#include "display_list/effects/dl_color_source.h"
#include "display_list/effects/dl_mask_filter.h"
#include "flutter/impeller/aiks/aiks_unittests.h"
#include "flutter/display_list/dl_blend_mode.h"
@ -10,6 +14,13 @@
#include "flutter/display_list/dl_paint.h"
#include "flutter/display_list/effects/dl_color_filter.h"
#include "flutter/testing/testing.h"
#include "impeller/display_list/dl_image_impeller.h"
#include "impeller/playground/widgets.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkRRect.h"
namespace impeller {
namespace testing {
@ -42,5 +53,458 @@ TEST_P(AiksTest, RotateColorFilteredPath) {
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderStrokes) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kRed());
paint.setStrokeWidth(20);
paint.setDrawStyle(DlDrawStyle::kStroke);
builder.DrawPath(SkPath::Line({200, 100}, {800, 100}), paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderCurvedStrokes) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kRed());
paint.setStrokeWidth(25);
paint.setDrawStyle(DlDrawStyle::kStroke);
builder.DrawPath(SkPath::Circle(500, 500, 250), paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderThickCurvedStrokes) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kRed());
paint.setStrokeWidth(100);
paint.setDrawStyle(DlDrawStyle::kStroke);
builder.DrawPath(SkPath::Circle(100, 100, 50), paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderThinCurvedStrokes) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kRed());
paint.setStrokeWidth(0.01);
paint.setDrawStyle(DlDrawStyle::kStroke);
builder.DrawPath(SkPath::Circle(100, 100, 50), paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderStrokePathThatEndsAtSharpTurn) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kRed());
paint.setStrokeWidth(200);
paint.setDrawStyle(DlDrawStyle::kStroke);
SkPath path;
path.arcTo(SkRect::MakeXYWH(100, 100, 200, 200), 0, 90, false);
builder.DrawPath(path, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderStrokePathWithCubicLine) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kRed());
paint.setStrokeWidth(20);
paint.setDrawStyle(DlDrawStyle::kStroke);
SkPath path;
path.moveTo(0, 200);
path.cubicTo(50, 400, 350, 0, 400, 200);
builder.DrawPath(path, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderQuadraticStrokeWithInstantTurn) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kRed());
paint.setStrokeWidth(50);
paint.setDrawStyle(DlDrawStyle::kStroke);
paint.setStrokeCap(DlStrokeCap::kRound);
// Should draw a diagonal pill shape. If flat on either end, the stroke is
// rendering wrong.
SkPath path;
path.moveTo(250, 250);
path.quadTo(100, 100, 250, 250);
builder.DrawPath(path, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderDifferencePaths) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kRed());
SkPoint radii[4] = {{50, 25}, {25, 50}, {50, 25}, {25, 50}};
SkPath path;
SkRRect rrect;
rrect.setRectRadii(SkRect::MakeXYWH(100, 100, 200, 200), radii);
path.addRRect(rrect);
path.addCircle(200, 200, 50);
path.setFillType(SkPathFillType::kEvenOdd);
builder.DrawImage(
DlImageImpeller::Make(CreateTextureForFixture("boston.jpg")), {10, 10},
{});
builder.DrawPath(path, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
// Regression test for https://github.com/flutter/flutter/issues/134816.
//
// It should be possible to draw 3 lines, and not have an implicit close path.
TEST_P(AiksTest, CanDrawAnOpenPath) {
DisplayListBuilder builder;
// Starting at (50, 50), draw lines from:
// 1. (50, height)
// 2. (width, height)
// 3. (width, 50)
SkPath path;
path.moveTo(50, 50);
path.lineTo(50, 100);
path.lineTo(100, 100);
path.lineTo(100, 50);
DlPaint paint;
paint.setColor(DlColor::kRed());
paint.setDrawStyle(DlDrawStyle::kStroke);
paint.setStrokeWidth(10);
builder.DrawPath(path, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanDrawAnOpenPathThatIsntARect) {
DisplayListBuilder builder;
// Draw a stroked path that is explicitly closed to verify
// It doesn't become a rectangle.
SkPath path;
// PathBuilder builder;
path.moveTo(50, 50);
path.lineTo(520, 120);
path.lineTo(300, 310);
path.lineTo(100, 50);
path.close();
DlPaint paint;
paint.setColor(DlColor::kRed());
paint.setDrawStyle(DlDrawStyle::kStroke);
paint.setStrokeWidth(10);
builder.DrawPath(path, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, SolidStrokesRenderCorrectly) {
// Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
auto callback = [&]() -> sk_sp<DisplayList> {
static Color color = Color::Black().WithAlpha(0.5);
static float scale = 3;
static bool add_circle_clip = true;
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::ColorEdit4("Color", reinterpret_cast<float*>(&color));
ImGui::SliderFloat("Scale", &scale, 0, 6);
ImGui::Checkbox("Circle clip", &add_circle_clip);
ImGui::End();
}
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
DlPaint paint;
paint.setColor(DlColor::kWhite());
builder.DrawPaint(paint);
paint.setColor(DlColor(color.alpha, color.red, color.green, color.blue));
paint.setDrawStyle(DlDrawStyle::kStroke);
paint.setStrokeWidth(10);
SkPath path;
path.moveTo({20, 20});
path.quadTo({60, 20}, {60, 60});
path.close();
path.moveTo({60, 20});
path.quadTo({60, 60}, {20, 60});
builder.Scale(scale, scale);
if (add_circle_clip) {
static PlaygroundPoint circle_clip_point_a(Point(60, 300), 20,
Color::Red());
static PlaygroundPoint circle_clip_point_b(Point(600, 300), 20,
Color::Red());
auto [handle_a, handle_b] =
DrawPlaygroundLine(circle_clip_point_a, circle_clip_point_b);
SkMatrix screen_to_canvas = SkMatrix::I();
if (!builder.GetTransform().invert(&screen_to_canvas)) {
return nullptr;
}
SkPoint point_a =
screen_to_canvas.mapPoint(SkPoint::Make(handle_a.x, handle_a.y));
SkPoint point_b =
screen_to_canvas.mapPoint(SkPoint::Make(handle_b.x, handle_b.y));
SkPoint middle = point_a + point_b;
middle.scale(GetContentScale().x / 2);
auto radius = SkPoint::Distance(point_a, middle);
builder.ClipPath(SkPath::Circle(middle.x(), middle.y(), radius));
}
for (auto join :
{DlStrokeJoin::kBevel, DlStrokeJoin::kRound, DlStrokeJoin::kMiter}) {
paint.setStrokeJoin(join);
for (auto cap :
{DlStrokeCap::kButt, DlStrokeCap::kSquare, DlStrokeCap::kRound}) {
paint.setStrokeCap(cap);
builder.DrawPath(path, paint);
builder.Translate(80, 0);
}
builder.Translate(-240, 60);
}
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, DrawLinesRenderCorrectly) {
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
DlPaint paint;
paint.setColor(DlColor::kBlue());
paint.setStrokeWidth(10);
auto draw = [&builder](DlPaint& paint) {
for (auto cap :
{DlStrokeCap::kButt, DlStrokeCap::kSquare, DlStrokeCap::kRound}) {
paint.setStrokeCap(cap);
SkPoint origin = {100, 100};
builder.DrawLine({150, 100}, {250, 100}, paint);
for (int d = 15; d < 90; d += 15) {
Matrix m = Matrix::MakeRotationZ(Degrees(d));
Point origin = {100, 100};
Point p0 = {50, 0};
Point p1 = {150, 0};
auto a = origin + m * p0;
auto b = origin + m * p1;
builder.DrawLine(SkPoint::Make(a.x, a.y), SkPoint::Make(b.x, b.y),
paint);
}
builder.DrawLine({100, 150}, {100, 250}, paint);
builder.DrawCircle({origin}, 35, paint);
builder.DrawLine({250, 250}, {250, 250}, paint);
builder.Translate(250, 0);
}
builder.Translate(-750, 250);
};
std::vector<DlColor> colors = {
DlColor{1, 0x1f / 255.0, 0.0, 0x5c / 255.0},
DlColor{1, 0x5b / 255.0, 0.0, 0x60 / 255.0},
DlColor{1, 0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0},
DlColor{1, 0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0},
DlColor{1, 0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0},
DlColor{1, 0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0},
DlColor{1, 0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0}};
std::vector<Scalar> 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 = DlImageImpeller::Make(
CreateTextureForFixture("airplane.jpg",
/*enable_mipmapping=*/true));
draw(paint);
paint.setColorSource(DlColorSource::MakeRadial({100, 100}, 200, stops.size(),
colors.data(), stops.data(),
DlTileMode::kMirror));
draw(paint);
SkMatrix matrix = SkMatrix::Translate(-150, 75);
paint.setColorSource(std::make_shared<DlImageColorSource>(
texture, DlTileMode::kRepeat, DlTileMode::kRepeat,
DlImageSampling::kMipmapLinear, &matrix));
draw(paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, DrawRectStrokesRenderCorrectly) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kRed());
paint.setDrawStyle(DlDrawStyle::kStroke);
paint.setStrokeWidth(10);
builder.Translate(100, 100);
builder.DrawPath(SkPath::Rect(SkRect::MakeSize(SkSize{100, 100})), {paint});
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, DrawRectStrokesWithBevelJoinRenderCorrectly) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kRed());
paint.setDrawStyle(DlDrawStyle::kStroke);
paint.setStrokeWidth(10);
paint.setStrokeJoin(DlStrokeJoin::kBevel);
builder.Translate(100, 100);
builder.DrawPath(SkPath::Rect(SkRect::MakeSize(SkSize{100, 100})), paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanDrawMultiContourConvexPath) {
SkPath path;
for (auto i = 0; i < 10; i++) {
if (i % 2 == 0) {
path.addCircle(100 + 50 * i, 100 + 50 * i, 100);
path.close();
} else {
path.moveTo({100.f + 50.f * i - 100, 100.f + 50.f * i});
path.lineTo({100.f + 50.f * i, 100.f + 50.f * i - 100});
path.lineTo({100.f + 50.f * i - 100, 100.f + 50.f * i - 100});
path.close();
}
}
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kRed().withAlpha(102));
builder.DrawPath(path, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, ArcWithZeroSweepAndBlur) {
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
DlPaint paint;
paint.setColor(DlColor::kRed());
std::vector<DlColor> colors = {DlColor{1.0, 0.0, 0.0, 1.0},
DlColor{0.0, 0.0, 0.0, 1.0}};
std::vector<Scalar> stops = {0.0, 1.0};
paint.setColorSource(
DlColorSource::MakeSweep({100, 100}, 45, 135, stops.size(), colors.data(),
stops.data(), DlTileMode::kMirror));
paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 20));
SkPath path;
path.addArc(SkRect::MakeXYWH(10, 10, 100, 100), 0, 0);
builder.DrawPath(path, paint);
// Check that this empty picture can be created without crashing.
builder.Build();
}
TEST_P(AiksTest, CanRenderClips) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kFuchsia());
builder.ClipPath(SkPath::Rect(SkRect::MakeXYWH(0, 0, 500, 500)));
builder.DrawPath(SkPath::Circle(500, 500, 250), paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderOverlappingMultiContourPath) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kRed());
SkPoint radii[4] = {{50, 50}, {50, 50}, {50, 50}, {50, 50}};
const Scalar kTriangleHeight = 100;
SkRRect rrect;
rrect.setRectRadii(
SkRect::MakeXYWH(-kTriangleHeight / 2.0f, -kTriangleHeight / 2.0f,
kTriangleHeight, kTriangleHeight),
radii //
);
builder.Translate(200, 200);
// Form a path similar to the Material drop slider value indicator. Both
// shapes should render identically side-by-side.
{
SkPath path;
path.moveTo(0, kTriangleHeight);
path.lineTo(-kTriangleHeight / 2.0f, 0);
path.lineTo(kTriangleHeight / 2.0f, 0);
path.close();
path.addRRect(rrect);
builder.DrawPath(path, paint);
}
builder.Translate(100, 0);
{
SkPath path;
path.moveTo(0, kTriangleHeight);
path.lineTo(-kTriangleHeight / 2.0f, 0);
path.lineTo(0, -10);
path.lineTo(kTriangleHeight / 2.0f, 0);
path.close();
path.addRRect(rrect);
builder.DrawPath(path, paint);
}
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
} // namespace testing
} // namespace impeller