Flutter Impeller
aiks_dl_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 "display_list/dl_sampling_options.h"
6 #include "display_list/dl_tile_mode.h"
7 #include "display_list/effects/dl_color_source.h"
8 #include "display_list/effects/dl_mask_filter.h"
10 
11 #include "flutter/display_list/dl_blend_mode.h"
12 #include "flutter/display_list/dl_builder.h"
13 #include "flutter/display_list/dl_color.h"
14 #include "flutter/display_list/dl_paint.h"
15 #include "flutter/display_list/effects/dl_color_filter.h"
16 #include "flutter/testing/testing.h"
19 
20 #include "include/core/SkMatrix.h"
21 #include "include/core/SkPath.h"
22 #include "include/core/SkPathTypes.h"
23 #include "include/core/SkRRect.h"
24 
25 namespace impeller {
26 namespace testing {
27 
28 using namespace flutter;
29 
30 TEST_P(AiksTest, RotateColorFilteredPath) {
31  DisplayListBuilder builder;
32  builder.Transform(SkMatrix::Translate(300, 300) * SkMatrix::RotateDeg(90));
33 
34  SkPath arrow_stem;
35  SkPath arrow_head;
36 
37  arrow_stem.moveTo({120, 190}).lineTo({120, 50});
38  arrow_head.moveTo({50, 120}).lineTo({120, 190}).lineTo({190, 120});
39 
40  auto filter =
41  DlColorFilter::MakeBlend(DlColor::kAliceBlue(), DlBlendMode::kSrcIn);
42 
43  DlPaint paint;
44  paint.setStrokeWidth(15.0);
45  paint.setStrokeCap(DlStrokeCap::kRound);
46  paint.setStrokeJoin(DlStrokeJoin::kRound);
47  paint.setDrawStyle(DlDrawStyle::kStroke);
48  paint.setColorFilter(filter);
49  paint.setColor(DlColor::kBlack());
50 
51  builder.DrawPath(arrow_stem, paint);
52  builder.DrawPath(arrow_head, paint);
53 
54  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
55 }
56 
57 TEST_P(AiksTest, CanRenderStrokes) {
58  DisplayListBuilder builder;
59  DlPaint paint;
60  paint.setColor(DlColor::kRed());
61  paint.setStrokeWidth(20);
62  paint.setDrawStyle(DlDrawStyle::kStroke);
63 
64  builder.DrawPath(SkPath::Line({200, 100}, {800, 100}), paint);
65 
66  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
67 }
68 
69 TEST_P(AiksTest, CanRenderCurvedStrokes) {
70  DisplayListBuilder builder;
71  DlPaint paint;
72  paint.setColor(DlColor::kRed());
73  paint.setStrokeWidth(25);
74  paint.setDrawStyle(DlDrawStyle::kStroke);
75 
76  builder.DrawPath(SkPath::Circle(500, 500, 250), paint);
77 
78  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
79 }
80 
81 TEST_P(AiksTest, CanRenderThickCurvedStrokes) {
82  DisplayListBuilder builder;
83  DlPaint paint;
84  paint.setColor(DlColor::kRed());
85  paint.setStrokeWidth(100);
86  paint.setDrawStyle(DlDrawStyle::kStroke);
87 
88  builder.DrawPath(SkPath::Circle(100, 100, 50), paint);
89 
90  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
91 }
92 
93 TEST_P(AiksTest, CanRenderThinCurvedStrokes) {
94  DisplayListBuilder builder;
95  DlPaint paint;
96  paint.setColor(DlColor::kRed());
97  paint.setStrokeWidth(0.01);
98  paint.setDrawStyle(DlDrawStyle::kStroke);
99 
100  builder.DrawPath(SkPath::Circle(100, 100, 50), paint);
101 
102  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
103 }
104 
105 TEST_P(AiksTest, CanRenderStrokePathThatEndsAtSharpTurn) {
106  DisplayListBuilder builder;
107  DlPaint paint;
108  paint.setColor(DlColor::kRed());
109  paint.setStrokeWidth(200);
110  paint.setDrawStyle(DlDrawStyle::kStroke);
111 
112  SkPath path;
113  path.arcTo(SkRect::MakeXYWH(100, 100, 200, 200), 0, 90, false);
114 
115  builder.DrawPath(path, paint);
116 
117  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
118 }
119 
120 TEST_P(AiksTest, CanRenderStrokePathWithCubicLine) {
121  DisplayListBuilder builder;
122 
123  DlPaint paint;
124  paint.setColor(DlColor::kRed());
125  paint.setStrokeWidth(20);
126  paint.setDrawStyle(DlDrawStyle::kStroke);
127 
128  SkPath path;
129  path.moveTo(0, 200);
130  path.cubicTo(50, 400, 350, 0, 400, 200);
131 
132  builder.DrawPath(path, paint);
133  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
134 }
135 
136 TEST_P(AiksTest, CanRenderQuadraticStrokeWithInstantTurn) {
137  DisplayListBuilder builder;
138 
139  DlPaint paint;
140  paint.setColor(DlColor::kRed());
141  paint.setStrokeWidth(50);
142  paint.setDrawStyle(DlDrawStyle::kStroke);
143  paint.setStrokeCap(DlStrokeCap::kRound);
144 
145  // Should draw a diagonal pill shape. If flat on either end, the stroke is
146  // rendering wrong.
147  SkPath path;
148  path.moveTo(250, 250);
149  path.quadTo(100, 100, 250, 250);
150 
151  builder.DrawPath(path, paint);
152 
153  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
154 }
155 
156 TEST_P(AiksTest, CanRenderDifferencePaths) {
157  DisplayListBuilder builder;
158 
159  DlPaint paint;
160  paint.setColor(DlColor::kRed());
161 
162  SkPoint radii[4] = {{50, 25}, {25, 50}, {50, 25}, {25, 50}};
163  SkPath path;
164  SkRRect rrect;
165  rrect.setRectRadii(SkRect::MakeXYWH(100, 100, 200, 200), radii);
166  path.addRRect(rrect);
167  path.addCircle(200, 200, 50);
168  path.setFillType(SkPathFillType::kEvenOdd);
169 
170  builder.DrawImage(
171  DlImageImpeller::Make(CreateTextureForFixture("boston.jpg")),
172  SkPoint{10, 10}, {});
173  builder.DrawPath(path, paint);
174 
175  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
176 }
177 
178 // Regression test for https://github.com/flutter/flutter/issues/134816.
179 //
180 // It should be possible to draw 3 lines, and not have an implicit close path.
181 TEST_P(AiksTest, CanDrawAnOpenPath) {
182  DisplayListBuilder builder;
183 
184  // Starting at (50, 50), draw lines from:
185  // 1. (50, height)
186  // 2. (width, height)
187  // 3. (width, 50)
188  SkPath path;
189  path.moveTo(50, 50);
190  path.lineTo(50, 100);
191  path.lineTo(100, 100);
192  path.lineTo(100, 50);
193 
194  DlPaint paint;
195  paint.setColor(DlColor::kRed());
196  paint.setDrawStyle(DlDrawStyle::kStroke);
197  paint.setStrokeWidth(10);
198 
199  builder.DrawPath(path, paint);
200 
201  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
202 }
203 
204 TEST_P(AiksTest, CanDrawAnOpenPathThatIsntARect) {
205  DisplayListBuilder builder;
206 
207  // Draw a stroked path that is explicitly closed to verify
208  // It doesn't become a rectangle.
209  SkPath path;
210  // PathBuilder builder;
211  path.moveTo(50, 50);
212  path.lineTo(520, 120);
213  path.lineTo(300, 310);
214  path.lineTo(100, 50);
215  path.close();
216 
217  DlPaint paint;
218  paint.setColor(DlColor::kRed());
219  paint.setDrawStyle(DlDrawStyle::kStroke);
220  paint.setStrokeWidth(10);
221 
222  builder.DrawPath(path, paint);
223 
224  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
225 }
226 
227 TEST_P(AiksTest, SolidStrokesRenderCorrectly) {
228  // Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
229  auto callback = [&]() -> sk_sp<DisplayList> {
230  static Color color = Color::Black().WithAlpha(0.5);
231  static float scale = 3;
232  static bool add_circle_clip = true;
233 
234  if (AiksTest::ImGuiBegin("Controls", nullptr,
235  ImGuiWindowFlags_AlwaysAutoResize)) {
236  ImGui::ColorEdit4("Color", reinterpret_cast<float*>(&color));
237  ImGui::SliderFloat("Scale", &scale, 0, 6);
238  ImGui::Checkbox("Circle clip", &add_circle_clip);
239  ImGui::End();
240  }
241 
242  DisplayListBuilder builder;
243  builder.Scale(GetContentScale().x, GetContentScale().y);
244  DlPaint paint;
245 
246  paint.setColor(DlColor::kWhite());
247  builder.DrawPaint(paint);
248 
249  paint.setColor(
250  DlColor::ARGB(color.alpha, color.red, color.green, color.blue));
251  paint.setDrawStyle(DlDrawStyle::kStroke);
252  paint.setStrokeWidth(10);
253 
254  SkPath path;
255  path.moveTo({20, 20});
256  path.quadTo({60, 20}, {60, 60});
257  path.close();
258  path.moveTo({60, 20});
259  path.quadTo({60, 60}, {20, 60});
260 
261  builder.Scale(scale, scale);
262 
263  if (add_circle_clip) {
264  static PlaygroundPoint circle_clip_point_a(Point(60, 300), 20,
265  Color::Red());
266  static PlaygroundPoint circle_clip_point_b(Point(600, 300), 20,
267  Color::Red());
268  auto [handle_a, handle_b] =
269  DrawPlaygroundLine(circle_clip_point_a, circle_clip_point_b);
270 
271  SkMatrix screen_to_canvas = SkMatrix::I();
272  if (!builder.GetTransform().invert(&screen_to_canvas)) {
273  return nullptr;
274  }
275 
276  SkPoint point_a =
277  screen_to_canvas.mapPoint(SkPoint::Make(handle_a.x, handle_a.y));
278  SkPoint point_b =
279  screen_to_canvas.mapPoint(SkPoint::Make(handle_b.x, handle_b.y));
280 
281  SkPoint middle = point_a + point_b;
282  middle.scale(GetContentScale().x / 2);
283 
284  auto radius = SkPoint::Distance(point_a, middle);
285 
286  builder.ClipPath(SkPath::Circle(middle.x(), middle.y(), radius));
287  }
288 
289  for (auto join :
290  {DlStrokeJoin::kBevel, DlStrokeJoin::kRound, DlStrokeJoin::kMiter}) {
291  paint.setStrokeJoin(join);
292  for (auto cap :
293  {DlStrokeCap::kButt, DlStrokeCap::kSquare, DlStrokeCap::kRound}) {
294  paint.setStrokeCap(cap);
295  builder.DrawPath(path, paint);
296  builder.Translate(80, 0);
297  }
298  builder.Translate(-240, 60);
299  }
300 
301  return builder.Build();
302  };
303 
304  ASSERT_TRUE(OpenPlaygroundHere(callback));
305 }
306 
307 TEST_P(AiksTest, DrawLinesRenderCorrectly) {
308  DisplayListBuilder builder;
309  builder.Scale(GetContentScale().x, GetContentScale().y);
310 
311  DlPaint paint;
312  paint.setColor(DlColor::kBlue());
313  paint.setStrokeWidth(10);
314 
315  auto draw = [&builder](DlPaint& paint) {
316  for (auto cap :
317  {DlStrokeCap::kButt, DlStrokeCap::kSquare, DlStrokeCap::kRound}) {
318  paint.setStrokeCap(cap);
319  SkPoint origin = {100, 100};
320  builder.DrawLine(SkPoint{150, 100}, SkPoint{250, 100}, paint);
321  for (int d = 15; d < 90; d += 15) {
323  Point origin = {100, 100};
324  Point p0 = {50, 0};
325  Point p1 = {150, 0};
326  auto a = origin + m * p0;
327  auto b = origin + m * p1;
328 
329  builder.DrawLine(SkPoint::Make(a.x, a.y), SkPoint::Make(b.x, b.y),
330  paint);
331  }
332  builder.DrawLine(SkPoint{100, 150}, SkPoint{100, 250}, paint);
333  builder.DrawCircle({origin}, 35, paint);
334 
335  builder.DrawLine(SkPoint{250, 250}, SkPoint{250, 250}, paint);
336 
337  builder.Translate(250, 0);
338  }
339  builder.Translate(-750, 250);
340  };
341 
342  std::vector<DlColor> colors = {
343  DlColor::ARGB(1, 0x1f / 255.0, 0.0, 0x5c / 255.0),
344  DlColor::ARGB(1, 0x5b / 255.0, 0.0, 0x60 / 255.0),
345  DlColor::ARGB(1, 0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0),
346  DlColor::ARGB(1, 0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0),
347  DlColor::ARGB(1, 0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0),
348  DlColor::ARGB(1, 0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0),
349  DlColor::ARGB(1, 0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0)};
350  std::vector<Scalar> stops = {
351  0.0,
352  (1.0 / 6.0) * 1,
353  (1.0 / 6.0) * 2,
354  (1.0 / 6.0) * 3,
355  (1.0 / 6.0) * 4,
356  (1.0 / 6.0) * 5,
357  1.0,
358  };
359 
360  auto texture = DlImageImpeller::Make(
361  CreateTextureForFixture("airplane.jpg",
362  /*enable_mipmapping=*/true));
363 
364  draw(paint);
365 
366  paint.setColorSource(DlColorSource::MakeRadial({100, 100}, 200, stops.size(),
367  colors.data(), stops.data(),
368  DlTileMode::kMirror));
369  draw(paint);
370 
371  DlMatrix matrix = DlMatrix::MakeTranslation({-150, 75});
372  paint.setColorSource(DlColorSource::MakeImage(
373  texture, DlTileMode::kRepeat, DlTileMode::kRepeat,
374  DlImageSampling::kMipmapLinear, &matrix));
375  draw(paint);
376 
377  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
378 }
379 
380 TEST_P(AiksTest, DrawRectStrokesRenderCorrectly) {
381  DisplayListBuilder builder;
382  DlPaint paint;
383  paint.setColor(DlColor::kRed());
384  paint.setDrawStyle(DlDrawStyle::kStroke);
385  paint.setStrokeWidth(10);
386 
387  builder.Translate(100, 100);
388  builder.DrawPath(SkPath::Rect(SkRect::MakeSize(SkSize{100, 100})), {paint});
389 
390  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
391 }
392 
393 TEST_P(AiksTest, DrawRectStrokesWithBevelJoinRenderCorrectly) {
394  DisplayListBuilder builder;
395  DlPaint paint;
396  paint.setColor(DlColor::kRed());
397  paint.setDrawStyle(DlDrawStyle::kStroke);
398  paint.setStrokeWidth(10);
399  paint.setStrokeJoin(DlStrokeJoin::kBevel);
400 
401  builder.Translate(100, 100);
402  builder.DrawPath(SkPath::Rect(SkRect::MakeSize(SkSize{100, 100})), paint);
403 
404  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
405 }
406 
407 TEST_P(AiksTest, CanDrawMultiContourConvexPath) {
408  SkPath path;
409  for (auto i = 0; i < 10; i++) {
410  if (i % 2 == 0) {
411  path.addCircle(100 + 50 * i, 100 + 50 * i, 100);
412  path.close();
413  } else {
414  path.moveTo({100.f + 50.f * i - 100, 100.f + 50.f * i});
415  path.lineTo({100.f + 50.f * i, 100.f + 50.f * i - 100});
416  path.lineTo({100.f + 50.f * i - 100, 100.f + 50.f * i - 100});
417  path.close();
418  }
419  }
420 
421  DisplayListBuilder builder;
422  DlPaint paint;
423  paint.setColor(DlColor::kRed().withAlpha(102));
424  builder.DrawPath(path, paint);
425 
426  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
427 }
428 
429 TEST_P(AiksTest, ArcWithZeroSweepAndBlur) {
430  DisplayListBuilder builder;
431  builder.Scale(GetContentScale().x, GetContentScale().y);
432 
433  DlPaint paint;
434  paint.setColor(DlColor::kRed());
435 
436  std::vector<DlColor> colors = {DlColor::RGBA(1.0, 0.0, 0.0, 1.0),
437  DlColor::RGBA(0.0, 0.0, 0.0, 1.0)};
438  std::vector<Scalar> stops = {0.0, 1.0};
439 
440  paint.setColorSource(
441  DlColorSource::MakeSweep({100, 100}, 45, 135, stops.size(), colors.data(),
442  stops.data(), DlTileMode::kMirror));
443  paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 20));
444 
445  SkPath path;
446  path.addArc(SkRect::MakeXYWH(10, 10, 100, 100), 0, 0);
447  builder.DrawPath(path, paint);
448 
449  // Check that this empty picture can be created without crashing.
450  builder.Build();
451 }
452 
453 TEST_P(AiksTest, CanRenderClips) {
454  DisplayListBuilder builder;
455  DlPaint paint;
456  paint.setColor(DlColor::kFuchsia());
457 
458  builder.ClipPath(SkPath::Rect(SkRect::MakeXYWH(0, 0, 500, 500)));
459  builder.DrawPath(SkPath::Circle(500, 500, 250), paint);
460 
461  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
462 }
463 
464 TEST_P(AiksTest, FatStrokeArc) {
465  DlScalar stroke_width = 300;
466  DlScalar aspect = 1.0;
467  DlScalar start_angle = 0;
468  DlScalar end_angle = 90;
469  auto callback = [&]() -> sk_sp<DisplayList> {
470  if (AiksTest::ImGuiBegin("Controls", nullptr,
471  ImGuiWindowFlags_AlwaysAutoResize)) {
472  ImGui::SliderFloat("Stroke Width", &stroke_width, 1, 300);
473  ImGui::SliderFloat("Aspect", &aspect, 0.5, 2.0);
474  ImGui::SliderFloat("Start Angle", &start_angle, 0, 360);
475  ImGui::SliderFloat("End Angle", &end_angle, 0, 360);
476  ImGui::End();
477  }
478 
479  DisplayListBuilder builder;
480  DlPaint grey_paint;
481  grey_paint.setColor(DlColor(0xff111111));
482  builder.DrawPaint(grey_paint);
483 
484  DlPaint white_paint;
485  white_paint.setColor(DlColor::kWhite());
486  white_paint.setStrokeWidth(stroke_width);
487  white_paint.setDrawStyle(DlDrawStyle::kStroke);
488  DlPaint red_paint;
489  red_paint.setColor(DlColor::kRed());
490 
491  Rect rect = Rect::MakeXYWH(100, 100, 100, aspect * 100);
492  builder.DrawRect(rect, red_paint);
493  builder.DrawArc(rect, start_angle, end_angle,
494  /*useCenter=*/false, white_paint);
495  DlScalar frontier = rect.GetRight() + stroke_width / 2.0;
496  builder.DrawLine(Point(frontier, 0), Point(frontier, 150), red_paint);
497 
498  return builder.Build();
499  };
500  ASSERT_TRUE(OpenPlaygroundHere(callback));
501 }
502 
503 TEST_P(AiksTest, CanRenderOverlappingMultiContourPath) {
504  DisplayListBuilder builder;
505 
506  DlPaint paint;
507  paint.setColor(DlColor::kRed());
508 
509  SkPoint radii[4] = {{50, 50}, {50, 50}, {50, 50}, {50, 50}};
510 
511  const Scalar kTriangleHeight = 100;
512  SkRRect rrect;
513  rrect.setRectRadii(
514  SkRect::MakeXYWH(-kTriangleHeight / 2.0f, -kTriangleHeight / 2.0f,
515  kTriangleHeight, kTriangleHeight),
516  radii //
517  );
518 
519  builder.Translate(200, 200);
520  // Form a path similar to the Material drop slider value indicator. Both
521  // shapes should render identically side-by-side.
522  {
523  SkPath path;
524  path.moveTo(0, kTriangleHeight);
525  path.lineTo(-kTriangleHeight / 2.0f, 0);
526  path.lineTo(kTriangleHeight / 2.0f, 0);
527  path.close();
528  path.addRRect(rrect);
529 
530  builder.DrawPath(path, paint);
531  }
532  builder.Translate(100, 0);
533 
534  {
535  SkPath path;
536  path.moveTo(0, kTriangleHeight);
537  path.lineTo(-kTriangleHeight / 2.0f, 0);
538  path.lineTo(0, -10);
539  path.lineTo(kTriangleHeight / 2.0f, 0);
540  path.close();
541  path.addRRect(rrect);
542 
543  builder.DrawPath(path, paint);
544  }
545 
546  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
547 }
548 
549 } // namespace testing
550 } // namespace impeller
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
static sk_sp< DlImageImpeller > Make(std::shared_ptr< Texture > texture, OwningContext owning_context=OwningContext::kIO)
int32_t x
TEST_P(AiksTest, DrawAtlasNoColor)
float Scalar
Definition: scalar.h:18
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition: widgets.cc:50
TRect< Scalar > Rect
Definition: rect.h:792
TPoint< Scalar > Point
Definition: point.h:327
flutter::DlScalar DlScalar
Definition: dl_dispatcher.h:23
const Scalar stroke_width
const Scalar scale
Scalar blue
Definition: color.h:137
Scalar alpha
Definition: color.h:142
static constexpr Color Black()
Definition: color.h:265
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:277
Scalar red
Definition: color.h:127
static constexpr Color Red()
Definition: color.h:271
Scalar green
Definition: color.h:132
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:223
constexpr auto GetRight() const
Definition: rect.h:359
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136