Flutter Engine
The Flutter Engine
aiks_path_unittests.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "flutter/impeller/aiks/aiks_unittests.h"
6
10#include "third_party/imgui/imgui.h"
11
12////////////////////////////////////////////////////////////////////////////////
13// This is for tests of Canvas that are interested the results of rendering
14// paths.
15////////////////////////////////////////////////////////////////////////////////
16
17namespace impeller {
18namespace testing {
19
20TEST_P(AiksTest, CanRenderStrokes) {
21 Canvas canvas;
23 paint.color = Color::Red();
24 paint.stroke_width = 20.0;
26 canvas.DrawPath(PathBuilder{}.AddLine({200, 100}, {800, 100}).TakePath(),
27 paint);
28 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
29}
30
31TEST_P(AiksTest, CanRenderCurvedStrokes) {
32 Canvas canvas;
34 paint.color = Color::Red();
35 paint.stroke_width = 25.0;
37 canvas.DrawPath(PathBuilder{}.AddCircle({500, 500}, 250).TakePath(), paint);
38 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
39}
40
41TEST_P(AiksTest, CanRenderThickCurvedStrokes) {
42 Canvas canvas;
44 paint.color = Color::Red();
45 paint.stroke_width = 100.0;
47 canvas.DrawPath(PathBuilder{}.AddCircle({100, 100}, 50).TakePath(), paint);
48 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
49}
50
51TEST_P(AiksTest, CanRenderThinCurvedStrokes) {
52 Canvas canvas;
54 paint.color = Color::Red();
55 // Impeller doesn't support hairlines yet, but size this guarantees
56 // the smallest possible stroke width.
57 paint.stroke_width = 0.01;
59 canvas.DrawPath(PathBuilder{}.AddCircle({100, 100}, 50).TakePath(), paint);
60 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
61}
62
63TEST_P(AiksTest, CanRenderStrokePathThatEndsAtSharpTurn) {
64 Canvas canvas;
65
67 paint.color = Color::Red();
69 paint.stroke_width = 200;
70
71 Rect rect = Rect::MakeXYWH(100, 100, 200, 200);
73 builder.AddArc(rect, Degrees(0), Degrees(90), false);
74
75 canvas.DrawPath(builder.TakePath(), paint);
76 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
77}
78
79TEST_P(AiksTest, CanRenderStrokePathWithCubicLine) {
80 Canvas canvas;
81
83 paint.color = Color::Red();
85 paint.stroke_width = 20;
86
88 builder.AddCubicCurve({0, 200}, {50, 400}, {350, 0}, {400, 200});
89
90 canvas.DrawPath(builder.TakePath(), paint);
91 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
92}
93
94TEST_P(AiksTest, CanRenderQuadraticStrokeWithInstantTurn) {
95 Canvas canvas;
96
98 paint.color = Color::Red();
100 paint.stroke_cap = Cap::kRound;
101 paint.stroke_width = 50;
102
103 // Should draw a diagonal pill shape. If flat on either end, the stroke is
104 // rendering wrong.
106 builder.MoveTo({250, 250});
107 builder.QuadraticCurveTo({100, 100}, {250, 250});
108
109 canvas.DrawPath(builder.TakePath(), paint);
110 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
111}
112
113TEST_P(AiksTest, CanRenderDifferencePaths) {
114 Canvas canvas;
115
116 Paint paint;
117 paint.color = Color::Red();
118
120
122 radii.top_left = {50, 25};
123 radii.top_right = {25, 50};
124 radii.bottom_right = {50, 25};
125 radii.bottom_left = {25, 50};
126
127 builder.AddRoundedRect(Rect::MakeXYWH(100, 100, 200, 200), radii);
128 builder.AddCircle({200, 200}, 50);
129 auto path = builder.TakePath(FillType::kOdd);
130
131 canvas.DrawImage(
132 std::make_shared<Image>(CreateTextureForFixture("boston.jpg")), {10, 10},
133 Paint{});
134 canvas.DrawPath(path, paint);
135
136 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
137}
138
139// Regression test for https://github.com/flutter/flutter/issues/134816.
140//
141// It should be possible to draw 3 lines, and not have an implicit close path.
142TEST_P(AiksTest, CanDrawAnOpenPath) {
143 Canvas canvas;
144
145 // Starting at (50, 50), draw lines from:
146 // 1. (50, height)
147 // 2. (width, height)
148 // 3. (width, 50)
150 builder.MoveTo({50, 50});
151 builder.LineTo({50, 100});
152 builder.LineTo({100, 100});
153 builder.LineTo({100, 50});
154
155 Paint paint;
156 paint.color = Color::Red();
158 paint.stroke_width = 10;
159
160 canvas.DrawPath(builder.TakePath(), paint);
161
162 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
163}
164
165TEST_P(AiksTest, CanDrawAnOpenPathThatIsntARect) {
166 Canvas canvas;
167
168 // Draw a stroked path that is explicitly closed to verify
169 // It doesn't become a rectangle.
171 builder.MoveTo({50, 50});
172 builder.LineTo({520, 120});
173 builder.LineTo({300, 310});
174 builder.LineTo({100, 50});
175 builder.Close();
176
177 Paint paint;
178 paint.color = Color::Red();
180 paint.stroke_width = 10;
181
182 canvas.DrawPath(builder.TakePath(), paint);
183
184 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
185}
186
187TEST_P(AiksTest, SolidStrokesRenderCorrectly) {
188 // Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
189 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
190 static Color color = Color::Black().WithAlpha(0.5);
191 static float scale = 3;
192 static bool add_circle_clip = true;
193
194 if (AiksTest::ImGuiBegin("Controls", nullptr,
195 ImGuiWindowFlags_AlwaysAutoResize)) {
196 ImGui::ColorEdit4("Color", reinterpret_cast<float*>(&color));
197 ImGui::SliderFloat("Scale", &scale, 0, 6);
198 ImGui::Checkbox("Circle clip", &add_circle_clip);
199 ImGui::End();
200 }
201
202 Canvas canvas;
203 canvas.Scale(GetContentScale());
204 Paint paint;
205
206 paint.color = Color::White();
207 canvas.DrawPaint(paint);
208
209 paint.color = color;
211 paint.stroke_width = 10;
212
214 .MoveTo({20, 20})
215 .QuadraticCurveTo({60, 20}, {60, 60})
216 .Close()
217 .MoveTo({60, 20})
218 .QuadraticCurveTo({60, 60}, {20, 60})
219 .TakePath();
220
221 canvas.Scale(Vector2(scale, scale));
222
223 if (add_circle_clip) {
224 static PlaygroundPoint circle_clip_point_a(Point(60, 300), 20,
225 Color::Red());
226 static PlaygroundPoint circle_clip_point_b(Point(600, 300), 20,
227 Color::Red());
228 auto [handle_a, handle_b] =
229 DrawPlaygroundLine(circle_clip_point_a, circle_clip_point_b);
230
231 auto screen_to_canvas = canvas.GetCurrentTransform().Invert();
232 Point point_a = screen_to_canvas * handle_a * GetContentScale();
233 Point point_b = screen_to_canvas * handle_b * GetContentScale();
234
235 Point middle = (point_a + point_b) / 2;
236 auto radius = point_a.GetDistance(middle);
237 canvas.ClipPath(PathBuilder{}.AddCircle(middle, radius).TakePath());
238 }
239
240 for (auto join : {Join::kBevel, Join::kRound, Join::kMiter}) {
241 paint.stroke_join = join;
242 for (auto cap : {Cap::kButt, Cap::kSquare, Cap::kRound}) {
243 paint.stroke_cap = cap;
244 canvas.DrawPath(path, paint);
245 canvas.Translate({80, 0});
246 }
247 canvas.Translate({-240, 60});
248 }
249
250 return canvas.EndRecordingAsPicture();
251 };
252
253 ASSERT_TRUE(OpenPlaygroundHere(callback));
254}
255
256TEST_P(AiksTest, DrawLinesRenderCorrectly) {
257 Canvas canvas;
258 canvas.Scale(GetContentScale());
259 Paint paint;
260 paint.color = Color::Blue();
261 paint.stroke_width = 10;
262
263 auto draw = [&canvas](Paint& paint) {
264 for (auto cap : {Cap::kButt, Cap::kSquare, Cap::kRound}) {
265 paint.stroke_cap = cap;
266 Point origin = {100, 100};
267 Point p0 = {50, 0};
268 Point p1 = {150, 0};
269 canvas.DrawLine({150, 100}, {250, 100}, paint);
270 for (int d = 15; d < 90; d += 15) {
272 canvas.DrawLine(origin + m * p0, origin + m * p1, paint);
273 }
274 canvas.DrawLine({100, 150}, {100, 250}, paint);
275 canvas.DrawCircle({origin}, 35, paint);
276
277 canvas.DrawLine({250, 250}, {250, 250}, paint);
278
279 canvas.Translate({250, 0});
280 }
281 canvas.Translate({-750, 250});
282 };
283
284 std::vector<Color> colors = {
285 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
286 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
287 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
288 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
289 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
290 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
291 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
292 std::vector<Scalar> stops = {
293 0.0,
294 (1.0 / 6.0) * 1,
295 (1.0 / 6.0) * 2,
296 (1.0 / 6.0) * 3,
297 (1.0 / 6.0) * 4,
298 (1.0 / 6.0) * 5,
299 1.0,
300 };
301
302 auto texture = CreateTextureForFixture("airplane.jpg",
303 /*enable_mipmapping=*/true);
304
305 draw(paint);
306
308 {100, 100}, 200, std::move(colors), std::move(stops),
310 draw(paint);
311
312 paint.color_source = ColorSource::MakeImage(
314 Matrix::MakeTranslation({-150, 75}));
315 draw(paint);
316
317 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
318}
319
320TEST_P(AiksTest, DrawRectStrokesRenderCorrectly) {
321 Canvas canvas;
322 Paint paint;
323 paint.color = Color::Red();
325 paint.stroke_width = 10;
326
327 canvas.Translate({100, 100});
328 canvas.DrawPath(
329 PathBuilder{}.AddRect(Rect::MakeSize(Size{100, 100})).TakePath(),
330 {paint});
331
332 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
333}
334
335TEST_P(AiksTest, DrawRectStrokesWithBevelJoinRenderCorrectly) {
336 Canvas canvas;
337 Paint paint;
338 paint.color = Color::Red();
340 paint.stroke_width = 10;
341 paint.stroke_join = Join::kBevel;
342
343 canvas.Translate({100, 100});
344 canvas.DrawPath(
345 PathBuilder{}.AddRect(Rect::MakeSize(Size{100, 100})).TakePath(),
346 {paint});
347
348 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
349}
350
351TEST_P(AiksTest, CanDrawMultiContourConvexPath) {
352 PathBuilder builder = {};
353 for (auto i = 0; i < 10; i++) {
354 if (i % 2 == 0) {
355 builder.AddCircle(Point(100 + 50 * i, 100 + 50 * i), 100);
356 } else {
357 builder.MoveTo({100.f + 50.f * i - 100, 100.f + 50.f * i});
358 builder.LineTo({100.f + 50.f * i, 100.f + 50.f * i - 100});
359 builder.LineTo({100.f + 50.f * i - 100, 100.f + 50.f * i - 100});
360 builder.Close();
361 }
362 }
363 builder.SetConvexity(Convexity::kConvex);
364
365 Canvas canvas;
366 canvas.DrawPath(builder.TakePath(), {.color = Color::Red().WithAlpha(0.4)});
367
368 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
369}
370
371TEST_P(AiksTest, ArcWithZeroSweepAndBlur) {
372 Canvas canvas;
373 canvas.Scale(GetContentScale());
374
375 Paint paint;
376 paint.color = Color::Red();
377 std::vector<Color> colors = {Color{1.0, 0.0, 0.0, 1.0},
378 Color{0.0, 0.0, 0.0, 1.0}};
379 std::vector<Scalar> stops = {0.0, 1.0};
381 {100, 100}, Degrees(45), Degrees(135), std::move(colors),
382 std::move(stops), Entity::TileMode::kMirror, {});
383 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
385 .sigma = Sigma(20),
386 };
387
389 builder.AddArc(Rect::MakeXYWH(10, 10, 100, 100), Degrees(0), Degrees(0),
390 false);
391 canvas.DrawPath(builder.TakePath(), paint);
392
393 // Check that this empty picture can be created without crashing.
394 canvas.EndRecordingAsPicture();
395}
396
397TEST_P(AiksTest, CanRenderClips) {
398 Canvas canvas;
399 Paint paint;
400 paint.color = Color::Fuchsia();
401 canvas.ClipPath(
402 PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 500, 500)).TakePath());
403 canvas.DrawPath(PathBuilder{}.AddCircle({500, 500}, 250).TakePath(), paint);
404 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
405}
406
407TEST_P(AiksTest, CanRenderOverlappingMultiContourPath) {
408 Canvas canvas;
409
410 Paint paint;
411 paint.color = Color::Red();
412
414 radii.top_left = {50, 50};
415 radii.top_right = {50, 50};
416 radii.bottom_right = {50, 50};
417 radii.bottom_left = {50, 50};
418
419 const Scalar kTriangleHeight = 100;
420 canvas.Translate(Vector2(200, 200));
421 // Form a path similar to the Material drop slider value indicator. Both
422 // shapes should render identically side-by-side.
423 {
424 auto path =
426 .MoveTo({0, kTriangleHeight})
427 .LineTo({-kTriangleHeight / 2.0f, 0})
428 .LineTo({kTriangleHeight / 2.0f, 0})
429 .Close()
430 .AddRoundedRect(
431 Rect::MakeXYWH(-kTriangleHeight / 2.0f, -kTriangleHeight / 2.0f,
432 kTriangleHeight, kTriangleHeight),
433 radii)
434 .TakePath();
435
436 canvas.DrawPath(path, paint);
437 }
438 canvas.Translate(Vector2(100, 0));
439 {
440 auto path =
442 .MoveTo({0, kTriangleHeight})
443 .LineTo({-kTriangleHeight / 2.0f, 0})
444 .LineTo({0, -10})
445 .LineTo({kTriangleHeight / 2.0f, 0})
446 .Close()
447 .AddRoundedRect(
448 Rect::MakeXYWH(-kTriangleHeight / 2.0f, -kTriangleHeight / 2.0f,
449 kTriangleHeight, kTriangleHeight),
450 radii)
451 .TakePath();
452
453 canvas.DrawPath(path, paint);
454 }
455
456 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
457}
458
459} // namespace testing
460} // namespace impeller
static void draw(SkCanvas *canvas, SkRect &target, int x, int y)
Definition: aaclip.cpp:27
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
static ColorSource MakeRadialGradient(Point center, Scalar radius, std::vector< Color > colors, std::vector< Scalar > stops, Entity::TileMode tile_mode, Matrix effect_transform)
static ColorSource MakeImage(std::shared_ptr< Texture > texture, Entity::TileMode x_tile_mode, Entity::TileMode y_tile_mode, SamplerDescriptor sampler_descriptor, Matrix effect_transform)
static ColorSource MakeSweepGradient(Point center, Degrees start_angle, Degrees end_angle, std::vector< Color > colors, std::vector< Scalar > stops, Entity::TileMode tile_mode, Matrix effect_transform)
@ kNormal
Blurred inside and outside.
PathBuilder & AddRect(Rect rect)
Path TakePath(FillType fill=FillType::kNonZero)
Definition: path_builder.cc:22
PathBuilder & MoveTo(Point point, bool relative=false)
Definition: path_builder.cc:33
PathBuilder & AddCircle(const Point &center, Scalar radius)
PathBuilder & AddLine(const Point &p1, const Point &p2)
Move to point p1, then insert a line from p1 to p2.
const Paint & paint
Definition: color_source.cc:38
DlColor color
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition: main.cc:19
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
FlTexture * texture
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
PODArray< SkColor > colors
Definition: SkRecords.h:276
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer)
Point Vector2
Definition: point.h:326
float Scalar
Definition: scalar.h:18
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition: widgets.cc:50
TPoint< Scalar > Point
Definition: point.h:322
void LineTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:24
void Close(PathBuilder *builder)
Definition: tessellator.cc:38
static SkString join(const CommandLineFlags::StringArray &)
Definition: skpbench.cpp:741
const Scalar scale
static constexpr Color Fuchsia()
Definition: color.h:468
static constexpr Color Black()
Definition: color.h:268
static constexpr Color White()
Definition: color.h:266
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:280
static constexpr Color Red()
Definition: color.h:274
static constexpr Color Blue()
Definition: color.h:278
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:213
FilterContents::BlurStyle style
Definition: paint.h:49
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:32
static constexpr TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
static constexpr TRect MakeSize(const TSize< U > &size)
Definition: rect.h:146