| Flutter Impeller |

aiks_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 <array>
6 #include <cmath>
7 #include <cstdlib>
8 #include <memory>
9 #include <tuple>
10 #include <utility>
11 #include <vector>
12
13 #include "flutter/testing/testing.h"
14 #include "impeller/aiks/aiks_playground.h"
15 #include "impeller/aiks/canvas.h"
16 #include "impeller/aiks/image.h"
17 #include "impeller/aiks/image_filter.h"
18 #include "impeller/aiks/paint_pass_delegate.h"
19 #include "impeller/aiks/testing/context_spy.h"
20 #include "impeller/core/capture.h"
21 #include "impeller/entity/contents/color_source_contents.h"
22 #include "impeller/entity/contents/conical_gradient_contents.h"
23 #include "impeller/entity/contents/filters/inputs/filter_input.h"
24 #include "impeller/entity/contents/linear_gradient_contents.h"
25 #include "impeller/entity/contents/solid_color_contents.h"
26 #include "impeller/entity/contents/tiled_texture_contents.h"
27 #include "impeller/geometry/color.h"
28 #include "impeller/geometry/constants.h"
29 #include "impeller/geometry/geometry_asserts.h"
30 #include "impeller/geometry/matrix.h"
31 #include "impeller/geometry/path_builder.h"
32 #include "impeller/golden_tests/golden_playground_test.h"
33 #include "impeller/playground/widgets.h"
34 #include "impeller/renderer/command_buffer.h"
35 #include "impeller/renderer/snapshot.h"
36 #include "impeller/scene/material.h"
37 #include "impeller/scene/node.h"
38 #include "impeller/typographer/backends/skia/text_frame_skia.h"
39 #include "impeller/typographer/backends/skia/typographer_context_skia.h"
40 #include "impeller/typographer/backends/stb/text_frame_stb.h"
41 #include "impeller/typographer/backends/stb/typeface_stb.h"
42 #include "impeller/typographer/backends/stb/typographer_context_stb.h"
43 #include "third_party/imgui/imgui.h"
44 #include "third_party/skia/include/core/SkData.h"
45
46 namespace impeller {
47 namespace testing {
48
49 #ifdef IMPELLER_GOLDEN_TESTS
50 using AiksTest = GoldenPlaygroundTest;
51 #else
52 using AiksTest = AiksPlayground;
53 #endif
54 INSTANTIATE_PLAYGROUND_SUITE(AiksTest);
55
56 TEST_P(AiksTest, RotateColorFilteredPath) {
57 Canvas canvas;
58 canvas.Concat(Matrix::MakeTranslation({300, 300}));
59 canvas.Concat(Matrix::MakeRotationZ(Radians(kPiOver2)));
60 auto arrow_stem =
61 PathBuilder{}.MoveTo({120, 190}).LineTo({120, 50}).TakePath();
62 auto arrow_head = PathBuilder{}
63 .MoveTo({50, 120})
64 .LineTo({120, 190})
65 .LineTo({190, 120})
66 .TakePath();
67 auto paint = Paint{
68 .stroke_width = 15.0,
69 .stroke_cap = Cap::kRound,
70 .stroke_join = Join::kRound,
71 .style = Paint::Style::kStroke,
72 .color_filter =
73 ColorFilter::MakeBlend(BlendMode::kSourceIn, Color::AliceBlue()),
74 };
75
76 canvas.DrawPath(arrow_stem, paint);
77 canvas.DrawPath(arrow_head, paint);
78 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
79 }
80
81 TEST_P(AiksTest, CanvasCTMCanBeUpdated) {
82 Canvas canvas;
83 Matrix identity;
84 ASSERT_MATRIX_NEAR(canvas.GetCurrentTransformation(), identity);
85 canvas.Translate(Size{100, 100});
86 ASSERT_MATRIX_NEAR(canvas.GetCurrentTransformation(),
87 Matrix::MakeTranslation({100.0, 100.0, 0.0}));
88 }
89
90 TEST_P(AiksTest, CanvasCanPushPopCTM) {
91 Canvas canvas;
92 ASSERT_EQ(canvas.GetSaveCount(), 1u);
93 ASSERT_EQ(canvas.Restore(), false);
94
95 canvas.Translate(Size{100, 100});
96 canvas.Save();
97 ASSERT_EQ(canvas.GetSaveCount(), 2u);
98 ASSERT_MATRIX_NEAR(canvas.GetCurrentTransformation(),
99 Matrix::MakeTranslation({100.0, 100.0, 0.0}));
100 ASSERT_TRUE(canvas.Restore());
101 ASSERT_EQ(canvas.GetSaveCount(), 1u);
102 ASSERT_MATRIX_NEAR(canvas.GetCurrentTransformation(),
103 Matrix::MakeTranslation({100.0, 100.0, 0.0}));
104 }
105
106 TEST_P(AiksTest, CanRenderColoredRect) {
107 Canvas canvas;
108 Paint paint;
109 paint.color = Color::Blue();
110 canvas.DrawPath(PathBuilder{}
111 .AddRect(Rect::MakeXYWH(100.0, 100.0, 100.0, 100.0))
112 .TakePath(),
113 paint);
114 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
115 }
116
117 TEST_P(AiksTest, CanRenderImage) {
118 Canvas canvas;
119 Paint paint;
120 auto image = std::make_shared<Image>(CreateTextureForFixture("kalimba.jpg"));
121 paint.color = Color::Red();
122 canvas.DrawImage(image, Point::MakeXY(100.0, 100.0), paint);
123 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
124 }
125
126 TEST_P(AiksTest, CanRenderInvertedImage) {
127 Canvas canvas;
128 Paint paint;
129 auto image = std::make_shared<Image>(CreateTextureForFixture("kalimba.jpg"));
130 paint.color = Color::Red();
131 paint.invert_colors = true;
132 canvas.DrawImage(image, Point::MakeXY(100.0, 100.0), paint);
133 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
134 }
135
136 namespace {
137 bool GenerateMipmap(const std::shared_ptr<Context>& context,
138 std::shared_ptr<Texture> texture,
139 std::string label) {
140 auto buffer = context->CreateCommandBuffer();
141 if (!buffer) {
142 return false;
143 }
144 auto pass = buffer->CreateBlitPass();
145 if (!pass) {
146 return false;
147 }
148 pass->GenerateMipmap(std::move(texture), std::move(label));
149 pass->EncodeCommands(context->GetResourceAllocator());
150 return buffer->SubmitCommands();
151 }
152
153 void CanRenderTiledTexture(AiksTest* aiks_test,
154 Entity::TileMode tile_mode,
155 Matrix local_matrix = {}) {
156 auto context = aiks_test->GetContext();
157 ASSERT_TRUE(context);
158 auto texture = aiks_test->CreateTextureForFixture("table_mountain_nx.png",
159 /*enable_mipmapping=*/true);
160 GenerateMipmap(context, texture, "table_mountain_nx");
161 Canvas canvas;
162 canvas.Scale(aiks_test->GetContentScale());
163 canvas.Translate({100.0f, 100.0f, 0});
164 Paint paint;
165 paint.color_source =
166 ColorSource::MakeImage(texture, tile_mode, tile_mode, {}, local_matrix);
167 paint.color = Color(1, 1, 1, 1);
168 canvas.DrawRect({0, 0, 600, 600}, paint);
169
170 // Should not change the image.
171 constexpr auto stroke_width = 64;
172 paint.style = Paint::Style::kStroke;
173 paint.stroke_width = stroke_width;
174 if (tile_mode == Entity::TileMode::kDecal) {
175 canvas.DrawRect({stroke_width, stroke_width, 600, 600}, paint);
176 } else {
177 canvas.DrawRect({0, 0, 600, 600}, paint);
178 }
179
180 // Should not change the image.
181 PathBuilder path_builder;
182 path_builder.AddCircle({150, 150}, 150);
183 path_builder.AddRoundedRect(Rect::MakeLTRB(300, 300, 600, 600), 10);
184 paint.style = Paint::Style::kFill;
185 canvas.DrawPath(path_builder.TakePath(), paint);
186
187 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
188 }
189 } // namespace
190
191 TEST_P(AiksTest, CanRenderTiledTextureClamp) {
192 CanRenderTiledTexture(this, Entity::TileMode::kClamp);
193 }
194
195 TEST_P(AiksTest, CanRenderTiledTextureRepeat) {
196 CanRenderTiledTexture(this, Entity::TileMode::kRepeat);
197 }
198
199 TEST_P(AiksTest, CanRenderTiledTextureMirror) {
200 CanRenderTiledTexture(this, Entity::TileMode::kMirror);
201 }
202
203 TEST_P(AiksTest, CanRenderTiledTextureDecal) {
204 CanRenderTiledTexture(this, Entity::TileMode::kDecal);
205 }
206
207 TEST_P(AiksTest, CanRenderTiledTextureClampWithTranslate) {
208 CanRenderTiledTexture(this, Entity::TileMode::kClamp,
209 Matrix::MakeTranslation({172.f, 172.f, 0.f}));
210 }
211
212 TEST_P(AiksTest, CanRenderImageRect) {
213 Canvas canvas;
214 Paint paint;
215 auto image = std::make_shared<Image>(CreateTextureForFixture("kalimba.jpg"));
216 auto source_rect = Rect::MakeSize(Size(image->GetSize()));
217
218 // Render the bottom right quarter of the source image in a stretched rect.
219 source_rect.size.width /= 2;
220 source_rect.size.height /= 2;
221 source_rect.origin.x += source_rect.size.width;
222 source_rect.origin.y += source_rect.size.height;
223 canvas.DrawImageRect(image, source_rect, Rect::MakeXYWH(100, 100, 600, 600),
224 paint);
225 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
226 }
227
228 TEST_P(AiksTest, CanRenderStrokes) {
229 Canvas canvas;
230 Paint paint;
231 paint.color = Color::Red();
232 paint.stroke_width = 20.0;
233 paint.style = Paint::Style::kStroke;
234 canvas.DrawPath(PathBuilder{}.AddLine({200, 100}, {800, 100}).TakePath(),
235 paint);
236 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
237 }
238
239 TEST_P(AiksTest, CanRenderCurvedStrokes) {
240 Canvas canvas;
241 Paint paint;
242 paint.color = Color::Red();
243 paint.stroke_width = 25.0;
244 paint.style = Paint::Style::kStroke;
245 canvas.DrawPath(PathBuilder{}.AddCircle({500, 500}, 250).TakePath(), paint);
246 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
247 }
248
249 TEST_P(AiksTest, CanRenderClips) {
250 Canvas canvas;
251 Paint paint;
252 paint.color = Color::Fuchsia();
253 canvas.ClipPath(
254 PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 500, 500)).TakePath());
255 canvas.DrawPath(PathBuilder{}.AddCircle({500, 500}, 250).TakePath(), paint);
256 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
257 }
258
259 TEST_P(AiksTest, CanRenderNestedClips) {
260 Canvas canvas;
261 Paint paint;
262 paint.color = Color::Fuchsia();
263 canvas.Save();
264 canvas.ClipPath(PathBuilder{}.AddCircle({200, 400}, 300).TakePath());
265 canvas.Restore();
266 canvas.ClipPath(PathBuilder{}.AddCircle({600, 400}, 300).TakePath());
267 canvas.ClipPath(PathBuilder{}.AddCircle({400, 600}, 300).TakePath());
268 canvas.DrawRect(Rect::MakeXYWH(200, 200, 400, 400), paint);
269 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
270 }
271
272 TEST_P(AiksTest, CanRenderDifferenceClips) {
273 Paint paint;
274 Canvas canvas;
275 canvas.Translate({400, 400});
276
277 // Limit drawing to face circle with a clip.
278 canvas.ClipPath(PathBuilder{}.AddCircle(Point(), 200).TakePath());
279 canvas.Save();
280
281 // Cut away eyes/mouth using difference clips.
282 canvas.ClipPath(PathBuilder{}.AddCircle({-100, -50}, 30).TakePath(),
283 Entity::ClipOperation::kDifference);
284 canvas.ClipPath(PathBuilder{}.AddCircle({100, -50}, 30).TakePath(),
285 Entity::ClipOperation::kDifference);
286 canvas.ClipPath(PathBuilder{}
287 .AddQuadraticCurve({-100, 50}, {0, 150}, {100, 50})
288 .TakePath(),
289 Entity::ClipOperation::kDifference);
290
291 // Draw a huge yellow rectangle to prove the clipping works.
292 paint.color = Color::Yellow();
293 canvas.DrawRect(Rect::MakeXYWH(-1000, -1000, 2000, 2000), paint);
294
295 // Remove the difference clips and draw hair that partially covers the eyes.
296 canvas.Restore();
297 paint.color = Color::Maroon();
298 canvas.DrawPath(PathBuilder{}
299 .MoveTo({200, -200})
300 .HorizontalLineTo(-200)
301 .VerticalLineTo(-40)
302 .CubicCurveTo({0, -40}, {0, -80}, {200, -80})
303 .TakePath(),
304 paint);
305
306 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
307 }
308
309 TEST_P(AiksTest, CanRenderWithContiguousClipRestores) {
310 Canvas canvas;
311
312 // Cover the whole canvas with red.
313 canvas.DrawPaint({.color = Color::Red()});
314
315 canvas.Save();
316
317 // Append two clips, the second resulting in empty coverage.
318 canvas.ClipPath(
319 PathBuilder{}.AddRect(Rect::MakeXYWH(100, 100, 100, 100)).TakePath());
320 canvas.ClipPath(
321 PathBuilder{}.AddRect(Rect::MakeXYWH(300, 300, 100, 100)).TakePath());
322
323 // Restore to no clips.
324 canvas.Restore();
325
326 // Replace the whole canvas with green.
327 canvas.DrawPaint({.color = Color::Green()});
328
329 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
330 }
331
332 TEST_P(AiksTest, ClipsUseCurrentTransform) {
333 std::array<Color, 5> colors = {Color::White(), Color::Black(),
334 Color::SkyBlue(), Color::Red(),
335 Color::Yellow()};
336 Canvas canvas;
337 Paint paint;
338
339 canvas.Translate(Vector3(300, 300));
340 for (int i = 0; i < 15; i++) {
341 canvas.Scale(Vector3(0.8, 0.8));
342
343 paint.color = colors[i % colors.size()];
344 canvas.ClipPath(PathBuilder{}.AddCircle({0, 0}, 300).TakePath());
345 canvas.DrawRect(Rect(-300, -300, 600, 600), paint);
346 }
347 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
348 }
349
350 TEST_P(AiksTest, CanSaveLayerStandalone) {
351 Canvas canvas;
352
353 Paint red;
354 red.color = Color::Red();
355
356 Paint alpha;
357 alpha.color = Color::Red().WithAlpha(0.5);
358
359 canvas.SaveLayer(alpha);
360
361 canvas.DrawCircle({125, 125}, 125, red);
362
363 canvas.Restore();
364
365 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
366 }
367
368 namespace {
369 void CanRenderLinearGradient(AiksTest* aiks_test, Entity::TileMode tile_mode) {
370 Canvas canvas;
371 canvas.Scale(aiks_test->GetContentScale());
372 Paint paint;
373 canvas.Translate({100.0f, 0, 0});
374
375 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
376 Color{0.1294, 0.5882, 0.9529, 0.0}};
377 std::vector<Scalar> stops = {0.0, 1.0};
378
379 paint.color_source = ColorSource::MakeLinearGradient(
380 {0, 0}, {200, 200}, std::move(colors), std::move(stops), tile_mode, {});
381
382 paint.color = Color(1.0, 1.0, 1.0, 1.0);
383 canvas.DrawRect({0, 0, 600, 600}, paint);
384 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
385 }
386 } // namespace
387
388 TEST_P(AiksTest, CanRenderLinearGradientClamp) {
389 CanRenderLinearGradient(this, Entity::TileMode::kClamp);
390 }
391 TEST_P(AiksTest, CanRenderLinearGradientRepeat) {
392 CanRenderLinearGradient(this, Entity::TileMode::kRepeat);
393 }
394 TEST_P(AiksTest, CanRenderLinearGradientMirror) {
395 CanRenderLinearGradient(this, Entity::TileMode::kMirror);
396 }
397 TEST_P(AiksTest, CanRenderLinearGradientDecal) {
398 CanRenderLinearGradient(this, Entity::TileMode::kDecal);
399 }
400
401 TEST_P(AiksTest, CanRenderLinearGradientDecalWithColorFilter) {
402 Canvas canvas;
403 canvas.Scale(GetContentScale());
404 Paint paint;
405 canvas.Translate({100.0f, 0, 0});
406
407 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
408 Color{0.1294, 0.5882, 0.9529, 0.0}};
409 std::vector<Scalar> stops = {0.0, 1.0};
410
411 paint.color_source = ColorSource::MakeLinearGradient(
412 {0, 0}, {200, 200}, std::move(colors), std::move(stops),
413 Entity::TileMode::kDecal, {});
414 // Overlay the gradient with 25% green. This should appear as the entire
415 // rectangle being drawn with 25% green, including the border area outside the
416 // decal gradient.
417 paint.color_filter = ColorFilter::MakeBlend(BlendMode::kSourceOver,
418 Color::Green().WithAlpha(0.25));
419
420 paint.color = Color(1.0, 1.0, 1.0, 1.0);
421 canvas.DrawRect({0, 0, 600, 600}, paint);
422 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
423 }
424
425 static void CanRenderLinearGradientWithDithering(AiksTest* aiks_test,
426 bool use_dithering) {
427 Canvas canvas;
428 Paint paint;
429 canvas.Translate({100.0, 100.0, 0});
430
431 // 0xffcccccc --> 0xff333333, taken from
432 // https://github.com/flutter/flutter/issues/118073#issue-1521699748
433 std::vector<Color> colors = {Color{0.8, 0.8, 0.8, 1.0},
434 Color{0.2, 0.2, 0.2, 1.0}};
435 std::vector<Scalar> stops = {0.0, 1.0};
436
437 paint.color_source = ColorSource::MakeLinearGradient(
438 {0, 0}, {800, 500}, std::move(colors), std::move(stops),
439 Entity::TileMode::kClamp, {});
440 paint.dither = use_dithering;
441 canvas.DrawRect({0, 0, 800, 500}, paint);
442 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
443 }
444
445 TEST_P(AiksTest, CanRenderLinearGradientWithDitheringDisabled) {
446 CanRenderLinearGradientWithDithering(this, false);
447 }
448
449 TEST_P(AiksTest, CanRenderLinearGradientWithDitheringEnabled) {
450 CanRenderLinearGradientWithDithering(this, true);
451 } // namespace
452
453 static void CanRenderRadialGradientWithDithering(AiksTest* aiks_test,
454 bool use_dithering) {
455 Canvas canvas;
456 Paint paint;
457 canvas.Translate({100.0, 100.0, 0});
458
459 // #FFF -> #000
460 std::vector<Color> colors = {Color{1.0, 1.0, 1.0, 1.0},
461 Color{0.0, 0.0, 0.0, 1.0}};
462 std::vector<Scalar> stops = {0.0, 1.0};
463
464 paint.color_source = ColorSource::MakeRadialGradient(
465 {600, 600}, 600, std::move(colors), std::move(stops),
466 Entity::TileMode::kClamp, {});
467 paint.dither = use_dithering;
468 canvas.DrawRect({0, 0, 1200, 1200}, paint);
469 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
470 }
471
472 TEST_P(AiksTest, CanRenderRadialGradientWithDitheringDisabled) {
473 CanRenderRadialGradientWithDithering(this, false);
474 }
475
476 TEST_P(AiksTest, CanRenderRadialGradientWithDitheringEnabled) {
477 CanRenderRadialGradientWithDithering(this, true);
478 }
479
480 static void CanRenderSweepGradientWithDithering(AiksTest* aiks_test,
481 bool use_dithering) {
482 Canvas canvas;
483 canvas.Scale(aiks_test->GetContentScale());
484 Paint paint;
485 canvas.Translate({100.0, 100.0, 0});
486
487 // #FFF -> #000
488 std::vector<Color> colors = {Color{1.0, 1.0, 1.0, 1.0},
489 Color{0.0, 0.0, 0.0, 1.0}};
490 std::vector<Scalar> stops = {0.0, 1.0};
491
492 paint.color_source = ColorSource::MakeSweepGradient(
493 {100, 100}, Degrees(45), Degrees(135), std::move(colors),
494 std::move(stops), Entity::TileMode::kMirror, {});
495 paint.dither = use_dithering;
496
497 canvas.DrawRect({0, 0, 600, 600}, paint);
498 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
499 }
500
501 TEST_P(AiksTest, CanRenderSweepGradientWithDitheringDisabled) {
502 CanRenderSweepGradientWithDithering(this, false);
503 }
504
505 TEST_P(AiksTest, CanRenderSweepGradientWithDitheringEnabled) {
506 CanRenderSweepGradientWithDithering(this, true);
507 }
508
509 static void CanRenderConicalGradientWithDithering(AiksTest* aiks_test,
510 bool use_dithering) {
511 Canvas canvas;
512 canvas.Scale(aiks_test->GetContentScale());
513 Paint paint;
514 canvas.Translate({100.0, 100.0, 0});
515
516 // #FFF -> #000
517 std::vector<Color> colors = {Color{1.0, 1.0, 1.0, 1.0},
518 Color{0.0, 0.0, 0.0, 1.0}};
519 std::vector<Scalar> stops = {0.0, 1.0};
520
521 paint.color_source = ColorSource::MakeConicalGradient(
522 {100, 100}, 100, std::move(colors), std::move(stops), {0, 1}, 0,
523 Entity::TileMode::kMirror, {});
524 paint.dither = use_dithering;
525
526 canvas.DrawRect({0, 0, 600, 600}, paint);
527 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
528 }
529
530 TEST_P(AiksTest, CanRenderConicalGradientWithDitheringDisabled) {
531 CanRenderConicalGradientWithDithering(this, false);
532 }
533
534 TEST_P(AiksTest, CanRenderConicalGradientWithDitheringEnabled) {
535 CanRenderConicalGradientWithDithering(this, true);
536 }
537
538 namespace {
539 void CanRenderLinearGradientWithOverlappingStops(AiksTest* aiks_test,
540 Entity::TileMode tile_mode) {
541 Canvas canvas;
542 Paint paint;
543 canvas.Translate({100.0, 100.0, 0});
544
545 std::vector<Color> colors = {
546 Color{0.9568, 0.2627, 0.2118, 1.0}, Color{0.9568, 0.2627, 0.2118, 1.0},
547 Color{0.1294, 0.5882, 0.9529, 1.0}, Color{0.1294, 0.5882, 0.9529, 1.0}};
548 std::vector<Scalar> stops = {0.0, 0.5, 0.5, 1.0};
549
550 paint.color_source = ColorSource::MakeLinearGradient(
551 {0, 0}, {500, 500}, std::move(colors), std::move(stops), tile_mode, {});
552
553 paint.color = Color(1.0, 1.0, 1.0, 1.0);
554 canvas.DrawRect({0, 0, 500, 500}, paint);
555 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
556 }
557 } // namespace
558
559 // Only clamp is necessary. All tile modes are the same output.
560 TEST_P(AiksTest, CanRenderLinearGradientWithOverlappingStopsClamp) {
561 CanRenderLinearGradientWithOverlappingStops(this, Entity::TileMode::kClamp);
562 }
563
564 namespace {
565 void CanRenderLinearGradientManyColors(AiksTest* aiks_test,
566 Entity::TileMode tile_mode) {
567 Canvas canvas;
568 canvas.Scale(aiks_test->GetContentScale());
569 Paint paint;
570 canvas.Translate({100, 100, 0});
571
572 std::vector<Color> colors = {
573 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
574 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
575 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
576 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
577 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
578 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
579 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
580 std::vector<Scalar> stops = {
581 0.0,
582 (1.0 / 6.0) * 1,
583 (1.0 / 6.0) * 2,
584 (1.0 / 6.0) * 3,
585 (1.0 / 6.0) * 4,
586 (1.0 / 6.0) * 5,
587 1.0,
588 };
589
590 paint.color_source = ColorSource::MakeLinearGradient(
591 {0, 0}, {200, 200}, std::move(colors), std::move(stops), tile_mode, {});
592
593 paint.color = Color(1.0, 1.0, 1.0, 1.0);
594 canvas.DrawRect({0, 0, 600, 600}, paint);
595 canvas.Restore();
596 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
597 }
598 } // namespace
599
600 TEST_P(AiksTest, CanRenderLinearGradientManyColorsClamp) {
601 CanRenderLinearGradientManyColors(this, Entity::TileMode::kClamp);
602 }
603 TEST_P(AiksTest, CanRenderLinearGradientManyColorsRepeat) {
604 CanRenderLinearGradientManyColors(this, Entity::TileMode::kRepeat);
605 }
606 TEST_P(AiksTest, CanRenderLinearGradientManyColorsMirror) {
607 CanRenderLinearGradientManyColors(this, Entity::TileMode::kMirror);
608 }
609 TEST_P(AiksTest, CanRenderLinearGradientManyColorsDecal) {
610 CanRenderLinearGradientManyColors(this, Entity::TileMode::kDecal);
611 }
612
613 namespace {
614 void CanRenderLinearGradientWayManyColors(AiksTest* aiks_test,
615 Entity::TileMode tile_mode) {
616 Canvas canvas;
617 Paint paint;
618 canvas.Translate({100.0, 100.0, 0});
619 auto color = Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0};
620 std::vector<Color> colors;
621 std::vector<Scalar> stops;
622 auto current_stop = 0.0;
623 for (int i = 0; i < 2000; i++) {
624 colors.push_back(color);
625 stops.push_back(current_stop);
626 current_stop += 1 / 2000.0;
627 }
628 stops[2000 - 1] = 1.0;
629
630 paint.color_source = ColorSource::MakeLinearGradient(
631 {0, 0}, {200, 200}, std::move(colors), std::move(stops), tile_mode, {});
632
633 canvas.DrawRect({0, 0, 600, 600}, paint);
634 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
635 }
636 } // namespace
637
638 // Only test clamp on purpose since they all look the same.
639 TEST_P(AiksTest, CanRenderLinearGradientWayManyColorsClamp) {
640 CanRenderLinearGradientWayManyColors(this, Entity::TileMode::kClamp);
641 }
642
643 TEST_P(AiksTest, CanRenderLinearGradientManyColorsUnevenStops) {
644 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
645 const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
646 const Entity::TileMode tile_modes[] = {
647 Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
648 Entity::TileMode::kMirror, Entity::TileMode::kDecal};
649
650 static int selected_tile_mode = 0;
651 ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
652 ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
653 sizeof(tile_mode_names) / sizeof(char*));
654 static Matrix matrix = {
655 1, 0, 0, 0, //
656 0, 1, 0, 0, //
657 0, 0, 1, 0, //
658 0, 0, 0, 1 //
659 };
660 std::string label = "##1";
661 for (int i = 0; i < 4; i++) {
662 ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, &(matrix.vec[i]),
663 4, NULL, NULL, "%.2f", 0);
664 label[2]++;
665 }
666 ImGui::End();
667
668 Canvas canvas;
669 Paint paint;
670 canvas.Translate({100.0, 100.0, 0});
671 auto tile_mode = tile_modes[selected_tile_mode];
672
673 std::vector<Color> colors = {
674 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
675 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
676 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
677 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
678 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
679 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
680 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
681 std::vector<Scalar> stops = {
682 0.0, 2.0 / 62.0, 4.0 / 62.0, 8.0 / 62.0, 16.0 / 62.0, 32.0 / 62.0, 1.0,
683 };
684
685 paint.color_source = ColorSource::MakeLinearGradient(
686 {0, 0}, {200, 200}, std::move(colors), std::move(stops), tile_mode, {});
687
688 canvas.DrawRect({0, 0, 600, 600}, paint);
689 return canvas.EndRecordingAsPicture();
690 };
691 ASSERT_TRUE(OpenPlaygroundHere(callback));
692 }
693
694 TEST_P(AiksTest, CanRenderLinearGradientMaskBlur) {
695 Canvas canvas;
696
697 Paint paint = {
698 .color = Color::White(),
699 .color_source = ColorSource::MakeLinearGradient(
700 {200, 200}, {400, 400},
701 {Color::Red(), Color::White(), Color::Red(), Color::White(),
702 Color::Red(), Color::White(), Color::Red(), Color::White(),
703 Color::Red(), Color::White(), Color::Red()},
704 {0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0},
705 Entity::TileMode::kClamp, {}),
706 .mask_blur_descriptor =
707 Paint::MaskBlurDescriptor{
708 .style = FilterContents::BlurStyle::kNormal,
709 .sigma = Sigma(20),
710 },
711 };
712
713 canvas.DrawCircle({300, 300}, 200, paint);
714 canvas.DrawRect(Rect::MakeLTRB(100, 300, 500, 600), paint);
715
716 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
717 }
718
719 TEST_P(AiksTest, CanRenderRadialGradient) {
720 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
721 const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
722 const Entity::TileMode tile_modes[] = {
723 Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
724 Entity::TileMode::kMirror, Entity::TileMode::kDecal};
725
726 static int selected_tile_mode = 0;
727 ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
728 ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
729 sizeof(tile_mode_names) / sizeof(char*));
730 static Matrix matrix = {
731 1, 0, 0, 0, //
732 0, 1, 0, 0, //
733 0, 0, 1, 0, //
734 0, 0, 0, 1 //
735 };
736 std::string label = "##1";
737 for (int i = 0; i < 4; i++) {
738 ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, &(matrix.vec[i]),
739 4, NULL, NULL, "%.2f", 0);
740 label[2]++;
741 }
742 ImGui::End();
743
744 Canvas canvas;
745 Paint paint;
746 canvas.Translate({100.0, 100.0, 0});
747 auto tile_mode = tile_modes[selected_tile_mode];
748
749 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
750 Color{0.1294, 0.5882, 0.9529, 1.0}};
751 std::vector<Scalar> stops = {0.0, 1.0};
752
753 paint.color_source = ColorSource::MakeRadialGradient(
754 {100, 100}, 100, std::move(colors), std::move(stops), tile_mode, {});
755
756 canvas.DrawRect({0, 0, 600, 600}, paint);
757 return canvas.EndRecordingAsPicture();
758 };
759 ASSERT_TRUE(OpenPlaygroundHere(callback));
760 }
761
762 TEST_P(AiksTest, CanRenderRadialGradientManyColors) {
763 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
764 const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
765 const Entity::TileMode tile_modes[] = {
766 Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
767 Entity::TileMode::kMirror, Entity::TileMode::kDecal};
768
769 static int selected_tile_mode = 0;
770 ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
771 ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
772 sizeof(tile_mode_names) / sizeof(char*));
773 static Matrix matrix = {
774 1, 0, 0, 0, //
775 0, 1, 0, 0, //
776 0, 0, 1, 0, //
777 0, 0, 0, 1 //
778 };
779 std::string label = "##1";
780 for (int i = 0; i < 4; i++) {
781 ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, &(matrix.vec[i]),
782 4, NULL, NULL, "%.2f", 0);
783 label[2]++;
784 }
785 ImGui::End();
786
787 Canvas canvas;
788 Paint paint;
789 canvas.Translate({100.0, 100.0, 0});
790 auto tile_mode = tile_modes[selected_tile_mode];
791
792 std::vector<Color> colors = {
793 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
794 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
795 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
796 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
797 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
798 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
799 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
800 std::vector<Scalar> stops = {
801 0.0,
802 (1.0 / 6.0) * 1,
803 (1.0 / 6.0) * 2,
804 (1.0 / 6.0) * 3,
805 (1.0 / 6.0) * 4,
806 (1.0 / 6.0) * 5,
807 1.0,
808 };
809
810 paint.color_source = ColorSource::MakeRadialGradient(
811 {100, 100}, 100, std::move(colors), std::move(stops), tile_mode, {});
812
813 canvas.DrawRect({0, 0, 600, 600}, paint);
814 return canvas.EndRecordingAsPicture();
815 };
816 ASSERT_TRUE(OpenPlaygroundHere(callback));
817 }
818
819 namespace {
820 void CanRenderSweepGradient(AiksTest* aiks_test, Entity::TileMode tile_mode) {
821 Canvas canvas;
822 canvas.Scale(aiks_test->GetContentScale());
823 Paint paint;
824 canvas.Translate({100, 100, 0});
825
826 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
827 Color{0.1294, 0.5882, 0.9529, 1.0}};
828 std::vector<Scalar> stops = {0.0, 1.0};
829
830 paint.color_source = ColorSource::MakeSweepGradient(
831 {100, 100}, Degrees(45), Degrees(135), std::move(colors),
832 std::move(stops), tile_mode, {});
833
834 canvas.DrawRect({0, 0, 600, 600}, paint);
835 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
836 }
837 } // namespace
838
839 TEST_P(AiksTest, CanRenderSweepGradientClamp) {
840 CanRenderSweepGradient(this, Entity::TileMode::kClamp);
841 }
842 TEST_P(AiksTest, CanRenderSweepGradientRepeat) {
843 CanRenderSweepGradient(this, Entity::TileMode::kRepeat);
844 }
845 TEST_P(AiksTest, CanRenderSweepGradientMirror) {
846 CanRenderSweepGradient(this, Entity::TileMode::kMirror);
847 }
848 TEST_P(AiksTest, CanRenderSweepGradientDecal) {
849 CanRenderSweepGradient(this, Entity::TileMode::kDecal);
850 }
851
852 namespace {
853 void CanRenderSweepGradientManyColors(AiksTest* aiks_test,
854 Entity::TileMode tile_mode) {
855 Canvas canvas;
856 Paint paint;
857 canvas.Translate({100.0, 100.0, 0});
858
859 std::vector<Color> colors = {
860 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
861 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
862 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
863 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
864 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
865 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
866 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
867 std::vector<Scalar> stops = {
868 0.0,
869 (1.0 / 6.0) * 1,
870 (1.0 / 6.0) * 2,
871 (1.0 / 6.0) * 3,
872 (1.0 / 6.0) * 4,
873 (1.0 / 6.0) * 5,
874 1.0,
875 };
876
877 paint.color_source = ColorSource::MakeSweepGradient(
878 {100, 100}, Degrees(45), Degrees(135), std::move(colors),
879 std::move(stops), tile_mode, {});
880
881 canvas.DrawRect({0, 0, 600, 600}, paint);
882 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
883 }
884 } // namespace
885
886 TEST_P(AiksTest, CanRenderSweepGradientManyColorsClamp) {
887 CanRenderSweepGradientManyColors(this, Entity::TileMode::kClamp);
888 }
889 TEST_P(AiksTest, CanRenderSweepGradientManyColorsRepeat) {
890 CanRenderSweepGradientManyColors(this, Entity::TileMode::kRepeat);
891 }
892 TEST_P(AiksTest, CanRenderSweepGradientManyColorsMirror) {
893 CanRenderSweepGradientManyColors(this, Entity::TileMode::kMirror);
894 }
895 TEST_P(AiksTest, CanRenderSweepGradientManyColorsDecal) {
896 CanRenderSweepGradientManyColors(this, Entity::TileMode::kDecal);
897 }
898
899 TEST_P(AiksTest, CanRenderConicalGradient) {
900 Scalar size = 256;
901 Canvas canvas;
902 Paint paint;
903 paint.color = Color::White();
904 canvas.DrawRect({0, 0, size * 3, size * 3}, paint);
905 std::vector<Color> colors = {Color::MakeRGBA8(0xF4, 0x43, 0x36, 0xFF),
906 Color::MakeRGBA8(0xFF, 0xEB, 0x3B, 0xFF),
907 Color::MakeRGBA8(0x4c, 0xAF, 0x50, 0xFF),
908 Color::MakeRGBA8(0x21, 0x96, 0xF3, 0xFF)};
909 std::vector<Scalar> stops = {0.0, 1.f / 3.f, 2.f / 3.f, 1.0};
910 std::array<std::tuple<Point, float, Point, float>, 8> array{
911 std::make_tuple(Point{size / 2.f, size / 2.f}, 0.f,
912 Point{size / 2.f, size / 2.f}, size / 2.f),
913 std::make_tuple(Point{size / 2.f, size / 2.f}, size / 4.f,
914 Point{size / 2.f, size / 2.f}, size / 2.f),
915 std::make_tuple(Point{size / 4.f, size / 4.f}, 0.f,
916 Point{size / 2.f, size / 2.f}, size / 2.f),
917 std::make_tuple(Point{size / 4.f, size / 4.f}, size / 2.f,
918 Point{size / 2.f, size / 2.f}, 0),
919 std::make_tuple(Point{size / 4.f, size / 4.f}, size / 4.f,
920 Point{size / 2.f, size / 2.f}, size / 2.f),
921 std::make_tuple(Point{size / 4.f, size / 4.f}, size / 16.f,
922 Point{size / 2.f, size / 2.f}, size / 8.f),
923 std::make_tuple(Point{size / 4.f, size / 4.f}, size / 8.f,
924 Point{size / 2.f, size / 2.f}, size / 16.f),
925 std::make_tuple(Point{size / 8.f, size / 8.f}, size / 8.f,
926 Point{size / 2.f, size / 2.f}, size / 8.f),
927 };
928 for (int i = 0; i < 8; i++) {
929 canvas.Save();
930 canvas.Translate({(i % 3) * size, i / 3 * size, 0});
931 paint.color_source = ColorSource::MakeConicalGradient(
932 std::get<0>(array[i]), std::get<1>(array[i]), colors, stops,
933 std::get<2>(array[i]), std::get<3>(array[i]), Entity::TileMode::kClamp,
934 {});
935 canvas.DrawRect({0, 0, size, size}, paint);
936 canvas.Restore();
937 }
938 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
939 }
940
941 TEST_P(AiksTest, CanRenderGradientDecalWithBackground) {
942 std::vector<Color> colors = {Color::MakeRGBA8(0xF4, 0x43, 0x36, 0xFF),
943 Color::MakeRGBA8(0xFF, 0xEB, 0x3B, 0xFF),
944 Color::MakeRGBA8(0x4c, 0xAF, 0x50, 0xFF),
945 Color::MakeRGBA8(0x21, 0x96, 0xF3, 0xFF)};
946 std::vector<Scalar> stops = {0.0, 1.f / 3.f, 2.f / 3.f, 1.0};
947
948 std::array<ColorSource, 3> color_sources = {
949 ColorSource::MakeLinearGradient({0, 0}, {100, 100}, colors, stops,
950 Entity::TileMode::kDecal, {}),
951 ColorSource::MakeRadialGradient({100, 100}, 100, colors, stops,
952 Entity::TileMode::kDecal, {}),
953 ColorSource::MakeSweepGradient({100, 100}, Degrees(45), Degrees(135),
954 colors, stops, Entity::TileMode::kDecal,
955 {}),
956 };
957
958 Canvas canvas;
959 Paint paint;
960 paint.color = Color::White();
961 canvas.DrawRect(Rect::MakeLTRB(0, 0, 605, 205), paint);
962 for (int i = 0; i < 3; i++) {
963 canvas.Save();
964 canvas.Translate({i * 200.0f, 0, 0});
965 paint.color_source = color_sources[i];
966 canvas.DrawRect(Rect::MakeLTRB(0, 0, 200, 200), paint);
967 canvas.Restore();
968 }
969 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
970 }
971
972 TEST_P(AiksTest, CanRenderDifferentShapesWithSameColorSource) {
973 Canvas canvas;
974 Paint paint;
975
976 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
977 Color{0.1294, 0.5882, 0.9529, 1.0}};
978 std::vector<Scalar> stops = {
979 0.0,
980 1.0,
981 };
982
983 paint.color_source = ColorSource::MakeLinearGradient(
984 {0, 0}, {100, 100}, std::move(colors), std::move(stops),
985 Entity::TileMode::kRepeat, {});
986
987 canvas.Save();
988 canvas.Translate({100, 100, 0});
989 canvas.DrawRect({0, 0, 200, 200}, paint);
990 canvas.Restore();
991
992 canvas.Save();
993 canvas.Translate({100, 400, 0});
994 canvas.DrawCircle({100, 100}, 100, paint);
995 canvas.Restore();
996 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
997 }
998
999 TEST_P(AiksTest, CanPictureConvertToImage) {
1000 Canvas recorder_canvas;
1001 Paint paint;
1002 paint.color = Color{0.9568, 0.2627, 0.2118, 1.0};
1003 recorder_canvas.DrawRect({100.0, 100.0, 600, 600}, paint);
1004 paint.color = Color{0.1294, 0.5882, 0.9529, 1.0};
1005 recorder_canvas.DrawRect({200.0, 200.0, 600, 600}, paint);
1006
1007 Canvas canvas;
1008 AiksContext renderer(GetContext(), nullptr);
1009 paint.color = Color::BlackTransparent();
1010 canvas.DrawPaint(paint);
1011 Picture picture = recorder_canvas.EndRecordingAsPicture();
1012 auto image = picture.ToImage(renderer, ISize{1000, 1000});
1013 if (image) {
1014 canvas.DrawImage(image, Point(), Paint());
1015 paint.color = Color{0.1, 0.1, 0.1, 0.2};
1016 canvas.DrawRect(Rect::MakeSize(ISize{1000, 1000}), paint);
1017 }
1018
1019 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1020 }
1021
1022 TEST_P(AiksTest, BlendModeShouldCoverWholeScreen) {
1023 Canvas canvas;
1024 Paint paint;
1025
1026 paint.color = Color::Red();
1027 canvas.DrawPaint(paint);
1028
1029 paint.blend_mode = BlendMode::kSourceOver;
1030 canvas.SaveLayer(paint);
1031
1032 paint.color = Color::White();
1033 canvas.DrawRect({100, 100, 400, 400}, paint);
1034
1035 paint.blend_mode = BlendMode::kSource;
1036 canvas.SaveLayer(paint);
1037
1038 paint.color = Color::Blue();
1039 canvas.DrawRect({200, 200, 200, 200}, paint);
1040
1041 canvas.Restore();
1042 canvas.Restore();
1043
1044 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1045 }
1046
1047 TEST_P(AiksTest, CanRenderGroupOpacity) {
1048 Canvas canvas;
1049
1050 Paint red;
1051 red.color = Color::Red();
1052 Paint green;
1053 green.color = Color::Green().WithAlpha(0.5);
1054 Paint blue;
1055 blue.color = Color::Blue();
1056
1057 Paint alpha;
1058 alpha.color = Color::Red().WithAlpha(0.5);
1059
1060 canvas.SaveLayer(alpha);
1061
1062 canvas.DrawRect({000, 000, 100, 100}, red);
1063 canvas.DrawRect({020, 020, 100, 100}, green);
1064 canvas.DrawRect({040, 040, 100, 100}, blue);
1065
1066 canvas.Restore();
1067
1068 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1069 }
1070
1071 TEST_P(AiksTest, CoordinateConversionsAreCorrect) {
1072 Canvas canvas;
1073
1074 // Render a texture directly.
1075 {
1076 Paint paint;
1077 auto image =
1078 std::make_shared<Image>(CreateTextureForFixture("kalimba.jpg"));
1079 paint.color = Color::Red();
1080
1081 canvas.Save();
1082 canvas.Translate({100, 200, 0});
1083 canvas.Scale(Vector2{0.5, 0.5});
1084 canvas.DrawImage(image, Point::MakeXY(100.0, 100.0), paint);
1085 canvas.Restore();
1086 }
1087
1088 // Render an offscreen rendered texture.
1089 {
1090 Paint red;
1091 red.color = Color::Red();
1092 Paint green;
1093 green.color = Color::Green();
1094 Paint blue;
1095 blue.color = Color::Blue();
1096
1097 Paint alpha;
1098 alpha.color = Color::Red().WithAlpha(0.5);
1099
1100 canvas.SaveLayer(alpha);
1101
1102 canvas.DrawRect({000, 000, 100, 100}, red);
1103 canvas.DrawRect({020, 020, 100, 100}, green);
1104 canvas.DrawRect({040, 040, 100, 100}, blue);
1105
1106 canvas.Restore();
1107 }
1108
1109 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1110 }
1111
1112 TEST_P(AiksTest, CanPerformFullScreenMSAA) {
1113 Canvas canvas;
1114
1115 Paint red;
1116 red.color = Color::Red();
1117
1118 canvas.DrawCircle({250, 250}, 125, red);
1119
1120 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1121 }
1122
1123 TEST_P(AiksTest, CanPerformSkew) {
1124 Canvas canvas;
1125
1126 Paint red;
1127 red.color = Color::Red();
1128
1129 canvas.Skew(2, 5);
1130 canvas.DrawRect(Rect::MakeXYWH(0, 0, 100, 100), red);
1131
1132 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1133 }
1134
1135 TEST_P(AiksTest, CanPerformSaveLayerWithBounds) {
1136 Canvas canvas;
1137
1138 Paint red;
1139 red.color = Color::Red();
1140
1141 Paint green;
1142 green.color = Color::Green();
1143
1144 Paint blue;
1145 blue.color = Color::Blue();
1146
1147 Paint save;
1148 save.color = Color::Black();
1149
1150 canvas.SaveLayer(save, Rect{0, 0, 50, 50});
1151
1152 canvas.DrawRect({0, 0, 100, 100}, red);
1153 canvas.DrawRect({10, 10, 100, 100}, green);
1154 canvas.DrawRect({20, 20, 100, 100}, blue);
1155
1156 canvas.Restore();
1157
1158 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1159 }
1160
1161 TEST_P(AiksTest,
1162 CanPerformSaveLayerWithBoundsAndLargerIntermediateIsNotAllocated) {
1163 Canvas canvas;
1164
1165 Paint red;
1166 red.color = Color::Red();
1167
1168 Paint green;
1169 green.color = Color::Green();
1170
1171 Paint blue;
1172 blue.color = Color::Blue();
1173
1174 Paint save;
1175 save.color = Color::Black().WithAlpha(0.5);
1176
1177 canvas.SaveLayer(save, Rect{0, 0, 100000, 100000});
1178
1179 canvas.DrawRect({0, 0, 100, 100}, red);
1180 canvas.DrawRect({10, 10, 100, 100}, green);
1181 canvas.DrawRect({20, 20, 100, 100}, blue);
1182
1183 canvas.Restore();
1184
1185 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1186 }
1187
1188 TEST_P(AiksTest, CanRenderRoundedRectWithNonUniformRadii) {
1189 Canvas canvas;
1190
1191 Paint paint;
1192 paint.color = Color::Red();
1193
1194 PathBuilder::RoundingRadii radii;
1195 radii.top_left = {50, 25};
1196 radii.top_right = {25, 50};
1197 radii.bottom_right = {50, 25};
1198 radii.bottom_left = {25, 50};
1199
1200 auto path =
1201 PathBuilder{}.AddRoundedRect(Rect{100, 100, 500, 500}, radii).TakePath();
1202
1203 canvas.DrawPath(path, paint);
1204
1205 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1206 }
1207
1208 TEST_P(AiksTest, CanRenderDifferencePaths) {
1209 Canvas canvas;
1210
1211 Paint paint;
1212 paint.color = Color::Red();
1213
1214 PathBuilder builder;
1215
1216 PathBuilder::RoundingRadii radii;
1217 radii.top_left = {50, 25};
1218 radii.top_right = {25, 50};
1219 radii.bottom_right = {50, 25};
1220 radii.bottom_left = {25, 50};
1221
1222 builder.AddRoundedRect({100, 100, 200, 200}, radii);
1223 builder.AddCircle({200, 200}, 50);
1224 auto path = builder.TakePath(FillType::kOdd);
1225
1226 canvas.DrawImage(
1227 std::make_shared<Image>(CreateTextureForFixture("boston.jpg")), {10, 10},
1228 Paint{});
1229 canvas.DrawPath(path, paint);
1230
1231 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1232 }
1233
1234 // Regression test for https://github.com/flutter/flutter/issues/134816.
1235 //
1236 // It should be possible to draw 3 lines, and not have an implicit close path.
1237 TEST_P(AiksTest, CanDrawAnOpenPath) {
1238 Canvas canvas;
1239
1240 // Starting at (50, 50), draw lines from:
1241 // 1. (50, height)
1242 // 2. (width, height)
1243 // 3. (width, 50)
1244 PathBuilder builder;
1245 builder.MoveTo({50, 50});
1246 builder.LineTo({50, 100});
1247 builder.LineTo({100, 100});
1248 builder.LineTo({100, 50});
1249
1250 Paint paint;
1251 paint.color = Color::Red();
1252 paint.style = Paint::Style::kStroke;
1253 paint.stroke_width = 10;
1254
1255 canvas.DrawPath(builder.TakePath(), paint);
1256
1257 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1258 }
1259
1260 TEST_P(AiksTest, CanDrawAnOpenPathThatIsntARect) {
1261 Canvas canvas;
1262
1263 // Draw a stroked path that is explicitly closed to verify
1264 // It doesn't become a rectangle.
1265 PathBuilder builder;
1266 builder.MoveTo({50, 50});
1267 builder.LineTo({520, 120});
1268 builder.LineTo({300, 310});
1269 builder.LineTo({100, 50});
1270 builder.Close();
1271
1272 Paint paint;
1273 paint.color = Color::Red();
1274 paint.style = Paint::Style::kStroke;
1275 paint.stroke_width = 10;
1276
1277 canvas.DrawPath(builder.TakePath(), paint);
1278
1279 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1280 }
1281
1282 static sk_sp<SkData> OpenFixtureAsSkData(const char* fixture_name) {
1283 auto mapping = flutter::testing::OpenFixtureAsMapping(fixture_name);
1284 if (!mapping) {
1285 return nullptr;
1286 }
1287 auto data = SkData::MakeWithProc(
1288 mapping->GetMapping(), mapping->GetSize(),
1289 [](const void* ptr, void* context) {
1290 delete reinterpret_cast<fml::Mapping*>(context);
1291 },
1292 mapping.get());
1293 mapping.release();
1294 return data;
1295 }
1296
1297 struct TextRenderOptions {
1298 Scalar font_size = 50;
1299 Scalar alpha = 1;
1300 Point position = Vector2(100, 200);
1301 };
1302
1303 bool RenderTextInCanvasSkia(const std::shared_ptr<Context>& context,
1304 Canvas& canvas,
1305 const std::string& text,
1306 const std::string& font_fixture,
1307 TextRenderOptions options = {}) {
1308 // Draw the baseline.
1309 canvas.DrawRect({options.position.x - 50, options.position.y, 900, 10},
1310 Paint{.color = Color::Aqua().WithAlpha(0.25)});
1311
1312 // Mark the point at which the text is drawn.
1313 canvas.DrawCircle(options.position, 5.0,
1314 Paint{.color = Color::Red().WithAlpha(0.25)});
1315
1316 // Construct the text blob.
1317 auto mapping = OpenFixtureAsSkData(font_fixture.c_str());
1318 if (!mapping) {
1319 return false;
1320 }
1321 SkFont sk_font(SkTypeface::MakeFromData(mapping), options.font_size);
1322 auto blob = SkTextBlob::MakeFromString(text.c_str(), sk_font);
1323 if (!blob) {
1324 return false;
1325 }
1326
1327 // Create the Impeller text frame and draw it at the designated baseline.
1328 auto frame = MakeTextFrameFromTextBlobSkia(blob);
1329
1330 Paint text_paint;
1331 text_paint.color = Color::Yellow().WithAlpha(options.alpha);
1332 canvas.DrawTextFrame(frame, options.position, text_paint);
1333 return true;
1334 }
1335
1336 bool RenderTextInCanvasSTB(const std::shared_ptr<Context>& context,
1337 Canvas& canvas,
1338 const std::string& text,
1339 const std::string& font_fixture,
1340 TextRenderOptions options = {}) {
1341 // Draw the baseline.
1342 canvas.DrawRect({options.position.x - 50, options.position.y, 900, 10},
1343 Paint{.color = Color::Aqua().WithAlpha(0.25)});
1344
1345 // Mark the point at which the text is drawn.
1346 canvas.DrawCircle(options.position, 5.0,
1347 Paint{.color = Color::Red().WithAlpha(0.25)});
1348
1349 // Construct the text blob.
1350 auto mapping = flutter::testing::OpenFixtureAsMapping(font_fixture.c_str());
1351 if (!mapping) {
1352 return false;
1353 }
1354 auto typeface_stb = std::make_shared<TypefaceSTB>(std::move(mapping));
1355
1356 auto frame = MakeTextFrameSTB(
1357 typeface_stb, Font::Metrics{.point_size = options.font_size}, text);
1358
1359 Paint text_paint;
1360 text_paint.color = Color::Yellow().WithAlpha(options.alpha);
1361 canvas.DrawTextFrame(frame, options.position, text_paint);
1362 return true;
1363 }
1364
1365 TEST_P(AiksTest, CanRenderTextFrame) {
1366 Canvas canvas;
1367 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
1368 ASSERT_TRUE(RenderTextInCanvasSkia(
1369 GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
1370 "Roboto-Regular.ttf"));
1371 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1372 }
1373
1374 TEST_P(AiksTest, CanRenderTextFrameSTB) {
1375 Canvas canvas;
1376 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
1377 ASSERT_TRUE(RenderTextInCanvasSTB(
1378 GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
1379 "Roboto-Regular.ttf"));
1380
1381 SetTypographerContext(TypographerContextSTB::Make());
1382 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1383 }
1384
1385 TEST_P(AiksTest, TextFrameSubpixelAlignment) {
1386 std::array<Scalar, 20> phase_offsets;
1387 for (Scalar& offset : phase_offsets) {
1388 auto rand = std::rand(); // NOLINT
1389 offset = (static_cast<float>(rand) / static_cast<float>(RAND_MAX)) * k2Pi;
1390 }
1391
1392 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
1393 static float font_size = 20;
1394 static float phase_variation = 0.2;
1395 static float speed = 0.5;
1396 static float magnitude = 100;
1397 ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1398 ImGui::SliderFloat("Font size", &font_size, 5, 50);
1399 ImGui::SliderFloat("Phase variation", &phase_variation, 0, 1);
1400 ImGui::SliderFloat("Oscillation speed", &speed, 0, 2);
1401 ImGui::SliderFloat("Oscillation magnitude", &magnitude, 0, 300);
1402 ImGui::End();
1403
1404 Canvas canvas;
1405 canvas.Scale(GetContentScale());
1406
1407 for (size_t i = 0; i < phase_offsets.size(); i++) {
1408 auto position = Point(
1409 200 + magnitude * std::sin((-phase_offsets[i] * phase_variation +
1410 GetSecondsElapsed() * speed)), //
1411 200 + i * font_size * 1.1 //
1412 );
1413 if (!RenderTextInCanvasSkia(
1414 GetContext(), canvas,
1415 "the quick brown fox jumped over "
1416 "the lazy dog!.?",
1417 "Roboto-Regular.ttf",
1418 {.font_size = font_size, .position = position})) {
1419 return std::nullopt;
1420 }
1421 }
1422 return canvas.EndRecordingAsPicture();
1423 };
1424
1425 ASSERT_TRUE(OpenPlaygroundHere(callback));
1426 }
1427
1428 TEST_P(AiksTest, CanRenderItalicizedText) {
1429 Canvas canvas;
1430 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
1431
1432 ASSERT_TRUE(RenderTextInCanvasSkia(
1433 GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
1434 "HomemadeApple.ttf"));
1435 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1436 }
1437
1438 TEST_P(AiksTest, CanRenderEmojiTextFrame) {
1439 Canvas canvas;
1440 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
1441
1442 ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), canvas,
1443 "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 😊",
1444 #if FML_OS_MACOSX
1445 "Apple Color Emoji.ttc"));
1446 #else
1447 "NotoColorEmoji.ttf"));
1448 #endif
1449 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1450 }
1451
1452 TEST_P(AiksTest, CanRenderEmojiTextFrameWithAlpha) {
1453 Canvas canvas;
1454 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
1455
1456 ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), canvas,
1457 "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 😊",
1458 #if FML_OS_MACOSX
1459 "Apple Color Emoji.ttc", { .alpha = 0.5 }
1460 #else
1461 "NotoColorEmoji.ttf", {.alpha = 0.5}
1462 #endif
1463 ));
1464 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1465 }
1466
1467 TEST_P(AiksTest, CanRenderTextInSaveLayer) {
1468 Canvas canvas;
1469 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
1470
1471 canvas.Translate({100, 100});
1472 canvas.Scale(Vector2{0.5, 0.5});
1473
1474 // Blend the layer with the parent pass using kClear to expose the coverage.
1475 canvas.SaveLayer({.blend_mode = BlendMode::kClear});
1476 ASSERT_TRUE(RenderTextInCanvasSkia(
1477 GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
1478 "Roboto-Regular.ttf"));
1479 canvas.Restore();
1480
1481 // Render the text again over the cleared coverage rect.
1482 ASSERT_TRUE(RenderTextInCanvasSkia(
1483 GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
1484 "Roboto-Regular.ttf"));
1485
1486 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1487 }
1488
1489 TEST_P(AiksTest, CanRenderTextOutsideBoundaries) {
1490 Canvas canvas;
1491 canvas.Translate({200, 150});
1492
1493 // Construct the text blob.
1494 auto mapping = OpenFixtureAsSkData("wtf.otf");
1495 ASSERT_NE(mapping, nullptr);
1496
1497 Scalar font_size = 80;
1498 SkFont sk_font(SkTypeface::MakeFromData(mapping), font_size);
1499
1500 Paint text_paint;
1501 text_paint.color = Color::Blue().WithAlpha(0.8);
1502
1503 struct {
1504 Point position;
1505 const char* text;
1506 } text[] = {{Point(0, 0), "0F0F0F0"},
1507 {Point(1, 2), "789"},
1508 {Point(1, 3), "456"},
1509 {Point(1, 4), "123"},
1510 {Point(0, 6), "0F0F0F0"}};
1511 for (auto& t : text) {
1512 canvas.Save();
1513 canvas.Translate(t.position * Point(font_size * 2, font_size * 1.1));
1514 {
1515 auto blob = SkTextBlob::MakeFromString(t.text, sk_font);
1516 ASSERT_NE(blob, nullptr);
1517 auto frame = MakeTextFrameFromTextBlobSkia(blob);
1518 canvas.DrawTextFrame(frame, Point(), text_paint);
1519 }
1520 canvas.Restore();
1521 }
1522
1523 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1524 }
1525
1526 TEST_P(AiksTest, TextRotated) {
1527 Canvas canvas;
1528 canvas.Scale(GetContentScale());
1529 canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
1530
1531 canvas.Transform(Matrix(0.25, -0.3, 0, -0.002, //
1532 0, 0.5, 0, 0, //
1533 0, 0, 0.3, 0, //
1534 100, 100, 0, 1.3));
1535 ASSERT_TRUE(RenderTextInCanvasSkia(
1536 GetContext(), canvas, "the quick brown fox jumped over the lazy dog!.?",
1537 "Roboto-Regular.ttf"));
1538
1539 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1540 }
1541
1542 TEST_P(AiksTest, CanDrawPaint) {
1543 Canvas canvas;
1544 canvas.Scale(Vector2(0.2, 0.2));
1545 canvas.DrawPaint({.color = Color::MediumTurquoise()});
1546 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1547 }
1548
1549 TEST_P(AiksTest, CanDrawPaintMultipleTimes) {
1550 Canvas canvas;
1551 canvas.Scale(Vector2(0.2, 0.2));
1552 canvas.DrawPaint({.color = Color::MediumTurquoise()});
1553 canvas.DrawPaint({.color = Color::Color::OrangeRed().WithAlpha(0.5)});
1554 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1555 }
1556
1557 TEST_P(AiksTest, CanDrawPaintWithAdvancedBlend) {
1558 Canvas canvas;
1559 canvas.Scale(Vector2(0.2, 0.2));
1560 canvas.DrawPaint({.color = Color::MediumTurquoise()});
1561 canvas.DrawPaint({.color = Color::Color::OrangeRed().WithAlpha(0.5),
1562 .blend_mode = BlendMode::kHue});
1563 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1564 }
1565
1566 TEST_P(AiksTest, DrawPaintWithAdvancedBlendOverFilter) {
1567 Paint filtered = {
1568 .color = Color::Black(),
1569 .mask_blur_descriptor =
1570 Paint::MaskBlurDescriptor{
1571 .style = FilterContents::BlurStyle::kNormal,
1572 .sigma = Sigma(60),
1573 },
1574 };
1575
1576 Canvas canvas;
1577 canvas.DrawPaint({.color = Color::White()});
1578 canvas.DrawCircle({300, 300}, 200, filtered);
1579 canvas.DrawPaint({.color = Color::Green(), .blend_mode = BlendMode::kScreen});
1580 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1581 }
1582
1583 TEST_P(AiksTest, DrawAdvancedBlendPartlyOffscreen) {
1584 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
1585 Color{0.1294, 0.5882, 0.9529, 1.0}};
1586 std::vector<Scalar> stops = {0.0, 1.0};
1587
1588 Paint paint = {
1589 .color_source = ColorSource::MakeLinearGradient(
1590 {0, 0}, {100, 100}, std::move(colors), std::move(stops),
1591 Entity::TileMode::kRepeat, Matrix::MakeScale(Vector3(0.3, 0.3, 0.3))),
1592 .blend_mode = BlendMode::kLighten,
1593 };
1594
1595 Canvas canvas;
1596 canvas.DrawPaint({.color = Color::Blue()});
1597 canvas.Scale(Vector2(2, 2));
1598 canvas.ClipRect(Rect::MakeLTRB(0, 0, 200, 200));
1599 canvas.DrawCircle({100, 100}, 100, paint);
1600 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1601 }
1602
1603 #define BLEND_MODE_TUPLE(blend_mode) {#blend_mode, BlendMode::k##blend_mode},
1604
1605 struct BlendModeSelection {
1606 std::vector<const char*> blend_mode_names;
1607 std::vector<BlendMode> blend_mode_values;
1608 };
1609
1610 static BlendModeSelection GetBlendModeSelection() {
1611 std::vector<const char*> blend_mode_names;
1612 std::vector<BlendMode> blend_mode_values;
1613 {
1614 const std::vector<std::tuple<const char*, BlendMode>> blends = {
1615 IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TUPLE)};
1616 assert(blends.size() ==
1617 static_cast<size_t>(Entity::kLastAdvancedBlendMode) + 1);
1618 for (const auto& [name, mode] : blends) {
1619 blend_mode_names.push_back(name);
1620 blend_mode_values.push_back(mode);
1621 }
1622 }
1623
1624 return {blend_mode_names, blend_mode_values};
1625 }
1626
1627 TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) {
1628 auto modes = GetBlendModeSelection();
1629
1630 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
1631 static Color background = Color::MediumTurquoise();
1632 static Color foreground = Color::Color::OrangeRed().WithAlpha(0.5);
1633 static int current_blend_index = 3;
1634
1635 ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1636 {
1637 ImGui::ColorEdit4("Background", reinterpret_cast<float*>(&background));
1638 ImGui::ColorEdit4("Foreground", reinterpret_cast<float*>(&foreground));
1639 ImGui::ListBox("Blend mode", &current_blend_index,
1640 modes.blend_mode_names.data(),
1641 modes.blend_mode_names.size());
1642 }
1643 ImGui::End();
1644
1645 Canvas canvas;
1646 canvas.Scale(Vector2(0.2, 0.2));
1647 canvas.DrawPaint({.color = background});
1648 canvas.DrawPaint(
1649 {.color = foreground,
1650 .blend_mode = static_cast<BlendMode>(current_blend_index)});
1651 return canvas.EndRecordingAsPicture();
1652 };
1653 ASSERT_TRUE(OpenPlaygroundHere(callback));
1654 }
1655
1656 TEST_P(AiksTest, PaintBlendModeIsRespected) {
1657 Paint paint;
1658 Canvas canvas;
1659 // Default is kSourceOver.
1660 paint.color = Color(1, 0, 0, 0.5);
1661 canvas.DrawCircle(Point(150, 200), 100, paint);
1662 paint.color = Color(0, 1, 0, 0.5);
1663 canvas.DrawCircle(Point(250, 200), 100, paint);
1664
1665 paint.blend_mode = BlendMode::kPlus;
1666 paint.color = Color::Red();
1667 canvas.DrawCircle(Point(450, 250), 100, paint);
1668 paint.color = Color::Green();
1669 canvas.DrawCircle(Point(550, 250), 100, paint);
1670 paint.color = Color::Blue();
1671 canvas.DrawCircle(Point(500, 150), 100, paint);
1672 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
1673 }
1674
1675 TEST_P(AiksTest, ColorWheel) {
1676 // Compare with https://fiddle.skia.org/c/@BlendModes
1677
1678 BlendModeSelection blend_modes = GetBlendModeSelection();
1679
1680 auto draw_color_wheel = [](Canvas& canvas) {
1681 /// color_wheel_sampler: r=0 -> fuchsia, r=2pi/3 -> yellow, r=4pi/3 ->
1682 /// cyan domain: r >= 0 (because modulo used is non euclidean)
1683 auto color_wheel_sampler = [](Radians r) {
1684 Scalar x = r.radians / k2Pi + 1;
1685
1686 // https://www.desmos.com/calculator/6nhjelyoaj
1687 auto color_cycle = [](Scalar x) {
1688 Scalar cycle = std::fmod(x, 6.0f);
1689 return std::max(0.0f, std::min(1.0f, 2 - std::abs(2 - cycle)));
1690 };
1691 return Color(color_cycle(6 * x + 1), //
1692 color_cycle(6 * x - 1), //
1693 color_cycle(6 * x - 3), //
1694 1);
1695 };
1696
1697 Paint paint;
1698 paint.blend_mode = BlendMode::kSourceOver;
1699
1700 // Draw a fancy color wheel for the backdrop.
1701 // https://www.desmos.com/calculator/xw7kafthwd
1702 const int max_dist = 900;
1703 for (int i = 0; i <= 900; i++) {
1704 Radians r(kPhi / k2Pi * i);
1705 Scalar distance = r.radians / std::powf(4.12, 0.0026 * r.radians);
1706 Scalar normalized_distance = static_cast<Scalar>(i) / max_dist;
1707
1708 paint.color =
1709 color_wheel_sampler(r).WithAlpha(1.0f - normalized_distance);
1710 Point position(distance * std::sin(r.radians),
1711 -distance * std::cos(r.radians));
1712
1713 canvas.DrawCircle(position, 9 + normalized_distance * 3, paint);
1714 }
1715 };
1716
1717 std::shared_ptr<Image> color_wheel_image;
1718 Matrix color_wheel_transform;
1719
1720 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
1721 // UI state.
1722 static bool cache_the_wheel = true;
1723 static int current_blend_index = 3;
1724 static float dst_alpha = 1;
1725 static float src_alpha = 1;
1726 static Color color0 = Color::Red();
1727 static Color color1 = Color::Green();
1728 static Color color2 = Color::Blue();
1729
1730 ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1731 {
1732 ImGui::Checkbox("Cache the wheel", &cache_the_wheel);
1733 ImGui::ListBox("Blending mode", &current_blend_index,
1734 blend_modes.blend_mode_names.data(),
1735 blend_modes.blend_mode_names.size());
1736 ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
1737 ImGui::ColorEdit4("Color A", reinterpret_cast<float*>(&color0));
1738 ImGui::ColorEdit4("Color B", reinterpret_cast<float*>(&color1));
1739 ImGui::ColorEdit4("Color C", reinterpret_cast<float*>(&color2));
1740 ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1);
1741 }
1742 ImGui::End();
1743
1744 static Point content_scale;
1745 Point new_content_scale = GetContentScale();
1746
1747 if (!cache_the_wheel || new_content_scale != content_scale) {
1748 content_scale = new_content_scale;
1749
1750 // Render the color wheel to an image.
1751
1752 Canvas canvas;
1753 canvas.Scale(content_scale);
1754
1755 canvas.Translate(Vector2(500, 400));
1756 canvas.Scale(Vector2(3, 3));
1757
1758 draw_color_wheel(canvas);
1759 auto color_wheel_picture = canvas.EndRecordingAsPicture();
1760 auto snapshot = color_wheel_picture.Snapshot(renderer);
1761 if (!snapshot.has_value() || !snapshot->texture) {
1762 return std::nullopt;
1763 }
1764 color_wheel_image = std::make_shared<Image>(snapshot->texture);
1765 color_wheel_transform = snapshot->transform;
1766 }
1767
1768 Canvas canvas;
1769
1770 // Blit the color wheel backdrop to the screen with managed alpha.
1771 canvas.SaveLayer({.color = Color::White().WithAlpha(dst_alpha),
1772 .blend_mode = BlendMode::kSource});
1773 {
1774 canvas.DrawPaint({.color = Color::White()});
1775
1776 canvas.Save();
1777 canvas.Transform(color_wheel_transform);
1778 canvas.DrawImage(color_wheel_image, Point(), Paint());
1779 canvas.Restore();
1780 }
1781 canvas.Restore();
1782
1783 canvas.Scale(content_scale);
1784 canvas.Translate(Vector2(500, 400));
1785 canvas.Scale(Vector2(3, 3));
1786
1787 // Draw 3 circles to a subpass and blend it in.
1788 canvas.SaveLayer(
1789 {.color = Color::White().WithAlpha(src_alpha),
1790 .blend_mode = blend_modes.blend_mode_values[current_blend_index]});
1791 {
1792 Paint paint;
1793 paint.blend_mode = BlendMode::kPlus;
1794 const Scalar x = std::sin(k2Pi / 3);
1795 const Scalar y = -std::cos(k2Pi / 3);
1796 paint.color = color0;
1797 canvas.DrawCircle(Point(-x, y) * 45, 65, paint);
1798 paint.color = color1;
1799 canvas.DrawCircle(Point(0, -1) * 45, 65, paint);
1800 paint.color = color2;
1801 canvas.DrawCircle(Point(x, y) * 45, 65, paint);
1802 }
1803 canvas.Restore();
1804
1805 return canvas.EndRecordingAsPicture();
1806 };
1807
1808 ASSERT_TRUE(OpenPlaygroundHere(callback));
1809 }
1810
1811 TEST_P(AiksTest, TransformMultipliesCorrectly) {
1812 Canvas canvas;
1813 ASSERT_MATRIX_NEAR(canvas.GetCurrentTransformation(), Matrix());
1814
1815 // clang-format off
1816 canvas.Translate(Vector3(100, 200));
1817 ASSERT_MATRIX_NEAR(
1818 canvas.GetCurrentTransformation(),
1819 Matrix( 1, 0, 0, 0,
1820 0, 1, 0, 0,
1821 0, 0, 1, 0,
1822 100, 200, 0, 1));
1823
1824 canvas.Rotate(Radians(kPiOver2));
1825 ASSERT_MATRIX_NEAR(
1826 canvas.GetCurrentTransformation(),
1827 Matrix( 0, 1, 0, 0,
1828 -1, 0, 0, 0,
1829 0, 0, 1, 0,
1830 100, 200, 0, 1));
1831
1832 canvas.Scale(Vector3(2, 3));
1833 ASSERT_MATRIX_NEAR(
1834 canvas.GetCurrentTransformation(),
1835 Matrix( 0, 2, 0, 0,
1836 -3, 0, 0, 0,
1837 0, 0, 0, 0,
1838 100, 200, 0, 1));
1839
1840 canvas.Translate(Vector3(100, 200));
1841 ASSERT_MATRIX_NEAR(
1842 canvas.GetCurrentTransformation(),
1843 Matrix( 0, 2, 0, 0,
1844 -3, 0, 0, 0,
1845 0, 0, 0, 0,
1846 -500, 400, 0, 1));
1847 // clang-format on
1848 }
1849
1850 TEST_P(AiksTest, SolidStrokesRenderCorrectly) {
1851 // Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
1852 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
1853 static Color color = Color::Black().WithAlpha(0.5);
1854 static float scale = 3;
1855 static bool add_circle_clip = true;
1856
1857 ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1858 ImGui::ColorEdit4("Color", reinterpret_cast<float*>(&color));
1859 ImGui::SliderFloat("Scale", &scale, 0, 6);
1860 ImGui::Checkbox("Circle clip", &add_circle_clip);
1861 ImGui::End();
1862
1863 Canvas canvas;
1864 canvas.Scale(GetContentScale());
1865 Paint paint;
1866
1867 paint.color = Color::White();
1868 canvas.DrawPaint(paint);
1869
1870 paint.color = color;
1871 paint.style = Paint::Style::kStroke;
1872 paint.stroke_width = 10;
1873
1874 Path path = PathBuilder{}
1875 .MoveTo({20, 20})
1876 .QuadraticCurveTo({60, 20}, {60, 60})
1877 .Close()
1878 .MoveTo({60, 20})
1879 .QuadraticCurveTo({60, 60}, {20, 60})
1880 .TakePath();
1881
1882 canvas.Scale(Vector2(scale, scale));
1883
1884 if (add_circle_clip) {
1885 auto [handle_a, handle_b] = IMPELLER_PLAYGROUND_LINE(
1886 Point(60, 300), Point(600, 300), 20, Color::Red(), Color::Red());
1887
1888 auto screen_to_canvas = canvas.GetCurrentTransformation().Invert();
1889 Point point_a = screen_to_canvas * handle_a * GetContentScale();
1890 Point point_b = screen_to_canvas * handle_b * GetContentScale();
1891
1892 Point middle = (point_a + point_b) / 2;
1893 auto radius = point_a.GetDistance(middle);
1894 canvas.ClipPath(PathBuilder{}.AddCircle(middle, radius).TakePath());
1895 }
1896
1897 for (auto join : {Join::kBevel, Join::kRound, Join::kMiter}) {
1898 paint.stroke_join = join;
1899 for (auto cap : {Cap::kButt, Cap::kSquare, Cap::kRound}) {
1900 paint.stroke_cap = cap;
1901 canvas.DrawPath(path, paint);
1902 canvas.Translate({80, 0});
1903 }
1904 canvas.Translate({-240, 60});
1905 }
1906
1907 return canvas.EndRecordingAsPicture();
1908 };
1909
1910 ASSERT_TRUE(OpenPlaygroundHere(callback));
1911 }
1912
1913 TEST_P(AiksTest, GradientStrokesRenderCorrectly) {
1914 // Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
1915 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
1916 static float scale = 3;
1917 static bool add_circle_clip = true;
1918 const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
1919 const Entity::TileMode tile_modes[] = {
1920 Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
1921 Entity::TileMode::kMirror, Entity::TileMode::kDecal};
1922 static int selected_tile_mode = 0;
1923 static float alpha = 1;
1924
1925 ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
1926 ImGui::SliderFloat("Scale", &scale, 0, 6);
1927 ImGui::Checkbox("Circle clip", &add_circle_clip);
1928 ImGui::SliderFloat("Alpha", &alpha, 0, 1);
1929 ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
1930 sizeof(tile_mode_names) / sizeof(char*));
1931 ImGui::End();
1932
1933 Canvas canvas;
1934 canvas.Scale(GetContentScale());
1935 Paint paint;
1936 paint.color = Color::White();
1937 canvas.DrawPaint(paint);
1938
1939 paint.style = Paint::Style::kStroke;
1940 paint.color = Color(1.0, 1.0, 1.0, alpha);
1941 paint.stroke_width = 10;
1942 auto tile_mode = tile_modes[selected_tile_mode];
1943
1944 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
1945 Color{0.1294, 0.5882, 0.9529, 1.0}};
1946 std::vector<Scalar> stops = {0.0, 1.0};
1947
1948 paint.color_source = ColorSource::MakeLinearGradient(
1949 {0, 0}, {50, 50}, std::move(colors), std::move(stops), tile_mode, {});
1950
1951 Path path = PathBuilder{}
1952 .MoveTo({20, 20})
1953 .QuadraticCurveTo({60, 20}, {60, 60})
1954 .Close()
1955 .MoveTo({60, 20})
1956 .QuadraticCurveTo({60, 60}, {20, 60})
1957 .TakePath();
1958
1959 canvas.Scale(Vector2(scale, scale));
1960
1961 if (add_circle_clip) {
1962 auto [handle_a, handle_b] = IMPELLER_PLAYGROUND_LINE(
1963 Point(60, 300), Point(600, 300), 20, Color::Red(), Color::Red());
1964
1965 auto screen_to_canvas = canvas.GetCurrentTransformation().Invert();
1966 Point point_a = screen_to_canvas * handle_a * GetContentScale();
1967 Point point_b = screen_to_canvas * handle_b * GetContentScale();
1968
1969 Point middle = (point_a + point_b) / 2;
1970 auto radius = point_a.GetDistance(middle);
1971 canvas.ClipPath(PathBuilder{}.AddCircle(middle, radius).TakePath());
1972 }
1973
1974 for (auto join : {Join::kBevel, Join::kRound, Join::kMiter}) {
1975 paint.stroke_join = join;
1976 for (auto cap : {Cap::kButt, Cap::kSquare, Cap::kRound}) {
1977 paint.stroke_cap = cap;
1978 canvas.DrawPath(path, paint);
1979 canvas.Translate({80, 0});
1980 }
1981 canvas.Translate({-240, 60});
1982 }
1983
1984 return canvas.EndRecordingAsPicture();
1985 };
1986
1987 ASSERT_TRUE(OpenPlaygroundHere(callback));
1988 }
1989
1990 TEST_P(AiksTest, CoverageOriginShouldBeAccountedForInSubpasses) {
1991 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
1992 Canvas canvas;
1993 canvas.Scale(GetContentScale());
1994
1995 Paint alpha;
1996 alpha.color = Color::Red().WithAlpha(0.5);
1997
1998 auto current = Point{25, 25};
1999 const auto offset = Point{25, 25};
2000 const auto size = Size(100, 100);
2001
2002 auto [b0, b1] = IMPELLER_PLAYGROUND_LINE(Point(40, 40), Point(160, 160), 10,
2003 Color::White(), Color::White());
2004 auto bounds = Rect::MakeLTRB(b0.x, b0.y, b1.x, b1.y);
2005
2006 canvas.DrawRect(bounds, Paint{.color = Color::Yellow(),
2007 .stroke_width = 5.0f,
2008 .style = Paint::Style::kStroke});
2009
2010 canvas.SaveLayer(alpha, bounds);
2011
2012 canvas.DrawRect({current, size}, Paint{.color = Color::Red()});
2013 canvas.DrawRect({current += offset, size}, Paint{.color = Color::Green()});
2014 canvas.DrawRect({current += offset, size}, Paint{.color = Color::Blue()});
2015
2016 canvas.Restore();
2017
2018 return canvas.EndRecordingAsPicture();
2019 };
2020
2021 ASSERT_TRUE(OpenPlaygroundHere(callback));
2022 }
2023
2024 TEST_P(AiksTest, DrawRectStrokesRenderCorrectly) {
2025 Canvas canvas;
2026 Paint paint;
2027 paint.color = Color::Red();
2028 paint.style = Paint::Style::kStroke;
2029 paint.stroke_width = 10;
2030
2031 canvas.Translate({100, 100});
2032 canvas.DrawPath(
2033 PathBuilder{}.AddRect(Rect::MakeSize(Size{100, 100})).TakePath(),
2034 {paint});
2035
2036 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2037 }
2038
2039 TEST_P(AiksTest, SaveLayerDrawsBehindSubsequentEntities) {
2040 // Compare with https://fiddle.skia.org/c/9e03de8567ffb49e7e83f53b64bcf636
2041 Canvas canvas;
2042 Paint paint;
2043
2044 paint.color = Color::Black();
2045 Rect rect(25, 25, 25, 25);
2046 canvas.DrawRect(rect, paint);
2047
2048 canvas.Translate({10, 10});
2049 canvas.SaveLayer({});
2050
2051 paint.color = Color::Green();
2052 canvas.DrawRect(rect, paint);
2053
2054 canvas.Restore();
2055
2056 canvas.Translate({10, 10});
2057 paint.color = Color::Red();
2058 canvas.DrawRect(rect, paint);
2059
2060 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2061 }
2062
2063 TEST_P(AiksTest, SiblingSaveLayerBoundsAreRespected) {
2064 Canvas canvas;
2065 Paint paint;
2066 Rect rect(0, 0, 1000, 1000);
2067
2068 // Black, green, and red squares offset by [10, 10].
2069 {
2070 canvas.SaveLayer({}, Rect::MakeXYWH(25, 25, 25, 25));
2071 paint.color = Color::Black();
2072 canvas.DrawRect(rect, paint);
2073 canvas.Restore();
2074 }
2075
2076 {
2077 canvas.SaveLayer({}, Rect::MakeXYWH(35, 35, 25, 25));
2078 paint.color = Color::Green();
2079 canvas.DrawRect(rect, paint);
2080 canvas.Restore();
2081 }
2082
2083 {
2084 canvas.SaveLayer({}, Rect::MakeXYWH(45, 45, 25, 25));
2085 paint.color = Color::Red();
2086 canvas.DrawRect(rect, paint);
2087 canvas.Restore();
2088 }
2089
2090 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2091 }
2092
2093 TEST_P(AiksTest, CanRenderClippedLayers) {
2094 Canvas canvas;
2095
2096 canvas.DrawPaint({.color = Color::White()});
2097
2098 // Draw a green circle on the screen.
2099 {
2100 // Increase the clip depth for the savelayer to contend with.
2101 canvas.ClipPath(PathBuilder{}.AddCircle({100, 100}, 50).TakePath());
2102
2103 canvas.SaveLayer({}, Rect::MakeXYWH(50, 50, 100, 100));
2104
2105 // Fill the layer with white.
2106 canvas.DrawRect(Rect::MakeSize(Size{400, 400}), {.color = Color::White()});
2107 // Fill the layer with green, but do so with a color blend that can't be
2108 // collapsed into the parent pass.
2109 // TODO(jonahwilliams): this blend mode was changed from color burn to
2110 // hardlight to work around https://github.com/flutter/flutter/issues/136554
2111 // .
2112 canvas.DrawRect(
2113 Rect::MakeSize(Size{400, 400}),
2114 {.color = Color::Green(), .blend_mode = BlendMode::kHardLight});
2115 }
2116
2117 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2118 }
2119
2120 TEST_P(AiksTest, SaveLayerFiltersScaleWithTransform) {
2121 Canvas canvas;
2122 canvas.Scale(GetContentScale());
2123 canvas.Translate(Vector2(100, 100));
2124
2125 auto texture = std::make_shared<Image>(CreateTextureForFixture("boston.jpg"));
2126 auto draw_image_layer = [&canvas, &texture](const Paint& paint) {
2127 canvas.SaveLayer(paint);
2128 canvas.DrawImage(texture, {}, Paint{});
2129 canvas.Restore();
2130 };
2131
2132 Paint effect_paint;
2133 effect_paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
2134 .style = FilterContents::BlurStyle::kNormal,
2135 .sigma = Sigma{6},
2136 };
2137 draw_image_layer(effect_paint);
2138
2139 canvas.Translate(Vector2(300, 300));
2140 canvas.Scale(Vector2(3, 3));
2141 draw_image_layer(effect_paint);
2142
2143 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2144 }
2145
2146 #if IMPELLER_ENABLE_3D
2147 TEST_P(AiksTest, SceneColorSource) {
2148 // Load up the scene.
2149 auto mapping =
2150 flutter::testing::OpenFixtureAsMapping("flutter_logo_baked.glb.ipscene");
2151 ASSERT_NE(mapping, nullptr);
2152
2153 std::shared_ptr<scene::Node> gltf_scene = scene::Node::MakeFromFlatbuffer(
2154 *mapping, *GetContext()->GetResourceAllocator());
2155 ASSERT_NE(gltf_scene, nullptr);
2156
2157 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
2158 Paint paint;
2159
2160 ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
2161 static Scalar distance = 2;
2162 ImGui::SliderFloat("Distance", &distance, 0, 4);
2163 static Scalar y_pos = 0;
2164 ImGui::SliderFloat("Y", &y_pos, -3, 3);
2165 static Scalar fov = 45;
2166 ImGui::SliderFloat("FOV", &fov, 1, 180);
2167 ImGui::End();
2168
2169 Scalar angle = GetSecondsElapsed();
2170 auto camera_position =
2171 Vector3(distance * std::sin(angle), y_pos, -distance * std::cos(angle));
2172
2173 paint.color_source = ColorSource::MakeScene(
2174 gltf_scene,
2175 Matrix::MakePerspective(Degrees(fov), GetWindowSize(), 0.1, 1000) *
2176 Matrix::MakeLookAt(camera_position, {0, 0, 0}, {0, 1, 0}));
2177
2178 Canvas canvas;
2179 canvas.DrawPaint(Paint{.color = Color::MakeRGBA8(0xf9, 0xf9, 0xf9, 0xff)});
2180 canvas.Scale(GetContentScale());
2181 canvas.DrawPaint(paint);
2182 return canvas.EndRecordingAsPicture();
2183 };
2184
2185 ASSERT_TRUE(OpenPlaygroundHere(callback));
2186 }
2187 #endif // IMPELLER_ENABLE_3D
2188
2189 TEST_P(AiksTest, PaintWithFilters) {
2190 // validate that a paint with a color filter "HasFilters", no other filters
2191 // impact this setting.
2192 Paint paint;
2193
2194 ASSERT_FALSE(paint.HasColorFilter());
2195
2196 paint.color_filter =
2197 ColorFilter::MakeBlend(BlendMode::kSourceOver, Color::Blue());
2198
2199 ASSERT_TRUE(paint.HasColorFilter());
2200
2201 paint.image_filter = ImageFilter::MakeBlur(Sigma(1.0), Sigma(1.0),
2202 FilterContents::BlurStyle::kNormal,
2203 Entity::TileMode::kClamp);
2204
2205 ASSERT_TRUE(paint.HasColorFilter());
2206
2207 paint.mask_blur_descriptor = {};
2208
2209 ASSERT_TRUE(paint.HasColorFilter());
2210
2211 paint.color_filter = nullptr;
2212
2213 ASSERT_FALSE(paint.HasColorFilter());
2214 }
2215
2216 TEST_P(AiksTest, OpacityPeepHoleApplicationTest) {
2217 auto entity_pass = std::make_shared<EntityPass>();
2218 auto rect = Rect::MakeLTRB(0, 0, 100, 100);
2219 Paint paint;
2220 paint.color = Color::White().WithAlpha(0.5);
2221 paint.color_filter =
2222 ColorFilter::MakeBlend(BlendMode::kSourceOver, Color::Blue());
2223
2224 // Paint has color filter, can't elide.
2225 auto delegate = std::make_shared<OpacityPeepholePassDelegate>(paint);
2226 ASSERT_FALSE(delegate->CanCollapseIntoParentPass(entity_pass.get()));
2227
2228 paint.color_filter = nullptr;
2229 paint.image_filter = ImageFilter::MakeBlur(Sigma(1.0), Sigma(1.0),
2230 FilterContents::BlurStyle::kNormal,
2231 Entity::TileMode::kClamp);
2232
2233 // Paint has image filter, can't elide.
2234 delegate = std::make_shared<OpacityPeepholePassDelegate>(paint);
2235 ASSERT_FALSE(delegate->CanCollapseIntoParentPass(entity_pass.get()));
2236
2237 paint.image_filter = nullptr;
2238 paint.color = Color::Red();
2239
2240 // Paint has no alpha, can't elide;
2241 delegate = std::make_shared<OpacityPeepholePassDelegate>(paint);
2242 ASSERT_FALSE(delegate->CanCollapseIntoParentPass(entity_pass.get()));
2243
2244 // Positive test.
2245 Entity entity;
2246 entity.SetContents(SolidColorContents::Make(
2247 PathBuilder{}.AddRect(rect).TakePath(), Color::Red()));
2248 entity_pass->AddEntity(entity);
2249 paint.color = Color::Red().WithAlpha(0.5);
2250
2251 delegate = std::make_shared<OpacityPeepholePassDelegate>(paint);
2252 ASSERT_TRUE(delegate->CanCollapseIntoParentPass(entity_pass.get()));
2253 }
2254
2255 TEST_P(AiksTest, DrawPaintAbsorbsClears) {
2256 Canvas canvas;
2257 canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource});
2258 canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
2259 .blend_mode = BlendMode::kSourceOver});
2260
2261 Picture picture = canvas.EndRecordingAsPicture();
2262 auto expected = Color::Red().Blend(Color::CornflowerBlue().WithAlpha(0.75),
2263 BlendMode::kSourceOver);
2264 ASSERT_EQ(picture.pass->GetClearColor(), expected);
2265
2266 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
2267 std::shared_ptr<Context> real_context = GetContext();
2268 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
2269 AiksContext renderer(mock_context, nullptr);
2270 std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
2271
2272 ASSERT_EQ(spy->render_passes_.size(), 1llu);
2273 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
2274 ASSERT_EQ(render_pass->GetCommands().size(), 0llu);
2275 }
2276
2277 // This is important to enforce with texture reuse, since cached textures need
2278 // to be cleared before reuse.
2279 TEST_P(AiksTest,
2280 ParentSaveLayerCreatesRenderPassWhenChildBackdropFilterIsPresent) {
2281 Canvas canvas;
2282 canvas.SaveLayer({}, std::nullopt, ImageFilter::MakeMatrix(Matrix(), {}));
2283 canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource});
2284 canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
2285 .blend_mode = BlendMode::kSourceOver});
2286 canvas.Restore();
2287
2288 Picture picture = canvas.EndRecordingAsPicture();
2289
2290 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
2291 std::shared_ptr<Context> real_context = GetContext();
2292 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
2293 AiksContext renderer(mock_context, nullptr);
2294 std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
2295
2296 ASSERT_EQ(spy->render_passes_.size(), 3llu);
2297 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
2298 ASSERT_EQ(render_pass->GetCommands().size(), 0llu);
2299 }
2300
2301 TEST_P(AiksTest, DrawRectAbsorbsClears) {
2302 Canvas canvas;
2303 canvas.DrawRect({0, 0, 300, 300},
2304 {.color = Color::Red(), .blend_mode = BlendMode::kSource});
2305 canvas.DrawRect({0, 0, 300, 300},
2306 {.color = Color::CornflowerBlue().WithAlpha(0.75),
2307 .blend_mode = BlendMode::kSourceOver});
2308
2309 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
2310 Picture picture = canvas.EndRecordingAsPicture();
2311 std::shared_ptr<Context> real_context = GetContext();
2312 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
2313 AiksContext renderer(mock_context, nullptr);
2314 std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
2315
2316 ASSERT_EQ(spy->render_passes_.size(), 1llu);
2317 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
2318 ASSERT_EQ(render_pass->GetCommands().size(), 0llu);
2319 }
2320
2321 TEST_P(AiksTest, DrawRectAbsorbsClearsNegativeRRect) {
2322 Canvas canvas;
2323 canvas.DrawRRect({0, 0, 300, 300}, 5.0,
2324 {.color = Color::Red(), .blend_mode = BlendMode::kSource});
2325 canvas.DrawRRect({0, 0, 300, 300}, 5.0,
2326 {.color = Color::CornflowerBlue().WithAlpha(0.75),
2327 .blend_mode = BlendMode::kSourceOver});
2328
2329 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
2330 Picture picture = canvas.EndRecordingAsPicture();
2331 std::shared_ptr<Context> real_context = GetContext();
2332 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
2333 AiksContext renderer(mock_context, nullptr);
2334 std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
2335
2336 ASSERT_EQ(spy->render_passes_.size(), 1llu);
2337 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
2338 ASSERT_EQ(render_pass->GetCommands().size(), 2llu);
2339 }
2340
2341 TEST_P(AiksTest, DrawRectAbsorbsClearsNegativeRotation) {
2342 Canvas canvas;
2343 canvas.Translate(Vector3(150.0, 150.0, 0.0));
2344 canvas.Rotate(Degrees(45.0));
2345 canvas.Translate(Vector3(-150.0, -150.0, 0.0));
2346 canvas.DrawRect({0, 0, 300, 300},
2347 {.color = Color::Red(), .blend_mode = BlendMode::kSource});
2348
2349 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
2350 Picture picture = canvas.EndRecordingAsPicture();
2351 std::shared_ptr<Context> real_context = GetContext();
2352 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
2353 AiksContext renderer(mock_context, nullptr);
2354 std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
2355
2356 ASSERT_EQ(spy->render_passes_.size(), 1llu);
2357 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
2358 ASSERT_EQ(render_pass->GetCommands().size(), 1llu);
2359 }
2360
2361 TEST_P(AiksTest, DrawRectAbsorbsClearsNegative) {
2362 Canvas canvas;
2363 canvas.DrawRect({0, 0, 300, 300},
2364 {.color = Color::Red(), .blend_mode = BlendMode::kSource});
2365 canvas.DrawRect({0, 0, 300, 300},
2366 {.color = Color::CornflowerBlue().WithAlpha(0.75),
2367 .blend_mode = BlendMode::kSourceOver});
2368
2369 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
2370 Picture picture = canvas.EndRecordingAsPicture();
2371 std::shared_ptr<Context> real_context = GetContext();
2372 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
2373 AiksContext renderer(mock_context, nullptr);
2374 std::shared_ptr<Image> image = picture.ToImage(renderer, {301, 301});
2375
2376 ASSERT_EQ(spy->render_passes_.size(), 1llu);
2377 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
2378 ASSERT_EQ(render_pass->GetCommands().size(), 2llu);
2379 }
2380
2381 TEST_P(AiksTest, ClipRectElidesNoOpClips) {
2382 Canvas canvas(Rect(0, 0, 100, 100));
2383 canvas.ClipRect(Rect(0, 0, 100, 100));
2384 canvas.ClipRect(Rect(-100, -100, 300, 300));
2385 canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource});
2386 canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
2387 .blend_mode = BlendMode::kSourceOver});
2388
2389 Picture picture = canvas.EndRecordingAsPicture();
2390 auto expected = Color::Red().Blend(Color::CornflowerBlue().WithAlpha(0.75),
2391 BlendMode::kSourceOver);
2392 ASSERT_EQ(picture.pass->GetClearColor(), expected);
2393
2394 std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
2395 std::shared_ptr<Context> real_context = GetContext();
2396 std::shared_ptr<ContextMock> mock_context = spy->MakeContext(real_context);
2397 AiksContext renderer(mock_context, nullptr);
2398 std::shared_ptr<Image> image = picture.ToImage(renderer, {300, 300});
2399
2400 ASSERT_EQ(spy->render_passes_.size(), 1llu);
2401 std::shared_ptr<RenderPass> render_pass = spy->render_passes_[0];
2402 ASSERT_EQ(render_pass->GetCommands().size(), 0llu);
2403 }
2404
2405 TEST_P(AiksTest, ClearColorOptimizationDoesNotApplyForBackdropFilters) {
2406 Canvas canvas;
2407 canvas.SaveLayer({}, std::nullopt,
2408 ImageFilter::MakeBlur(Sigma(3), Sigma(3),
2409 FilterContents::BlurStyle::kNormal,
2410 Entity::TileMode::kClamp));
2411 canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource});
2412 canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
2413 .blend_mode = BlendMode::kSourceOver});
2414 canvas.Restore();
2415
2416 Picture picture = canvas.EndRecordingAsPicture();
2417
2418 std::optional<Color> actual_color;
2419 bool found_subpass = false;
2420 picture.pass->IterateAllElements([&](EntityPass::Element& element) -> bool {
2421 if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
2422 actual_color = subpass->get()->GetClearColor();
2423 found_subpass = true;
2424 }
2425 // Fail if the first element isn't a subpass.
2426 return true;
2427 });
2428
2429 EXPECT_TRUE(found_subpass);
2430 EXPECT_FALSE(actual_color.has_value());
2431 }
2432
2433 TEST_P(AiksTest, CollapsedDrawPaintInSubpass) {
2434 Canvas canvas;
2435 canvas.DrawPaint(
2436 {.color = Color::Yellow(), .blend_mode = BlendMode::kSource});
2437 canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
2438 canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
2439 .blend_mode = BlendMode::kSourceOver});
2440
2441 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2442 }
2443
2444 TEST_P(AiksTest, CollapsedDrawPaintInSubpassBackdropFilter) {
2445 // Bug: https://github.com/flutter/flutter/issues/131576
2446 Canvas canvas;
2447 canvas.DrawPaint(
2448 {.color = Color::Yellow(), .blend_mode = BlendMode::kSource});
2449 canvas.SaveLayer({}, {},
2450 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
2451 FilterContents::BlurStyle::kNormal,
2452 Entity::TileMode::kDecal));
2453 canvas.DrawPaint(
2454 {.color = Color::CornflowerBlue(), .blend_mode = BlendMode::kSourceOver});
2455
2456 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2457 }
2458
2459 TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) {
2460 Canvas canvas;
2461
2462 canvas.SaveLayer({
2463 .color_filter =
2464 ColorFilter::MakeBlend(BlendMode::kColorDodge, Color::Red()),
2465 });
2466
2467 canvas.Translate({500, 300, 0});
2468 canvas.Rotate(Radians(2 * kPi / 3));
2469 canvas.DrawRect({100, 100, 200, 200}, {.color = Color::Blue()});
2470
2471 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2472 }
2473
2474 TEST_P(AiksTest, ColorMatrixFilterSubpassCollapseOptimization) {
2475 Canvas canvas;
2476
2477 canvas.SaveLayer({
2478 .color_filter =
2479 ColorFilter::MakeMatrix({.array =
2480 {
2481 -1.0, 0, 0, 1.0, 0, //
2482 0, -1.0, 0, 1.0, 0, //
2483 0, 0, -1.0, 1.0, 0, //
2484 1.0, 1.0, 1.0, 1.0, 0 //
2485 }}),
2486 });
2487
2488 canvas.Translate({500, 300, 0});
2489 canvas.Rotate(Radians(2 * kPi / 3));
2490 canvas.DrawRect({100, 100, 200, 200}, {.color = Color::Blue()});
2491
2492 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2493 }
2494
2495 TEST_P(AiksTest, LinearToSrgbFilterSubpassCollapseOptimization) {
2496 Canvas canvas;
2497
2498 canvas.SaveLayer({
2499 .color_filter = ColorFilter::MakeLinearToSrgb(),
2500 });
2501
2502 canvas.Translate({500, 300, 0});
2503 canvas.Rotate(Radians(2 * kPi / 3));
2504 canvas.DrawRect({100, 100, 200, 200}, {.color = Color::Blue()});
2505
2506 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2507 }
2508
2509 TEST_P(AiksTest, SrgbToLinearFilterSubpassCollapseOptimization) {
2510 Canvas canvas;
2511
2512 canvas.SaveLayer({
2513 .color_filter = ColorFilter::MakeSrgbToLinear(),
2514 });
2515
2516 canvas.Translate({500, 300, 0});
2517 canvas.Rotate(Radians(2 * kPi / 3));
2518 canvas.DrawRect({100, 100, 200, 200}, {.color = Color::Blue()});
2519
2520 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2521 }
2522
2523 static Picture BlendModeTest(BlendMode blend_mode,
2524 const std::shared_ptr<Image>& src_image,
2525 const std::shared_ptr<Image>& dst_image) {
2526 Color destination_color = Color::CornflowerBlue().WithAlpha(0.75);
2527 auto source_colors = std::vector<Color>({Color::White().WithAlpha(0.75),
2528 Color::LimeGreen().WithAlpha(0.75),
2529 Color::Black().WithAlpha(0.75)});
2530
2531 Canvas canvas;
2532
2533 canvas.DrawPaint({.color = Color::Black()});
2534
2535 //----------------------------------------------------------------------------
2536 /// 1. Save layer blending (top squares).
2537 ///
2538
2539 canvas.Save();
2540 for (const auto& color : source_colors) {
2541 canvas.Save();
2542 {
2543 canvas.ClipRect(Rect::MakeXYWH(50, 50, 100, 100));
2544 // Perform the blend in a SaveLayer so that the initial backdrop color is
2545 // fully transparent black. SourceOver blend the result onto the parent
2546 // pass.
2547 canvas.SaveLayer({});
2548 {
2549 canvas.DrawPaint({.color = destination_color});
2550 // Draw the source color in an offscreen pass and blend it to the parent
2551 // pass.
2552 canvas.SaveLayer({.blend_mode = blend_mode});
2553 { //
2554 canvas.DrawRect({50, 50, 100, 100}, {.color = color});
2555 }
2556 canvas.Restore();
2557 }
2558 canvas.Restore();
2559 }
2560 canvas.Restore();
2561 canvas.Translate(Vector2(100, 0));
2562 }
2563 canvas.RestoreToCount(0);
2564
2565 //----------------------------------------------------------------------------
2566 /// 2. CPU blend modes (bottom squares).
2567 ///
2568
2569 canvas.Save();
2570 canvas.Translate({0, 100});
2571
2572 // Perform the blend in a SaveLayer so that the initial backdrop color is
2573 // fully transparent black. SourceOver blend the result onto the parent pass.
2574 canvas.SaveLayer({});
2575 // canvas.DrawPaint({.color = destination_color});
2576 for (const auto& color : source_colors) {
2577 // Simply write the CPU blended color to the pass.
2578 canvas.DrawRect({50, 50, 100, 100},
2579 {.color = destination_color.Blend(color, blend_mode),
2580 .blend_mode = BlendMode::kSourceOver});
2581 canvas.Translate(Vector2(100, 0));
2582 }
2583 canvas.RestoreToCount(0);
2584
2585 //----------------------------------------------------------------------------
2586 /// 3. Image blending (top right).
2587 ///
2588 /// Compare these results with the images in the Flutter blend mode
2589 /// documentation: https://api.flutter.dev/flutter/dart-ui/BlendMode.html
2590 ///
2591
2592 canvas.Save();
2593 // canvas.ClipRect(Rect::MakeXYWH(500, 0, 500, 500));
2594 canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver});
2595 {
2596 canvas.DrawImage(dst_image, {400, 50}, {.blend_mode = BlendMode::kSource});
2597 canvas.DrawImage(src_image, {400, 50}, {.blend_mode = blend_mode});
2598 }
2599 canvas.RestoreToCount(0);
2600
2601 return canvas.EndRecordingAsPicture();
2602 }
2603
2604 #define BLEND_MODE_TEST(blend_mode) \
2605 TEST_P(AiksTest, BlendMode##blend_mode) { \
2606 auto src_image = std::make_shared<Image>( \
2607 CreateTextureForFixture("blend_mode_src.png")); \
2608 auto dst_image = std::make_shared<Image>( \
2609 CreateTextureForFixture("blend_mode_dst.png")); \
2610 OpenPlaygroundHere( \
2611 BlendModeTest(BlendMode::k##blend_mode, src_image, dst_image)); \
2612 }
2613 IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TEST)
2614
2615 TEST_P(AiksTest, TranslucentSaveLayerDrawsCorrectly) {
2616 Canvas canvas;
2617
2618 canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
2619
2620 canvas.SaveLayer({.color = Color::Black().WithAlpha(0.5)});
2621 canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
2622 canvas.Restore();
2623
2624 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2625 }
2626
2627 TEST_P(AiksTest, TranslucentSaveLayerWithBlendColorFilterDrawsCorrectly) {
2628 Canvas canvas;
2629
2630 canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
2631
2632 canvas.SaveLayer({
2633 .color = Color::Black().WithAlpha(0.5),
2634 .color_filter =
2635 ColorFilter::MakeBlend(BlendMode::kDestinationOver, Color::Red()),
2636 });
2637 canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
2638 canvas.Restore();
2639
2640 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2641 }
2642
2643 TEST_P(AiksTest, TranslucentSaveLayerWithBlendImageFilterDrawsCorrectly) {
2644 Canvas canvas;
2645
2646 canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
2647
2648 canvas.SaveLayer({
2649 .color = Color::Black().WithAlpha(0.5),
2650 .image_filter = ImageFilter::MakeFromColorFilter(
2651 *ColorFilter::MakeBlend(BlendMode::kDestinationOver, Color::Red())),
2652 });
2653
2654 canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
2655 canvas.Restore();
2656
2657 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2658 }
2659
2660 TEST_P(AiksTest, TranslucentSaveLayerWithColorAndImageFilterDrawsCorrectly) {
2661 Canvas canvas;
2662
2663 canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
2664
2665 canvas.SaveLayer({
2666 .color = Color::Black().WithAlpha(0.5),
2667 .color_filter =
2668 ColorFilter::MakeBlend(BlendMode::kDestinationOver, Color::Red()),
2669 });
2670
2671 canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
2672 canvas.Restore();
2673
2674 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2675 }
2676
2677 TEST_P(AiksTest, TranslucentSaveLayerImageDrawsCorrectly) {
2678 Canvas canvas;
2679
2680 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2681 canvas.DrawImage(image, {100, 100}, {});
2682
2683 canvas.SaveLayer({.color = Color::Black().WithAlpha(0.5)});
2684 canvas.DrawImage(image, {100, 500}, {});
2685 canvas.Restore();
2686
2687 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2688 }
2689
2690 TEST_P(AiksTest, TranslucentSaveLayerWithColorMatrixColorFilterDrawsCorrectly) {
2691 Canvas canvas;
2692
2693 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2694 canvas.DrawImage(image, {100, 100}, {});
2695
2696 canvas.SaveLayer({
2697 .color = Color::Black().WithAlpha(0.5),
2698 .color_filter = ColorFilter::MakeMatrix({.array =
2699 {
2700 1, 0, 0, 0, 0, //
2701 0, 1, 0, 0, 0, //
2702 0, 0, 1, 0, 0, //
2703 0, 0, 0, 2, 0 //
2704 }}),
2705 });
2706 canvas.DrawImage(image, {100, 500}, {});
2707 canvas.Restore();
2708
2709 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2710 }
2711
2712 TEST_P(AiksTest, TranslucentSaveLayerWithColorMatrixImageFilterDrawsCorrectly) {
2713 Canvas canvas;
2714
2715 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2716 canvas.DrawImage(image, {100, 100}, {});
2717
2718 canvas.SaveLayer({
2719 .color = Color::Black().WithAlpha(0.5),
2720 .image_filter = ImageFilter::MakeFromColorFilter(
2721 *ColorFilter::MakeMatrix({.array =
2722 {
2723 1, 0, 0, 0, 0, //
2724 0, 1, 0, 0, 0, //
2725 0, 0, 1, 0, 0, //
2726 0, 0, 0, 2, 0 //
2727 }})),
2728 });
2729 canvas.DrawImage(image, {100, 500}, {});
2730 canvas.Restore();
2731
2732 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2733 }
2734
2735 TEST_P(AiksTest,
2736 TranslucentSaveLayerWithColorFilterAndImageFilterDrawsCorrectly) {
2737 Canvas canvas;
2738
2739 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2740 canvas.DrawImage(image, {100, 100}, {});
2741
2742 canvas.SaveLayer({
2743 .color = Color::Black().WithAlpha(0.5),
2744 .image_filter = ImageFilter::MakeFromColorFilter(
2745 *ColorFilter::MakeMatrix({.array =
2746 {
2747 1, 0, 0, 0, 0, //
2748 0, 1, 0, 0, 0, //
2749 0, 0.2, 1, 0, 0, //
2750 0, 0, 0, 0.5, 0 //
2751 }})),
2752 .color_filter =
2753 ColorFilter::MakeBlend(BlendMode::kModulate, Color::Green()),
2754 });
2755 canvas.DrawImage(image, {100, 500}, {});
2756 canvas.Restore();
2757
2758 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2759 }
2760
2761 TEST_P(AiksTest, TranslucentSaveLayerWithAdvancedBlendModeDrawsCorrectly) {
2762 Canvas canvas;
2763 canvas.DrawRect({0, 0, 400, 400}, {.color = Color::Red()});
2764 canvas.SaveLayer({
2765 .color = Color::Black().WithAlpha(0.5),
2766 .blend_mode = BlendMode::kLighten,
2767 });
2768 canvas.DrawCircle({200, 200}, 100, {.color = Color::Green()});
2769 canvas.Restore();
2770 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2771 }
2772
2773 /// This is a regression check for https://github.com/flutter/engine/pull/41129
2774 /// The entire screen is green if successful. If failing, no frames will render,
2775 /// or the entire screen will be transparent black.
2776 TEST_P(AiksTest, CanRenderTinyOverlappingSubpasses) {
2777 Canvas canvas;
2778 canvas.DrawPaint({.color = Color::Red()});
2779
2780 // Draw two overlapping subpixel circles.
2781 canvas.SaveLayer({});
2782 canvas.DrawCircle({100, 100}, 0.1, {.color = Color::Yellow()});
2783 canvas.Restore();
2784 canvas.SaveLayer({});
2785 canvas.DrawCircle({100, 100}, 0.1, {.color = Color::Yellow()});
2786 canvas.Restore();
2787
2788 canvas.DrawPaint({.color = Color::Green()});
2789
2790 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2791 }
2792
2793 /// Tests that the debug checkerboard displays for offscreen textures when
2794 /// enabled. Most of the complexity here is just to future proof by making pass
2795 /// collapsing hard.
2796 TEST_P(AiksTest, CanRenderOffscreenCheckerboard) {
2797 Canvas canvas;
2798 canvas.debug_options.offscreen_texture_checkerboard = true;
2799
2800 canvas.DrawPaint({.color = Color::AntiqueWhite()});
2801 canvas.DrawCircle({400, 300}, 200,
2802 {.color = Color::CornflowerBlue().WithAlpha(0.75)});
2803
2804 canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
2805 {
2806 canvas.DrawCircle({500, 400}, 200,
2807 {.color = Color::DarkBlue().WithAlpha(0.75)});
2808 canvas.DrawCircle({550, 450}, 200,
2809 {.color = Color::LightCoral().WithAlpha(0.75),
2810 .blend_mode = BlendMode::kLuminosity});
2811 }
2812 canvas.Restore();
2813
2814 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2815 }
2816
2817 TEST_P(AiksTest, OpaqueEntitiesGetCoercedToSource) {
2818 Canvas canvas;
2819 canvas.Scale(Vector2(1.618, 1.618));
2820 canvas.DrawCircle(Point(), 10,
2821 {
2822 .color = Color::CornflowerBlue(),
2823 .blend_mode = BlendMode::kSourceOver,
2824 });
2825 Picture picture = canvas.EndRecordingAsPicture();
2826
2827 // Extract the SolidColorSource.
2828 Entity entity;
2829 std::shared_ptr<SolidColorContents> contents;
2830 picture.pass->IterateAllEntities([&e = entity, &contents](Entity& entity) {
2831 if (ScalarNearlyEqual(entity.GetTransformation().GetScale().x, 1.618f)) {
2832 e = entity;
2833 contents =
2834 std::static_pointer_cast<SolidColorContents>(entity.GetContents());
2835 return false;
2836 }
2837 return true;
2838 });
2839
2840 ASSERT_TRUE(contents->IsOpaque());
2841 ASSERT_EQ(entity.GetBlendMode(), BlendMode::kSource);
2842 }
2843
2844 TEST_P(AiksTest, CanRenderDestructiveSaveLayer) {
2845 Canvas canvas;
2846
2847 canvas.DrawPaint({.color = Color::Red()});
2848 // Draw an empty savelayer with a destructive blend mode, which will replace
2849 // the entire red screen with fully transparent black, except for the green
2850 // circle drawn within the layer.
2851 canvas.SaveLayer({.blend_mode = BlendMode::kSource});
2852 canvas.DrawCircle({300, 300}, 100, {.color = Color::Green()});
2853 canvas.Restore();
2854
2855 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2856 }
2857
2858 TEST_P(AiksTest, CanRenderMaskBlurHugeSigma) {
2859 Canvas canvas;
2860 canvas.DrawCircle({400, 400}, 300,
2861 {.color = Color::Green(),
2862 .mask_blur_descriptor = Paint::MaskBlurDescriptor{
2863 .style = FilterContents::BlurStyle::kNormal,
2864 .sigma = Sigma(99999),
2865 }});
2866 canvas.Restore();
2867
2868 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2869 }
2870
2871 TEST_P(AiksTest, CanRenderBackdropBlurInteractive) {
2872 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
2873 auto [a, b] = IMPELLER_PLAYGROUND_LINE(Point(50, 50), Point(300, 200), 30,
2874 Color::White(), Color::White());
2875
2876 Canvas canvas;
2877 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
2878 canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()});
2879 canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()});
2880 canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()});
2881 canvas.ClipRRect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), 20);
2882 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
2883 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
2884 FilterContents::BlurStyle::kNormal,
2885 Entity::TileMode::kClamp));
2886 canvas.Restore();
2887
2888 return canvas.EndRecordingAsPicture();
2889 };
2890
2891 ASSERT_TRUE(OpenPlaygroundHere(callback));
2892 }
2893
2894 TEST_P(AiksTest, CanRenderBackdropBlur) {
2895 Canvas canvas;
2896 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
2897 canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()});
2898 canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()});
2899 canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()});
2900 canvas.ClipRRect(Rect::MakeLTRB(75, 50, 375, 275), 20);
2901 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
2902 ImageFilter::MakeBlur(Sigma(30.0), Sigma(30.0),
2903 FilterContents::BlurStyle::kNormal,
2904 Entity::TileMode::kClamp));
2905 canvas.Restore();
2906
2907 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2908 }
2909
2910 TEST_P(AiksTest, CanRenderBackdropBlurHugeSigma) {
2911 Canvas canvas;
2912 canvas.DrawCircle({400, 400}, 300, {.color = Color::Green()});
2913 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
2914 ImageFilter::MakeBlur(Sigma(999999), Sigma(999999),
2915 FilterContents::BlurStyle::kNormal,
2916 Entity::TileMode::kClamp));
2917 canvas.Restore();
2918
2919 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2920 }
2921
2922 TEST_P(AiksTest, CanRenderClippedBlur) {
2923 Canvas canvas;
2924 canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
2925 canvas.DrawCircle(
2926 {400, 400}, 200,
2927 {
2928 .color = Color::Green(),
2929 .image_filter = ImageFilter::MakeBlur(
2930 Sigma(20.0), Sigma(20.0), FilterContents::BlurStyle::kNormal,
2931 Entity::TileMode::kClamp),
2932 });
2933 canvas.Restore();
2934
2935 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2936 }
2937
2938 TEST_P(AiksTest, CanRenderForegroundBlendWithMaskBlur) {
2939 // This case triggers the ForegroundPorterDuffBlend path. The color filter
2940 // should apply to the color only, and respect the alpha mask.
2941 Canvas canvas;
2942 canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
2943 canvas.DrawCircle({400, 400}, 200,
2944 {
2945 .color = Color::White(),
2946 .color_filter = ColorFilter::MakeBlend(
2947 BlendMode::kSource, Color::Green()),
2948 .mask_blur_descriptor =
2949 Paint::MaskBlurDescriptor{
2950 .style = FilterContents::BlurStyle::kNormal,
2951 .sigma = Radius(20),
2952 },
2953 });
2954 canvas.Restore();
2955
2956 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2957 }
2958
2959 TEST_P(AiksTest, CanRenderForegroundAdvancedBlendWithMaskBlur) {
2960 // This case triggers the ForegroundAdvancedBlend path. The color filter
2961 // should apply to the color only, and respect the alpha mask.
2962 Canvas canvas;
2963 canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
2964 canvas.DrawCircle({400, 400}, 200,
2965 {
2966 .color = Color::Grey(),
2967 .color_filter = ColorFilter::MakeBlend(
2968 BlendMode::kColor, Color::Green()),
2969 .mask_blur_descriptor =
2970 Paint::MaskBlurDescriptor{
2971 .style = FilterContents::BlurStyle::kNormal,
2972 .sigma = Radius(20),
2973 },
2974 });
2975 canvas.Restore();
2976
2977 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2978 }
2979
2980 // Regression test for https://github.com/flutter/flutter/issues/126701 .
2981 TEST_P(AiksTest, CanRenderClippedRuntimeEffects) {
2982 if (GetParam() != PlaygroundBackend::kMetal) {
2983 GTEST_SKIP_("This backend doesn't support runtime effects.");
2984 }
2985
2986 auto runtime_stage =
2987 OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
2988 ASSERT_TRUE(runtime_stage->IsDirty());
2989
2990 struct FragUniforms {
2991 Vector2 iResolution;
2992 Scalar iTime;
2993 } frag_uniforms = {.iResolution = Vector2(400, 400), .iTime = 100.0};
2994 auto uniform_data = std::make_shared<std::vector<uint8_t>>();
2995 uniform_data->resize(sizeof(FragUniforms));
2996 memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
2997
2998 std::vector<RuntimeEffectContents::TextureInput> texture_inputs;
2999
3000 Paint paint;
3001 paint.color_source = ColorSource::MakeRuntimeEffect(
3002 runtime_stage, uniform_data, texture_inputs);
3003
3004 Canvas canvas;
3005 canvas.Save();
3006 canvas.ClipRRect(Rect{0, 0, 400, 400}, 10.0,
3007 Entity::ClipOperation::kIntersect);
3008 canvas.DrawRect(Rect{0, 0, 400, 400}, paint);
3009 canvas.Restore();
3010
3011 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3012 }
3013
3014 TEST_P(AiksTest, DrawPaintTransformsBounds) {
3015 if (GetParam() != PlaygroundBackend::kMetal) {
3016 GTEST_SKIP_("This backend doesn't support runtime effects.");
3017 }
3018
3019 auto runtime_stage = OpenAssetAsRuntimeStage("gradient.frag.iplr");
3020 ASSERT_TRUE(runtime_stage->IsDirty());
3021
3022 struct FragUniforms {
3023 Size size;
3024 } frag_uniforms = {.size = Size::MakeWH(400, 400)};
3025 auto uniform_data = std::make_shared<std::vector<uint8_t>>();
3026 uniform_data->resize(sizeof(FragUniforms));
3027 memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
3028
3029 std::vector<RuntimeEffectContents::TextureInput> texture_inputs;
3030
3031 Paint paint;
3032 paint.color_source = ColorSource::MakeRuntimeEffect(
3033 runtime_stage, uniform_data, texture_inputs);
3034
3035 Canvas canvas;
3036 canvas.Save();
3037 canvas.Scale(GetContentScale());
3038 canvas.DrawPaint(paint);
3039 canvas.Restore();
3040
3041 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3042 }
3043
3044 TEST_P(AiksTest, CanDrawPoints) {
3045 std::vector<Point> points = {
3046 {0, 0}, //
3047 {100, 100}, //
3048 {100, 0}, //
3049 {0, 100}, //
3050 {0, 0}, //
3051 {48, 48}, //
3052 {52, 52}, //
3053 };
3054 std::vector<PointStyle> caps = {
3055 PointStyle::kRound,
3056 PointStyle::kSquare,
3057 };
3058 Paint paint;
3059 paint.color = Color::Yellow().WithAlpha(0.5);
3060
3061 Paint background;
3062 background.color = Color::Black();
3063
3064 Canvas canvas;
3065 canvas.DrawPaint(background);
3066 canvas.Translate({200, 200});
3067 canvas.DrawPoints(points, 10, paint, PointStyle::kRound);
3068 canvas.Translate({150, 0});
3069 canvas.DrawPoints(points, 10, paint, PointStyle::kSquare);
3070
3071 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3072 }
3073
3074 // Regression test for https://github.com/flutter/flutter/issues/127374.
3075 TEST_P(AiksTest, DrawAtlasWithColorAdvancedAndTransform) {
3076 // Draws the image as four squares stiched together.
3077 auto atlas = CreateTextureForFixture("bay_bridge.jpg");
3078 auto size = atlas->GetSize();
3079 auto image = std::make_shared<Image>(atlas);
3080 // Divide image into four quadrants.
3081 Scalar half_width = size.width / 2;
3082 Scalar half_height = size.height / 2;
3083 std::vector<Rect> texture_coordinates = {
3084 Rect::MakeLTRB(0, 0, half_width, half_height),
3085 Rect::MakeLTRB(half_width, 0, size.width, half_height),
3086 Rect::MakeLTRB(0, half_height, half_width, size.height),
3087 Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
3088 // Position quadrants adjacent to eachother.
3089 std::vector<Matrix> transforms = {
3090 Matrix::MakeTranslation({0, 0, 0}),
3091 Matrix::MakeTranslation({half_width, 0, 0}),
3092 Matrix::MakeTranslation({0, half_height, 0}),
3093 Matrix::MakeTranslation({half_width, half_height, 0})};
3094 std::vector<Color> colors = {Color::Red(), Color::Green(), Color::Blue(),
3095 Color::Yellow()};
3096
3097 Paint paint;
3098
3099 Canvas canvas;
3100 canvas.Scale({0.25, 0.25, 1.0});
3101 canvas.DrawAtlas(image, transforms, texture_coordinates, colors,
3102 BlendMode::kModulate, {}, std::nullopt, paint);
3103
3104 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3105 }
3106
3107 // Regression test for https://github.com/flutter/flutter/issues/127374.
3108 TEST_P(AiksTest, DrawAtlasAdvancedAndTransform) {
3109 // Draws the image as four squares stiched together.
3110 auto atlas = CreateTextureForFixture("bay_bridge.jpg");
3111 auto size = atlas->GetSize();
3112 auto image = std::make_shared<Image>(atlas);
3113 // Divide image into four quadrants.
3114 Scalar half_width = size.width / 2;
3115 Scalar half_height = size.height / 2;
3116 std::vector<Rect> texture_coordinates = {
3117 Rect::MakeLTRB(0, 0, half_width, half_height),
3118 Rect::MakeLTRB(half_width, 0, size.width, half_height),
3119 Rect::MakeLTRB(0, half_height, half_width, size.height),
3120 Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
3121 // Position quadrants adjacent to eachother.
3122 std::vector<Matrix> transforms = {
3123 Matrix::MakeTranslation({0, 0, 0}),
3124 Matrix::MakeTranslation({half_width, 0, 0}),
3125 Matrix::MakeTranslation({0, half_height, 0}),
3126 Matrix::MakeTranslation({half_width, half_height, 0})};
3127
3128 Paint paint;
3129
3130 Canvas canvas;
3131 canvas.Scale({0.25, 0.25, 1.0});
3132 canvas.DrawAtlas(image, transforms, texture_coordinates, {},
3133 BlendMode::kModulate, {}, std::nullopt, paint);
3134
3135 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3136 }
3137
3138 TEST_P(AiksTest, CanDrawPointsWithTextureMap) {
3139 auto texture = CreateTextureForFixture("table_mountain_nx.png",
3140 /*enable_mipmapping=*/true);
3141
3142 std::vector<Point> points = {
3143 {0, 0}, //
3144 {100, 100}, //
3145 {100, 0}, //
3146 {0, 100}, //
3147 {0, 0}, //
3148 {48, 48}, //
3149 {52, 52}, //
3150 };
3151 std::vector<PointStyle> caps = {
3152 PointStyle::kRound,
3153 PointStyle::kSquare,
3154 };
3155 Paint paint;
3156 paint.color_source = ColorSource::MakeImage(texture, Entity::TileMode::kClamp,
3157 Entity::TileMode::kClamp, {}, {});
3158
3159 Canvas canvas;
3160 canvas.Translate({200, 200});
3161 canvas.DrawPoints(points, 100, paint, PointStyle::kRound);
3162 canvas.Translate({150, 0});
3163 canvas.DrawPoints(points, 100, paint, PointStyle::kSquare);
3164
3165 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3166 }
3167
3168 // This currently renders solid blue, as the support for text color sources was
3169 // moved into DLDispatching. Path data requires the SkTextBlobs which are not
3170 // used in impeller::TextFrames.
3171 TEST_P(AiksTest, TextForegroundShaderWithTransform) {
3172 auto mapping = OpenFixtureAsSkData("Roboto-Regular.ttf");
3173 ASSERT_NE(mapping, nullptr);
3174
3175 Scalar font_size = 100;
3176 SkFont sk_font(SkTypeface::MakeFromData(mapping), font_size);
3177
3178 Paint text_paint;
3179 text_paint.color = Color::Blue();
3180
3181 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
3182 Color{0.1294, 0.5882, 0.9529, 1.0}};
3183 std::vector<Scalar> stops = {
3184 0.0,
3185 1.0,
3186 };
3187 text_paint.color_source = ColorSource::MakeLinearGradient(
3188 {0, 0}, {100, 100}, std::move(colors), std::move(stops),
3189 Entity::TileMode::kRepeat, {});
3190
3191 Canvas canvas;
3192 canvas.Translate({100, 100});
3193 canvas.Rotate(Radians(kPi / 4));
3194
3195 auto blob = SkTextBlob::MakeFromString("Hello", sk_font);
3196 ASSERT_NE(blob, nullptr);
3197 auto frame = MakeTextFrameFromTextBlobSkia(blob);
3198 canvas.DrawTextFrame(frame, Point(), text_paint);
3199
3200 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3201 }
3202
3203 TEST_P(AiksTest, CanCanvasDrawPicture) {
3204 Canvas subcanvas;
3205 subcanvas.DrawRect(Rect::MakeLTRB(-100, -50, 100, 50),
3206 {.color = Color::CornflowerBlue()});
3207 auto picture = subcanvas.EndRecordingAsPicture();
3208
3209 Canvas canvas;
3210 canvas.Translate({200, 200});
3211 canvas.Rotate(Radians(kPi / 4));
3212 canvas.DrawPicture(picture);
3213
3214 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3215 }
3216
3217 TEST_P(AiksTest, CanCanvasDrawPictureWithAdvancedBlend) {
3218 Canvas subcanvas;
3219 subcanvas.DrawRect(
3220 Rect::MakeLTRB(-100, -50, 100, 50),
3221 {.color = Color::CornflowerBlue(), .blend_mode = BlendMode::kColorDodge});
3222 auto picture = subcanvas.EndRecordingAsPicture();
3223
3224 Canvas canvas;
3225 canvas.DrawPaint({.color = Color::Black()});
3226 canvas.DrawCircle(Point::MakeXY(150, 150), 25, {.color = Color::Red()});
3227 canvas.DrawPicture(picture);
3228
3229 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3230 }
3231
3232 TEST_P(AiksTest, CanCanvasDrawPictureWithBackdropFilter) {
3233 Canvas subcanvas;
3234 subcanvas.SaveLayer({}, {},
3235 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
3236 FilterContents::BlurStyle::kNormal,
3237 Entity::TileMode::kDecal));
3238 auto image = std::make_shared<Image>(CreateTextureForFixture("kalimba.jpg"));
3239 Paint paint;
3240 paint.color = Color::Red().WithAlpha(0.5);
3241 subcanvas.DrawImage(image, Point::MakeXY(100.0, 100.0), paint);
3242
3243 auto picture = subcanvas.EndRecordingAsPicture();
3244
3245 Canvas canvas;
3246 canvas.DrawPaint({.color = Color::Black()});
3247 canvas.DrawCircle(Point::MakeXY(150, 150), 25, {.color = Color::Red()});
3248 canvas.DrawPicture(picture);
3249
3250 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3251 }
3252
3253 TEST_P(AiksTest, DrawPictureWithText) {
3254 Canvas subcanvas;
3255 ASSERT_TRUE(RenderTextInCanvasSkia(
3256 GetContext(), subcanvas,
3257 "the quick brown fox jumped over the lazy dog!.?", "Roboto-Regular.ttf"));
3258 subcanvas.Translate({0, 10});
3259 subcanvas.Scale(Vector2(3, 3));
3260 ASSERT_TRUE(RenderTextInCanvasSkia(
3261 GetContext(), subcanvas,
3262 "the quick brown fox jumped over the very big lazy dog!.?",
3263 "Roboto-Regular.ttf"));
3264 auto picture = subcanvas.EndRecordingAsPicture();
3265
3266 Canvas canvas;
3267 canvas.Scale(Vector2(.2, .2));
3268 canvas.Save();
3269 canvas.Translate({200, 200});
3270 canvas.Scale(Vector2(3.5, 3.5)); // The text must not be blurry after this.
3271 canvas.DrawPicture(picture);
3272 canvas.Restore();
3273
3274 canvas.Scale(Vector2(1.5, 1.5));
3275 ASSERT_TRUE(RenderTextInCanvasSkia(
3276 GetContext(), canvas,
3277 "the quick brown fox jumped over the smaller lazy dog!.?",
3278 "Roboto-Regular.ttf"));
3279 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3280 }
3281
3282 TEST_P(AiksTest, DrawPictureClipped) {
3283 Canvas subcanvas;
3284 subcanvas.ClipRRect(Rect::MakeLTRB(100, 100, 400, 400), 15);
3285 subcanvas.DrawPaint({.color = Color::Red()});
3286 auto picture = subcanvas.EndRecordingAsPicture();
3287
3288 Canvas canvas;
3289 canvas.DrawPaint({.color = Color::CornflowerBlue()});
3290
3291 // Draw a red RRect via DrawPicture.
3292 canvas.DrawPicture(picture);
3293
3294 // Draw over the picture with a larger green rectangle, completely covering it
3295 // up.
3296 canvas.ClipRRect(Rect::MakeLTRB(100, 100, 400, 400).Expand(20), 15);
3297 canvas.DrawPaint({.color = Color::Green()});
3298
3299 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3300 }
3301
3302 TEST_P(AiksTest, MatrixSaveLayerFilter) {
3303 Canvas canvas;
3304 canvas.DrawPaint({.color = Color::Black()});
3305 canvas.SaveLayer({}, std::nullopt);
3306 {
3307 canvas.DrawCircle(Point(200, 200), 100,
3308 {.color = Color::Green().WithAlpha(0.5),
3309 .blend_mode = BlendMode::kPlus});
3310 // Should render a second circle, centered on the bottom-right-most edge of
3311 // the circle.
3312 canvas.SaveLayer({.image_filter = ImageFilter::MakeMatrix(
3313 Matrix::MakeTranslation(Vector2(1, 1) *
3314 (200 + 100 * k1OverSqrt2)) *
3315 Matrix::MakeScale(Vector2(1, 1) * 0.5) *
3316 Matrix::MakeTranslation(Vector2(-200, -200)),
3317 SamplerDescriptor{})},
3318 std::nullopt);
3319 canvas.DrawCircle(Point(200, 200), 100,
3320 {.color = Color::Green().WithAlpha(0.5),
3321 .blend_mode = BlendMode::kPlus});
3322 canvas.Restore();
3323 }
3324 canvas.Restore();
3325
3326 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3327 }
3328
3329 TEST_P(AiksTest, MatrixBackdropFilter) {
3330 Canvas canvas;
3331 canvas.DrawPaint({.color = Color::Black()});
3332 canvas.SaveLayer({}, std::nullopt);
3333 {
3334 canvas.DrawCircle(Point(200, 200), 100,
3335 {.color = Color::Green().WithAlpha(0.5),
3336 .blend_mode = BlendMode::kPlus});
3337 // Should render a second circle, centered on the bottom-right-most edge of
3338 // the circle.
3339 canvas.SaveLayer(
3340 {}, std::nullopt,
3341 ImageFilter::MakeMatrix(
3342 Matrix::MakeTranslation(Vector2(1, 1) * (100 + 100 * k1OverSqrt2)) *
3343 Matrix::MakeScale(Vector2(1, 1) * 0.5) *
3344 Matrix::MakeTranslation(Vector2(-100, -100)),
3345 SamplerDescriptor{}));
3346 canvas.Restore();
3347 }
3348 canvas.Restore();
3349
3350 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3351 }
3352
3353 TEST_P(AiksTest, SolidColorApplyColorFilter) {
3354 auto contents = SolidColorContents();
3355 contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75));
3356 auto result = contents.ApplyColorFilter([](const Color& color) {
3357 return color.Blend(Color::LimeGreen().WithAlpha(0.75), BlendMode::kScreen);
3358 });
3359 ASSERT_TRUE(result);
3360 ASSERT_COLOR_NEAR(contents.GetColor(),
3361 Color(0.433247, 0.879523, 0.825324, 0.75));
3362 }
3363
3364 #define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \
3365 TEST_P(AiksTest, name##GradientApplyColorFilter) { \
3366 auto contents = name##GradientContents(); \
3367 contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \
3368 auto result = contents.ApplyColorFilter([](const Color& color) { \
3369 return color.Blend(Color::LimeGreen().WithAlpha(0.75), \
3370 BlendMode::kScreen); \
3371 }); \
3372 ASSERT_TRUE(result); \
3373 \
3374 std::vector<Color> expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \
3375 ASSERT_COLORS_NEAR(contents.GetColors(), expected); \
3376 }
3377
3378 APPLY_COLOR_FILTER_GRADIENT_TEST(Linear);
3379 APPLY_COLOR_FILTER_GRADIENT_TEST(Radial);
3380 APPLY_COLOR_FILTER_GRADIENT_TEST(Conical);
3381 APPLY_COLOR_FILTER_GRADIENT_TEST(Sweep);
3382
3383 TEST_P(AiksTest, DrawScaledTextWithPerspectiveNoSaveLayer) {
3384 Canvas canvas;
3385 // clang-format off
3386 canvas.Transform(Matrix(
3387 2.000000, 0.000000, 0.000000, 0.000000,
3388 1.445767, 2.637070, -0.507928, 0.001524,
3389 -2.451887, -0.534662, 0.861399, -0.002584,
3390 1063.481934, 1025.951416, -48.300270, 1.144901
3391 ));
3392 // clang-format on
3393
3394 ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), canvas, "Hello world",
3395 "Roboto-Regular.ttf"));
3396
3397 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3398 }
3399
3400 TEST_P(AiksTest, DrawScaledTextWithPerspectiveSaveLayer) {
3401 Canvas canvas;
3402 Paint save_paint;
3403 canvas.SaveLayer(save_paint);
3404 // clang-format off
3405 canvas.Transform(Matrix(
3406 2.000000, 0.000000, 0.000000, 0.000000,
3407 1.445767, 2.637070, -0.507928, 0.001524,
3408 -2.451887, -0.534662, 0.861399, -0.002584,
3409 1063.481934, 1025.951416, -48.300270, 1.144901
3410 ));
3411 // clang-format on
3412
3413 ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), canvas, "Hello world",
3414 "Roboto-Regular.ttf"));
3415 }
3416
3417 TEST_P(AiksTest, PipelineBlendSingleParameter) {
3418 Canvas canvas;
3419
3420 // Should render a green square in the middle of a blue circle.
3421 canvas.SaveLayer({});
3422 {
3423 canvas.Translate(Point(100, 100));
3424 canvas.DrawCircle(Point(200, 200), 200, {.color = Color::Blue()});
3425 canvas.ClipRect(Rect(100, 100, 200, 200));
3426 canvas.DrawCircle(Point(200, 200), 200,
3427 {
3428 .color = Color::Green(),
3429 .blend_mode = BlendMode::kSourceOver,
3430 .image_filter = ImageFilter::MakeFromColorFilter(
3431 *ColorFilter::MakeBlend(BlendMode::kDestination,
3432 Color::White())),
3433 });
3434 canvas.Restore();
3435 }
3436
3437 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3438 }
3439
3440 TEST_P(AiksTest, ClippedBlurFilterRendersCorrectlyInteractive) {
3441 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
3442 auto point = IMPELLER_PLAYGROUND_POINT(Point(400, 400), 20, Color::Green());
3443
3444 Canvas canvas;
3445 canvas.Translate(point - Point(400, 400));
3446 Paint paint;
3447 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
3448 .style = FilterContents::BlurStyle::kNormal,
3449 .sigma = Radius{120 * 3},
3450 };
3451 paint.color = Color::Red();
3452 PathBuilder builder{};
3453 builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
3454 canvas.DrawPath(builder.TakePath(), paint);
3455 return canvas.EndRecordingAsPicture();
3456 };
3457 ASSERT_TRUE(OpenPlaygroundHere(callback));
3458 }
3459
3460 TEST_P(AiksTest, ClippedBlurFilterRendersCorrectly) {
3461 Canvas canvas;
3462 canvas.Translate(Point(0, -400));
3463 Paint paint;
3464 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
3465 .style = FilterContents::BlurStyle::kNormal,
3466 .sigma = Radius{120 * 3},
3467 };
3468 paint.color = Color::Red();
3469 PathBuilder builder{};
3470 builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
3471 canvas.DrawPath(builder.TakePath(), paint);
3472 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3473 }
3474
3475 TEST_P(AiksTest, CaptureContext) {
3476 auto capture_context = CaptureContext::MakeAllowlist({"TestDocument"});
3477
3478 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
3479 Canvas canvas;
3480
3481 capture_context.Rewind();
3482 auto document = capture_context.GetDocument("TestDocument");
3483
3484 auto color = document.AddColor("Background color", Color::CornflowerBlue());
3485 canvas.DrawPaint({.color = color});
3486
3487 ImGui::Begin("TestDocument", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
3488 document.GetElement()->properties.Iterate([](CaptureProperty& property) {
3489 property.Invoke({.color = [](CaptureColorProperty& p) {
3490 ImGui::ColorEdit4(p.label.c_str(), reinterpret_cast<float*>(&p.value));
3491 }});
3492 });
3493 ImGui::End();
3494
3495 return canvas.EndRecordingAsPicture();
3496 };
3497 OpenPlaygroundHere(callback);
3498 }
3499
3500 TEST_P(AiksTest, CaptureInactivatedByDefault) {
3501 ASSERT_FALSE(GetContext()->capture.IsActive());
3502 }
3503
3504 // Regression test for https://github.com/flutter/flutter/issues/134678.
3505 TEST_P(AiksTest, ReleasesTextureOnTeardown) {
3506 auto context = GetContext();
3507 std::weak_ptr<Texture> weak_texture;
3508
3509 {
3510 auto texture = CreateTextureForFixture("table_mountain_nx.png");
3511
3512 Canvas canvas;
3513 canvas.Scale(GetContentScale());
3514 canvas.Translate({100.0f, 100.0f, 0});
3515
3516 Paint paint;
3517 paint.color_source = ColorSource::MakeImage(
3518 texture, Entity::TileMode::kClamp, Entity::TileMode::kClamp, {}, {});
3519 canvas.DrawRect({0, 0, 600, 600}, paint);
3520
3521 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3522 }
3523
3524 // See https://github.com/flutter/flutter/issues/134751.
3525 //
3526 // If the fence waiter was working this may not be released by the end of the
3527 // scope above. Adding a manual shutdown so that future changes to the fence
3528 // waiter will not flake this test.
3529 context->Shutdown();
3530
3531 // The texture should be released by now.
3532 ASSERT_TRUE(weak_texture.expired()) << "When the texture is no longer in use "
3533 "by the backend, it should be "
3534 "released.";
3535 }
3536
3537 // Regression test for https://github.com/flutter/flutter/issues/135441 .
3538 TEST_P(AiksTest, VerticesGeometryUVPositionData) {
3539 Canvas canvas;
3540 Paint paint;
3541 auto texture = CreateTextureForFixture("table_mountain_nx.png");
3542
3543 paint.color_source = ColorSource::MakeImage(texture, Entity::TileMode::kClamp,
3544 Entity::TileMode::kClamp, {}, {});
3545
3546 auto vertices = {Point(0, 0), Point(texture->GetSize().width, 0),
3547 Point(0, texture->GetSize().height)};
3548 std::vector<uint16_t> indices = {0u, 1u, 2u};
3549 std::vector<Point> texture_coordinates = {};
3550 std::vector<Color> vertex_colors = {};
3551 auto geometry = std::make_shared<VerticesGeometry>(
3552 vertices, indices, texture_coordinates, vertex_colors,
3553 Rect::MakeLTRB(0, 0, 1, 1), VerticesGeometry::VertexMode::kTriangleStrip);
3554
3555 canvas.DrawVertices(geometry, BlendMode::kSourceOver, paint);
3556 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3557 }
3558
3559 TEST_P(AiksTest, ClearBlendWithBlur) {
3560 Canvas canvas;
3561 Paint white;
3562 white.color = Color::Blue();
3563 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white);
3564
3565 Paint clear;
3566 clear.blend_mode = BlendMode::kClear;
3567 clear.mask_blur_descriptor = Paint::MaskBlurDescriptor{
3568 .style = FilterContents::BlurStyle::kNormal,
3569 .sigma = Sigma(20),
3570 };
3571
3572 canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear);
3573
3574 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3575 }
3576
3577 TEST_P(AiksTest, ClearBlend) {
3578 Canvas canvas;
3579 Paint white;
3580 white.color = Color::Blue();
3581 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white);
3582
3583 Paint clear;
3584 clear.blend_mode = BlendMode::kClear;
3585
3586 canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear);
3587 }
3588
3589 TEST_P(AiksTest, MatrixImageFilterMagnify) {
3590 Canvas canvas;
3591 canvas.Scale(GetContentScale());
3592 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
3593 canvas.Translate({600, -200});
3594 canvas.SaveLayer({
3595 .image_filter = std::make_shared<MatrixImageFilter>(
3596 Matrix{
3597 2, 0, 0, 0, //
3598 0, 2, 0, 0, //
3599 0, 0, 2, 0, //
3600 0, 0, 0, 1 //
3601 },
3602 SamplerDescriptor{}),
3603 });
3604 canvas.DrawImage(image, {0, 0}, Paint{.color = Color(1.0, 1.0, 1.0, 0.5)});
3605 canvas.Restore();
3606
3607 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3608 }
3609
3610 // This should be solid red, if you see a little red box this is broken.
3611 TEST_P(AiksTest, ClearColorOptimizationWhenSubpassIsBiggerThanParentPass) {
3612 Canvas canvas;
3613 canvas.Scale(GetContentScale());
3614 canvas.DrawRect(Rect::MakeLTRB(200, 200, 300, 300), {.color = Color::Red()});
3615 canvas.SaveLayer({
3616 .image_filter = std::make_shared<MatrixImageFilter>(
3617 Matrix::MakeScale({2, 2, 1}), SamplerDescriptor{}),
3618 });
3619 // Draw a rectangle that would fully cover the parent pass size, but not
3620 // the subpass that it is rendered in.
3621 canvas.DrawRect(Rect::MakeLTRB(0, 0, 400, 400), {.color = Color::Green()});
3622 // Draw a bigger rectangle to force the subpass to be bigger.
3623 canvas.DrawRect(Rect::MakeLTRB(0, 0, 800, 800), {.color = Color::Red()});
3624 canvas.Restore();
3625
3626 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3627 }
3628
3629 TEST_P(AiksTest, MaskBlurWithZeroSigmaIsSkipped) {
3630 Canvas canvas;
3631
3632 Paint paint = {
3633 .color = Color::White(),
3634 .mask_blur_descriptor =
3635 Paint::MaskBlurDescriptor{
3636 .style = FilterContents::BlurStyle::kNormal,
3637 .sigma = Sigma(0),
3638 },
3639 };
3640
3641 canvas.DrawCircle({300, 300}, 200, paint);
3642 canvas.DrawRect(Rect::MakeLTRB(100, 300, 500, 600), paint);
3643
3644 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3645 }
3646
3647 TEST_P(AiksTest, SubpassWithClearColorOptimization) {
3648 Canvas canvas;
3649
3650 // Use a non-srcOver blend mode to ensure that we don't detect this as an
3651 // opacity peephole optimization.
3652 canvas.SaveLayer(
3653 {.color = Color::Blue().WithAlpha(0.5), .blend_mode = BlendMode::kSource},
3654 Rect::MakeLTRB(0, 0, 200, 200));
3655 canvas.DrawPaint(
3656 {.color = Color::BlackTransparent(), .blend_mode = BlendMode::kSource});
3657 canvas.Restore();
3658
3659 canvas.SaveLayer(
3660 {.color = Color::Blue(), .blend_mode = BlendMode::kDestinationOver});
3661 canvas.Restore();
3662
3663 // This playground should appear blank on CI since we are only drawing
3664 // transparent black. If the clear color optimization is broken, the texture
3665 // will be filled with NaNs and may produce a magenta texture on macOS or iOS.
3666 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3667 }
3668
3669 } // namespace testing
3670 } // namespace impeller
BLEND_MODE_TEST
#define BLEND_MODE_TEST(blend_mode)
Definition: aiks_unittests.cc:2604
impeller::Paint::stroke_cap
Cap stroke_cap
Definition: paint.h:59
impeller::BlendMode
BlendMode
Definition: color.h:57
impeller::Color::Blue
static constexpr Color Blue()
Definition: color.h:266
typeface_stb.h
impeller::AiksPlayground
Definition: aiks_playground.h:16
ASSERT_COLOR_NEAR
#define ASSERT_COLOR_NEAR(a, b)
Definition: geometry_asserts.h:155
impeller::Entity::TileMode::kClamp
@ kClamp
impeller::k1OverSqrt2
constexpr float k1OverSqrt2
Definition: constants.h:49
impeller::testing::TEST_P
TEST_P(AiksTest, SubpassWithClearColorOptimization)
Definition: aiks_unittests.cc:3647
impeller::Canvas::EndRecordingAsPicture
Picture EndRecordingAsPicture()
Definition: canvas.cc:519
impeller::Picture::pass
std::unique_ptr< EntityPass > pass
Definition: picture.h:20
impeller::Cap::kRound
@ kRound
capture.h
impeller::PathBuilder::AddQuadraticCurve
PathBuilder & AddQuadraticCurve(Point p1, Point cp, Point p2)
Move to point p1, then insert a quadradic curve from p1 to p2 with the control point cp.
Definition: path_builder.cc:166
impeller::Entity::ClipOperation::kDifference
@ kDifference
impeller::Scalar
float Scalar
Definition: scalar.h:15
image_filter.h
impeller::AiksContext
Definition: aiks_context.h:20
impeller::testing::RenderTextInCanvasSkia
bool RenderTextInCanvasSkia(const std::shared_ptr< Context > &context, Canvas &canvas, const std::string &text, const std::string &font_fixture, TextRenderOptions options={})
Definition: aiks_unittests.cc:1303
impeller::Color::Red
static constexpr Color Red()
Definition: color.h:262
impeller::testing::CanRenderSweepGradientWithDithering
static void CanRenderSweepGradientWithDithering(AiksTest *aiks_test, bool use_dithering)
Definition: aiks_unittests.cc:480
geometry_asserts.h
impeller::Paint::Style::kStroke
@ kStroke
impeller::ColorSource::MakeLinearGradient
static ColorSource MakeLinearGradient(Point start_point, Point end_point, std::vector< Color > colors, std::vector< Scalar > stops, Entity::TileMode tile_mode, Matrix effect_transform)
Definition: color_source.cc:45
impeller::Paint
Definition: paint.h:25
impeller::FillType::kOdd
@ kOdd
impeller::testing::BlendModeTest
static Picture BlendModeTest(BlendMode blend_mode, const std::shared_ptr< Image > &src_image, const std::shared_ptr< Image > &dst_image)
Definition: aiks_unittests.cc:2523
impeller::testing::AiksTest
AiksPlayground AiksTest
Definition: aiks_unittests.cc:52
impeller::TRect< Scalar >::MakeXYWH
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:47
impeller::Canvas::Skew
void Skew(Scalar sx, Scalar sy)
Definition: canvas.cc:154
impeller::Color::MakeRGBA8
static constexpr Color MakeRGBA8(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Definition: color.h:152
solid_color_contents.h
impeller::Color
Definition: color.h:122
impeller::Paint::color
Color color
Definition: paint.h:54
paint_pass_delegate.h
impeller::Entity::TileMode::kDecal
@ kDecal
impeller::testing::CanRenderConicalGradientWithDithering
static void CanRenderConicalGradientWithDithering(AiksTest *aiks_test, bool use_dithering)
Definition: aiks_unittests.cc:509
impeller::Canvas::DrawTextFrame
void DrawTextFrame(const std::shared_ptr< TextFrame > &text_frame, Point position, const Paint &paint)
Definition: canvas.cc:563
impeller::Canvas
Definition: canvas.h:50
impeller::PathBuilder
Definition: path_builder.h:13
impeller::Paint::MaskBlurDescriptor::style
FilterContents::BlurStyle style
Definition: paint.h:42
impeller::BlendMode::kSourceIn
@ kSourceIn
tiled_texture_contents.h
impeller::Vector2
Point Vector2
Definition: point.h:310
impeller::Canvas::DrawVertices
void DrawVertices(const std::shared_ptr< VerticesGeometry > &vertices, BlendMode blend_mode, const Paint &paint)
Definition: canvas.cc:606
impeller::kPi
constexpr float kPi
Definition: constants.h:25
impeller::testing::OpenFixtureAsSkData
static sk_sp< SkData > OpenFixtureAsSkData(const char *fixture_name)
Definition: aiks_unittests.cc:1282
BLEND_MODE_TUPLE
#define BLEND_MODE_TUPLE(blend_mode)
Definition: aiks_unittests.cc:1603
impeller::Paint::MaskBlurDescriptor
Definition: paint.h:41
impeller::ColorSource::MakeSweepGradient
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)
Definition: color_source.cc:141
IMPELLER_PLAYGROUND_POINT
#define IMPELLER_PLAYGROUND_POINT(default_position, radius, color)
Definition: widgets.h:14
impeller::testing::TextRenderOptions
Definition: aiks_unittests.cc:1297
impeller::Canvas::DebugOptions::offscreen_texture_checkerboard
bool offscreen_texture_checkerboard
Definition: canvas.h:57
impeller::PathBuilder::AddRoundedRect
PathBuilder & AddRoundedRect(Rect rect, RoundingRadii radii)
Definition: path_builder.cc:207
impeller::Color::Yellow
static constexpr Color Yellow()
Definition: color.h:832
impeller::Radians::radians
Scalar radians
Definition: scalar.h:36
impeller::Color::Fuchsia
static constexpr Color Fuchsia()
Definition: color.h:456
impeller::ColorSource::MakeImage
static ColorSource MakeImage(std::shared_ptr< Texture > texture, Entity::TileMode x_tile_mode, Entity::TileMode y_tile_mode, SamplerDescriptor sampler_descriptor, Matrix effect_transform)
Definition: color_source.cc:167
impeller::Size
TSize< Scalar > Size
Definition: size.h:135
impeller::Canvas::debug_options
struct impeller::Canvas::DebugOptions debug_options
impeller::FilterContents::BlurStyle::kNormal
@ kNormal
Blurred inside and outside.
impeller::Canvas::SaveLayer
void SaveLayer(const Paint &paint, std::optional< Rect > bounds=std::nullopt, const std::shared_ptr< ImageFilter > &backdrop_filter=nullptr)
Definition: canvas.cc:538
impeller::Entity::TileMode::kRepeat
@ kRepeat
impeller::Matrix::MakeTranslation
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:94
impeller::Paint::color_source
ColorSource color_source
Definition: paint.h:55
impeller::Vector3::x
Scalar x
Definition: vector.h:20
impeller::PathBuilder::AddRect
PathBuilder & AddRect(Rect rect)
Definition: path_builder.cc:181
impeller::Color::Maroon
static constexpr Color Maroon()
Definition: color.h:604
impeller::PathBuilder::RoundingRadii::bottom_right
Point bottom_right
Definition: path_builder.h:112
impeller::testing::TextRenderOptions::font_size
Scalar font_size
Definition: aiks_unittests.cc:1298
impeller::testing::RenderTextInCanvasSTB
bool RenderTextInCanvasSTB(const std::shared_ptr< Context > &context, Canvas &canvas, const std::string &text, const std::string &font_fixture, TextRenderOptions options={})
Definition: aiks_unittests.cc:1336
impeller::Matrix::vec
Vector4 vec[4]
Definition: matrix.h:40
impeller::kPiOver2
constexpr float kPiOver2
Definition: constants.h:31
typographer_context_skia.h
impeller::Canvas::DrawImage
void DrawImage(const std::shared_ptr< Image > &image, Point offset, const Paint &paint, SamplerDescriptor sampler={})
Definition: canvas.cc:473
impeller::Canvas::DrawPicture
void DrawPicture(const Picture &picture)
Definition: canvas.cc:443
impeller::Entity::GetTransformation
const Matrix & GetTransformation() const
Definition: entity.cc:49
impeller::Entity::TileMode::kMirror
@ kMirror
impeller::MakeTextFrameSTB
std::shared_ptr< TextFrame > MakeTextFrameSTB(const std::shared_ptr< TypefaceSTB > &typeface_stb, Font::Metrics metrics, const std::string &text)
Definition: text_frame_stb.cc:11
impeller::Entity::SetContents
void SetContents(std::shared_ptr< Contents > contents)
Definition: entity.cc:81
path_builder.h
impeller::kPhi
constexpr float kPhi
Definition: constants.h:52
impeller::SamplerDescriptor
Definition: sampler_descriptor.h:18
impeller::PathBuilder::RoundingRadii
Definition: path_builder.h:108
impeller::testing::INSTANTIATE_PLAYGROUND_SUITE
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
matrix.h
impeller::kColor
@ kColor
Definition: geometry.h:27
impeller::Entity
Definition: entity.h:21
text_frame_skia.h
impeller::Paint::color_filter
std::shared_ptr< ColorFilter > color_filter
Definition: paint.h:67
impeller::testing::TEST_P
TEST_P(AiksTest, RotateColorFilteredPath)
Definition: aiks_unittests.cc:56
impeller::Picture
Definition: picture.h:19
impeller::TSize
Definition: size.h:18
impeller::Canvas::DrawImageRect
void DrawImageRect(const std::shared_ptr< Image > &image, Rect source, Rect dest, const Paint &paint, SamplerDescriptor sampler={})
Definition: canvas.cc:488
impeller::Point
TPoint< Scalar > Point
Definition: point.h:306
impeller::k2Pi
constexpr float k2Pi
Definition: constants.h:28
impeller::BlendMode::kSourceOver
@ kSourceOver
impeller::CaptureContext
Definition: capture.h:263
impeller::Canvas::Scale
void Scale(const Vector2 &scale)
Definition: canvas.cc:146
color_source_contents.h
node.h
material.h
impeller::Radius
For convolution filters, the "radius" is the size of the convolution kernel to use on the local space...
Definition: sigma.h:47
impeller::Matrix::GetScale
constexpr Vector3 GetScale() const
Definition: matrix.h:301
impeller::Path
Paths are lightweight objects that describe a collection of linear, quadratic, or cubic segments....
Definition: path.h:54
impeller::ColorSource::MakeConicalGradient
static ColorSource MakeConicalGradient(Point center, Scalar radius, std::vector< Color > colors, std::vector< Scalar > stops, Point focus_center, Scalar focus_radius, Entity::TileMode tile_mode, Matrix effect_transform)
Definition: color_source.cc:75
impeller::ColorFilter::MakeBlend
static std::shared_ptr< ColorFilter > MakeBlend(BlendMode blend_mode, Color color)
Definition: color_filter.cc:20
widgets.h
impeller::Canvas::Save
void Save()
Definition: canvas.cc:56
impeller::testing::GetBlendModeSelection
static BlendModeSelection GetBlendModeSelection()
Definition: aiks_unittests.cc:1610
conical_gradient_contents.h
impeller::Picture::Snapshot
std::optional< Snapshot > Snapshot(AiksContext &context)
Definition: picture.cc:17
impeller::GoldenPlaygroundTest
Definition: golden_playground_test.h:22
text_frame_stb.h
impeller::Paint::style
Style style
Definition: paint.h:62
impeller::EntityPass::Element
std::variant< Entity, std::unique_ptr< EntityPass > > Element
Definition: entity_pass.h:37
impeller::Canvas::DrawCircle
void DrawCircle(Point center, Scalar radius, const Paint &paint)
Definition: canvas.cc:277
impeller::Color::WithAlpha
constexpr Color WithAlpha(Scalar new_alpha) const
Definition: color.h:268
impeller::Paint::Style::kFill
@ kFill
impeller::SolidColorContents
Definition: solid_color_contents.h:24
impeller::Color::SkyBlue
static constexpr Color SkyBlue()
Definition: color.h:772
impeller::Canvas::DrawRRect
void DrawRRect(Rect rect, Scalar corner_radius, const Paint &paint)
Definition: canvas.cc:255
APPLY_COLOR_FILTER_GRADIENT_TEST
#define APPLY_COLOR_FILTER_GRADIENT_TEST(name)
Definition: aiks_unittests.cc:3364
impeller::testing::CanRenderRadialGradientWithDithering
static void CanRenderRadialGradientWithDithering(AiksTest *aiks_test, bool use_dithering)
Definition: aiks_unittests.cc:453
impeller::PathBuilder::LineTo
PathBuilder & LineTo(Point point, bool relative=false)
Insert a line from the current position to point.
Definition: path_builder.cc:46
impeller::Entity::GetContents
const std::shared_ptr< Contents > & GetContents() const
Definition: entity.cc:85
impeller::Color::White
static constexpr Color White()
Definition: color.h:254
impeller::Sigma
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:31
impeller::PathBuilder::RoundingRadii::top_left
Point top_left
Definition: path_builder.h:109
impeller::Canvas::Restore
bool Restore()
Definition: canvas.cc:92
impeller::Radians
Definition: scalar.h:35
impeller::Canvas::DrawAtlas
void DrawAtlas(const std::shared_ptr< Image > &atlas, std::vector< Matrix > transforms, std::vector< Rect > texture_coordinates, std::vector< Color > colors, BlendMode blend_mode, SamplerDescriptor sampler, std::optional< Rect > cull_rect, const Paint &paint)
Definition: canvas.cc:667
impeller::PathBuilder::AddLine
PathBuilder & AddLine(const Point &p1, const Point &p2)
Move to point p1, then insert a line from p1 to p2.
Definition: path_builder.cc:433
impeller::testing::CanRenderLinearGradientWithDithering
static void CanRenderLinearGradientWithDithering(AiksTest *aiks_test, bool use_dithering)
Definition: aiks_unittests.cc:425
canvas.h
impeller::Color::Green
static constexpr Color Green()
Definition: color.h:264
impeller::Canvas::DrawPath
void DrawPath(const Path &path, const Paint &paint)
Definition: canvas.cc:174
impeller::PathBuilder::TakePath
Path TakePath(FillType fill=FillType::kNonZero)
Definition: path_builder.cc:21
impeller::Canvas::DrawPaint
void DrawPaint(const Paint &paint)
Definition: canvas.cc:184
filter_input.h
impeller::Rect
TRect< Scalar > Rect
Definition: rect.h:306
golden_playground_test.h
impeller::Canvas::GetSaveCount
size_t GetSaveCount() const
Definition: canvas.cc:162
impeller::Join::kRound
@ kRound
ASSERT_MATRIX_NEAR
#define ASSERT_MATRIX_NEAR(a, b)
Definition: geometry_asserts.h:152
impeller::testing::TextRenderOptions::position
Point position
Definition: aiks_unittests.cc:1300
impeller::testing::BlendModeSelection
Definition: aiks_unittests.cc:1605
impeller::Entity::TileMode
TileMode
Definition: entity.h:40
aiks_playground.h
impeller::Entity::GetBlendMode
BlendMode GetBlendMode() const
Definition: entity.cc:105
IMPELLER_FOR_EACH_BLEND_MODE
#define IMPELLER_FOR_EACH_BLEND_MODE(V)
Definition: color.h:17
impeller::Matrix::Invert
Matrix Invert() const
Definition: matrix.cc:97
impeller::MakeTextFrameFromTextBlobSkia
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
Definition: text_frame_skia.cc:41
impeller::ColorSource::MakeRadialGradient
static ColorSource MakeRadialGradient(Point center, Scalar radius, std::vector< Color > colors, std::vector< Scalar > stops, Entity::TileMode tile_mode, Matrix effect_transform)
Definition: color_source.cc:110
impeller::Canvas::Transform
void Transform(const Matrix &xformation)
Definition: canvas.cc:125
impeller::Close
void Close(PathBuilder *builder)
Definition: tessellator.cc:36
impeller::Picture::ToImage
std::shared_ptr< Image > ToImage(AiksContext &context, ISize size) const
Definition: picture.cc:31
impeller::AiksPlayground::OpenPlaygroundHere
bool OpenPlaygroundHere(Picture picture)
Definition: aiks_playground.cc:31
command_buffer.h
impeller::CaptureProperty
A capturable property type.
Definition: capture.h:67
impeller::Canvas::ClipRRect
void ClipRRect(const Rect &rect, Scalar corner_radius, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition: canvas.cc:321
impeller::Matrix::MakeRotationZ
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:208
impeller::PathBuilder::Close
PathBuilder & Close()
Definition: path_builder.cc:39
impeller::Canvas::Rotate
void Rotate(Radians radians)
Definition: canvas.cc:158
image.h
impeller::LineTo
void LineTo(PathBuilder *builder, Scalar x, Scalar y)
Definition: tessellator.cc:22
impeller::Playground::GetContentScale
Point GetContentScale() const
Definition: playground.cc:177
impeller::TRect< Scalar >::MakeSize
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:52
impeller::Canvas::GetCurrentTransformation
const Matrix & GetCurrentTransformation() const
Definition: canvas.cc:129
impeller::PathBuilder::RoundingRadii::top_right
Point top_right
Definition: path_builder.h:111
impeller::testing::TextRenderOptions::alpha
Scalar alpha
Definition: aiks_unittests.cc:1299
constants.h
snapshot.h
impeller::TPoint< Scalar >
impeller::PathBuilder::MoveTo
PathBuilder & MoveTo(Point point, bool relative=false)
Definition: path_builder.cc:32
impeller::Color::BlackTransparent
static constexpr Color BlackTransparent()
Definition: color.h:260
impeller::Color::Black
static constexpr Color Black()
Definition: color.h:256
impeller::Paint::invert_colors
bool invert_colors
Definition: paint.h:64
impeller::PathBuilder::RoundingRadii::bottom_left
Point bottom_left
Definition: path_builder.h:110
impeller::testing::BlendModeSelection::blend_mode_values
std::vector< BlendMode > blend_mode_values
Definition: aiks_unittests.cc:1607
impeller::ScalarNearlyEqual
constexpr bool ScalarNearlyEqual(Scalar x, Scalar y, Scalar tolerance=kEhCloseEnough)
Definition: scalar.h:27
impeller::Canvas::Concat
void Concat(const Matrix &xformation)
Definition: canvas.cc:113
impeller::Canvas::DrawRect
void DrawRect(Rect rect, const Paint &paint)
Definition: canvas.cc:235
impeller::Degrees
Definition: scalar.h:43
color.h
impeller::Paint::HasColorFilter
bool HasColorFilter() const
Whether this paint has a color filter that can apply opacity.
Definition: paint.cc:211
impeller::PathBuilder::AddCircle
PathBuilder & AddCircle(const Point &center, Scalar radius)
Definition: path_builder.cc:198
impeller::Canvas::DrawPoints
void DrawPoints(std::vector< Point >, Scalar radius, const Paint &paint, PointStyle point_style)
Definition: canvas.cc:424
typographer_context_stb.h
impeller::Color::AliceBlue
static constexpr Color AliceBlue()
Definition: color.h:272
impeller::TPoint::GetDistance
constexpr Type GetDistance(const TPoint &p) const
Definition: point.h:193
impeller::Canvas::ClipRect
void ClipRect(const Rect &rect, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition: canvas.cc:300
impeller::TRect< Scalar >::MakeLTRB
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:40
impeller::testing::BlendModeSelection::blend_mode_names
std::vector< const char * > blend_mode_names
Definition: aiks_unittests.cc:1606
IMPELLER_PLAYGROUND_LINE
#define IMPELLER_PLAYGROUND_LINE(default_position_a, default_position_b, radius, color_a, color_b)
Definition: widgets.h:55
impeller
Definition: aiks_context.cc:10
impeller::Paint::dither
bool dither
Definition: paint.h:56
impeller::Paint::mask_blur_descriptor
std::optional< MaskBlurDescriptor > mask_blur_descriptor
Definition: paint.h:68
impeller::Paint::stroke_width
Scalar stroke_width
Definition: paint.h:58
impeller::TRect< Scalar >
impeller::BlendMode::kSource
@ kSource
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:36
impeller::Color::Aqua
static constexpr Color Aqua()
Definition: color.h:280
impeller::Vector3
Definition: vector.h:17
impeller::Paint::blend_mode
BlendMode blend_mode
Definition: paint.h:63
impeller::Paint::image_filter
std::shared_ptr< ImageFilter > image_filter
Definition: paint.h:66
impeller::TPoint< Scalar >::MakeXY
static constexpr TPoint< Type > MakeXY(Type x, Type y)
Definition: point.h:39
linear_gradient_contents.h
impeller::Paint::stroke_join
Join stroke_join
Definition: paint.h:60
impeller::Canvas::Translate
void Translate(const Vector3 &offset)
Definition: canvas.cc:142
impeller::Canvas::ClipPath
void ClipPath(const Path &path, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition: canvas.cc:290
impeller::Color::Blend
Color Blend(Color source, BlendMode blend_mode) const
Blends an unpremultiplied destination color into a given unpremultiplied source color to form a new u...
Definition: color.cc:222