| 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 picture.pass->IterateAllElements([&](EntityPass::Element& element) -> bool {
2420 if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
2421 actual_color = subpass->get()->GetClearColor();
2422 }
2423 // Fail if the first element isn't a subpass.
2424 return true;
2425 });
2426
2427 ASSERT_TRUE(actual_color.has_value());
2428 if (!actual_color) {
2429 return;
2430 }
2431 ASSERT_EQ(actual_color.value(), Color::BlackTransparent());
2432 }
2433
2434 TEST_P(AiksTest, CollapsedDrawPaintInSubpass) {
2435 Canvas canvas;
2436 canvas.DrawPaint(
2437 {.color = Color::Yellow(), .blend_mode = BlendMode::kSource});
2438 canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
2439 canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
2440 .blend_mode = BlendMode::kSourceOver});
2441
2442 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2443 }
2444
2445 TEST_P(AiksTest, CollapsedDrawPaintInSubpassBackdropFilter) {
2446 // Bug: https://github.com/flutter/flutter/issues/131576
2447 Canvas canvas;
2448 canvas.DrawPaint(
2449 {.color = Color::Yellow(), .blend_mode = BlendMode::kSource});
2450 canvas.SaveLayer({}, {},
2451 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
2452 FilterContents::BlurStyle::kNormal,
2453 Entity::TileMode::kDecal));
2454 canvas.DrawPaint(
2455 {.color = Color::CornflowerBlue(), .blend_mode = BlendMode::kSourceOver});
2456
2457 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2458 }
2459
2460 TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) {
2461 Canvas canvas;
2462
2463 canvas.SaveLayer({
2464 .color_filter =
2465 ColorFilter::MakeBlend(BlendMode::kColorDodge, Color::Red()),
2466 });
2467
2468 canvas.Translate({500, 300, 0});
2469 canvas.Rotate(Radians(2 * kPi / 3));
2470 canvas.DrawRect({100, 100, 200, 200}, {.color = Color::Blue()});
2471
2472 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2473 }
2474
2475 TEST_P(AiksTest, ColorMatrixFilterSubpassCollapseOptimization) {
2476 Canvas canvas;
2477
2478 canvas.SaveLayer({
2479 .color_filter =
2480 ColorFilter::MakeMatrix({.array =
2481 {
2482 -1.0, 0, 0, 1.0, 0, //
2483 0, -1.0, 0, 1.0, 0, //
2484 0, 0, -1.0, 1.0, 0, //
2485 1.0, 1.0, 1.0, 1.0, 0 //
2486 }}),
2487 });
2488
2489 canvas.Translate({500, 300, 0});
2490 canvas.Rotate(Radians(2 * kPi / 3));
2491 canvas.DrawRect({100, 100, 200, 200}, {.color = Color::Blue()});
2492
2493 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2494 }
2495
2496 TEST_P(AiksTest, LinearToSrgbFilterSubpassCollapseOptimization) {
2497 Canvas canvas;
2498
2499 canvas.SaveLayer({
2500 .color_filter = ColorFilter::MakeLinearToSrgb(),
2501 });
2502
2503 canvas.Translate({500, 300, 0});
2504 canvas.Rotate(Radians(2 * kPi / 3));
2505 canvas.DrawRect({100, 100, 200, 200}, {.color = Color::Blue()});
2506
2507 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2508 }
2509
2510 TEST_P(AiksTest, SrgbToLinearFilterSubpassCollapseOptimization) {
2511 Canvas canvas;
2512
2513 canvas.SaveLayer({
2514 .color_filter = ColorFilter::MakeSrgbToLinear(),
2515 });
2516
2517 canvas.Translate({500, 300, 0});
2518 canvas.Rotate(Radians(2 * kPi / 3));
2519 canvas.DrawRect({100, 100, 200, 200}, {.color = Color::Blue()});
2520
2521 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2522 }
2523
2524 static Picture BlendModeTest(BlendMode blend_mode,
2525 const std::shared_ptr<Image>& src_image,
2526 const std::shared_ptr<Image>& dst_image) {
2527 Color destination_color = Color::CornflowerBlue().WithAlpha(0.75);
2528 auto source_colors = std::vector<Color>({Color::White().WithAlpha(0.75),
2529 Color::LimeGreen().WithAlpha(0.75),
2530 Color::Black().WithAlpha(0.75)});
2531
2532 Canvas canvas;
2533
2534 canvas.DrawPaint({.color = Color::Black()});
2535
2536 //----------------------------------------------------------------------------
2537 /// 1. Save layer blending (top squares).
2538 ///
2539
2540 canvas.Save();
2541 for (const auto& color : source_colors) {
2542 canvas.Save();
2543 {
2544 canvas.ClipRect(Rect::MakeXYWH(50, 50, 100, 100));
2545 // Perform the blend in a SaveLayer so that the initial backdrop color is
2546 // fully transparent black. SourceOver blend the result onto the parent
2547 // pass.
2548 canvas.SaveLayer({});
2549 {
2550 canvas.DrawPaint({.color = destination_color});
2551 // Draw the source color in an offscreen pass and blend it to the parent
2552 // pass.
2553 canvas.SaveLayer({.blend_mode = blend_mode});
2554 { //
2555 canvas.DrawRect({50, 50, 100, 100}, {.color = color});
2556 }
2557 canvas.Restore();
2558 }
2559 canvas.Restore();
2560 }
2561 canvas.Restore();
2562 canvas.Translate(Vector2(100, 0));
2563 }
2564 canvas.RestoreToCount(0);
2565
2566 //----------------------------------------------------------------------------
2567 /// 2. CPU blend modes (bottom squares).
2568 ///
2569
2570 canvas.Save();
2571 canvas.Translate({0, 100});
2572
2573 // Perform the blend in a SaveLayer so that the initial backdrop color is
2574 // fully transparent black. SourceOver blend the result onto the parent pass.
2575 canvas.SaveLayer({});
2576 // canvas.DrawPaint({.color = destination_color});
2577 for (const auto& color : source_colors) {
2578 // Simply write the CPU blended color to the pass.
2579 canvas.DrawRect({50, 50, 100, 100},
2580 {.color = destination_color.Blend(color, blend_mode),
2581 .blend_mode = BlendMode::kSourceOver});
2582 canvas.Translate(Vector2(100, 0));
2583 }
2584 canvas.RestoreToCount(0);
2585
2586 //----------------------------------------------------------------------------
2587 /// 3. Image blending (top right).
2588 ///
2589 /// Compare these results with the images in the Flutter blend mode
2590 /// documentation: https://api.flutter.dev/flutter/dart-ui/BlendMode.html
2591 ///
2592
2593 canvas.Save();
2594 // canvas.ClipRect(Rect::MakeXYWH(500, 0, 500, 500));
2595 canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver});
2596 {
2597 canvas.DrawImage(dst_image, {400, 50}, {.blend_mode = BlendMode::kSource});
2598 canvas.DrawImage(src_image, {400, 50}, {.blend_mode = blend_mode});
2599 }
2600 canvas.RestoreToCount(0);
2601
2602 return canvas.EndRecordingAsPicture();
2603 }
2604
2605 #define BLEND_MODE_TEST(blend_mode) \
2606 TEST_P(AiksTest, BlendMode##blend_mode) { \
2607 auto src_image = std::make_shared<Image>( \
2608 CreateTextureForFixture("blend_mode_src.png")); \
2609 auto dst_image = std::make_shared<Image>( \
2610 CreateTextureForFixture("blend_mode_dst.png")); \
2611 OpenPlaygroundHere( \
2612 BlendModeTest(BlendMode::k##blend_mode, src_image, dst_image)); \
2613 }
2614 IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TEST)
2615
2616 TEST_P(AiksTest, TranslucentSaveLayerDrawsCorrectly) {
2617 Canvas canvas;
2618
2619 canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
2620
2621 canvas.SaveLayer({.color = Color::Black().WithAlpha(0.5)});
2622 canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
2623 canvas.Restore();
2624
2625 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2626 }
2627
2628 TEST_P(AiksTest, TranslucentSaveLayerWithBlendColorFilterDrawsCorrectly) {
2629 Canvas canvas;
2630
2631 canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
2632
2633 canvas.SaveLayer({
2634 .color = Color::Black().WithAlpha(0.5),
2635 .color_filter =
2636 ColorFilter::MakeBlend(BlendMode::kDestinationOver, Color::Red()),
2637 });
2638 canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
2639 canvas.Restore();
2640
2641 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2642 }
2643
2644 TEST_P(AiksTest, TranslucentSaveLayerWithBlendImageFilterDrawsCorrectly) {
2645 Canvas canvas;
2646
2647 canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
2648
2649 canvas.SaveLayer({
2650 .color = Color::Black().WithAlpha(0.5),
2651 .image_filter = ImageFilter::MakeFromColorFilter(
2652 *ColorFilter::MakeBlend(BlendMode::kDestinationOver, Color::Red())),
2653 });
2654
2655 canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
2656 canvas.Restore();
2657
2658 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2659 }
2660
2661 TEST_P(AiksTest, TranslucentSaveLayerWithColorAndImageFilterDrawsCorrectly) {
2662 Canvas canvas;
2663
2664 canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), {.color = Color::Blue()});
2665
2666 canvas.SaveLayer({
2667 .color = Color::Black().WithAlpha(0.5),
2668 .color_filter =
2669 ColorFilter::MakeBlend(BlendMode::kDestinationOver, Color::Red()),
2670 });
2671
2672 canvas.DrawRect(Rect::MakeXYWH(100, 500, 300, 300), {.color = Color::Blue()});
2673 canvas.Restore();
2674
2675 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2676 }
2677
2678 TEST_P(AiksTest, TranslucentSaveLayerImageDrawsCorrectly) {
2679 Canvas canvas;
2680
2681 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2682 canvas.DrawImage(image, {100, 100}, {});
2683
2684 canvas.SaveLayer({.color = Color::Black().WithAlpha(0.5)});
2685 canvas.DrawImage(image, {100, 500}, {});
2686 canvas.Restore();
2687
2688 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2689 }
2690
2691 TEST_P(AiksTest, TranslucentSaveLayerWithColorMatrixColorFilterDrawsCorrectly) {
2692 Canvas canvas;
2693
2694 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2695 canvas.DrawImage(image, {100, 100}, {});
2696
2697 canvas.SaveLayer({
2698 .color = Color::Black().WithAlpha(0.5),
2699 .color_filter = ColorFilter::MakeMatrix({.array =
2700 {
2701 1, 0, 0, 0, 0, //
2702 0, 1, 0, 0, 0, //
2703 0, 0, 1, 0, 0, //
2704 0, 0, 0, 2, 0 //
2705 }}),
2706 });
2707 canvas.DrawImage(image, {100, 500}, {});
2708 canvas.Restore();
2709
2710 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2711 }
2712
2713 TEST_P(AiksTest, TranslucentSaveLayerWithColorMatrixImageFilterDrawsCorrectly) {
2714 Canvas canvas;
2715
2716 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2717 canvas.DrawImage(image, {100, 100}, {});
2718
2719 canvas.SaveLayer({
2720 .color = Color::Black().WithAlpha(0.5),
2721 .image_filter = ImageFilter::MakeFromColorFilter(
2722 *ColorFilter::MakeMatrix({.array =
2723 {
2724 1, 0, 0, 0, 0, //
2725 0, 1, 0, 0, 0, //
2726 0, 0, 1, 0, 0, //
2727 0, 0, 0, 2, 0 //
2728 }})),
2729 });
2730 canvas.DrawImage(image, {100, 500}, {});
2731 canvas.Restore();
2732
2733 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2734 }
2735
2736 TEST_P(AiksTest,
2737 TranslucentSaveLayerWithColorFilterAndImageFilterDrawsCorrectly) {
2738 Canvas canvas;
2739
2740 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
2741 canvas.DrawImage(image, {100, 100}, {});
2742
2743 canvas.SaveLayer({
2744 .color = Color::Black().WithAlpha(0.5),
2745 .image_filter = ImageFilter::MakeFromColorFilter(
2746 *ColorFilter::MakeMatrix({.array =
2747 {
2748 1, 0, 0, 0, 0, //
2749 0, 1, 0, 0, 0, //
2750 0, 0.2, 1, 0, 0, //
2751 0, 0, 0, 0.5, 0 //
2752 }})),
2753 .color_filter =
2754 ColorFilter::MakeBlend(BlendMode::kModulate, Color::Green()),
2755 });
2756 canvas.DrawImage(image, {100, 500}, {});
2757 canvas.Restore();
2758
2759 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2760 }
2761
2762 TEST_P(AiksTest, TranslucentSaveLayerWithAdvancedBlendModeDrawsCorrectly) {
2763 Canvas canvas;
2764 canvas.DrawRect({0, 0, 400, 400}, {.color = Color::Red()});
2765 canvas.SaveLayer({
2766 .color = Color::Black().WithAlpha(0.5),
2767 .blend_mode = BlendMode::kLighten,
2768 });
2769 canvas.DrawCircle({200, 200}, 100, {.color = Color::Green()});
2770 canvas.Restore();
2771 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2772 }
2773
2774 /// This is a regression check for https://github.com/flutter/engine/pull/41129
2775 /// The entire screen is green if successful. If failing, no frames will render,
2776 /// or the entire screen will be transparent black.
2777 TEST_P(AiksTest, CanRenderTinyOverlappingSubpasses) {
2778 Canvas canvas;
2779 canvas.DrawPaint({.color = Color::Red()});
2780
2781 // Draw two overlapping subpixel circles.
2782 canvas.SaveLayer({});
2783 canvas.DrawCircle({100, 100}, 0.1, {.color = Color::Yellow()});
2784 canvas.Restore();
2785 canvas.SaveLayer({});
2786 canvas.DrawCircle({100, 100}, 0.1, {.color = Color::Yellow()});
2787 canvas.Restore();
2788
2789 canvas.DrawPaint({.color = Color::Green()});
2790
2791 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2792 }
2793
2794 /// Tests that the debug checkerboard displays for offscreen textures when
2795 /// enabled. Most of the complexity here is just to future proof by making pass
2796 /// collapsing hard.
2797 TEST_P(AiksTest, CanRenderOffscreenCheckerboard) {
2798 Canvas canvas;
2799 canvas.debug_options.offscreen_texture_checkerboard = true;
2800
2801 canvas.DrawPaint({.color = Color::AntiqueWhite()});
2802 canvas.DrawCircle({400, 300}, 200,
2803 {.color = Color::CornflowerBlue().WithAlpha(0.75)});
2804
2805 canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
2806 {
2807 canvas.DrawCircle({500, 400}, 200,
2808 {.color = Color::DarkBlue().WithAlpha(0.75)});
2809 canvas.DrawCircle({550, 450}, 200,
2810 {.color = Color::LightCoral().WithAlpha(0.75),
2811 .blend_mode = BlendMode::kLuminosity});
2812 }
2813 canvas.Restore();
2814
2815 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2816 }
2817
2818 TEST_P(AiksTest, OpaqueEntitiesGetCoercedToSource) {
2819 Canvas canvas;
2820 canvas.Scale(Vector2(1.618, 1.618));
2821 canvas.DrawCircle(Point(), 10,
2822 {
2823 .color = Color::CornflowerBlue(),
2824 .blend_mode = BlendMode::kSourceOver,
2825 });
2826 Picture picture = canvas.EndRecordingAsPicture();
2827
2828 // Extract the SolidColorSource.
2829 Entity entity;
2830 std::shared_ptr<SolidColorContents> contents;
2831 picture.pass->IterateAllEntities([&e = entity, &contents](Entity& entity) {
2832 if (ScalarNearlyEqual(entity.GetTransformation().GetScale().x, 1.618f)) {
2833 e = entity;
2834 contents =
2835 std::static_pointer_cast<SolidColorContents>(entity.GetContents());
2836 return false;
2837 }
2838 return true;
2839 });
2840
2841 ASSERT_TRUE(contents->IsOpaque());
2842 ASSERT_EQ(entity.GetBlendMode(), BlendMode::kSource);
2843 }
2844
2845 TEST_P(AiksTest, CanRenderDestructiveSaveLayer) {
2846 Canvas canvas;
2847
2848 canvas.DrawPaint({.color = Color::Red()});
2849 // Draw an empty savelayer with a destructive blend mode, which will replace
2850 // the entire red screen with fully transparent black, except for the green
2851 // circle drawn within the layer.
2852 canvas.SaveLayer({.blend_mode = BlendMode::kSource});
2853 canvas.DrawCircle({300, 300}, 100, {.color = Color::Green()});
2854 canvas.Restore();
2855
2856 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2857 }
2858
2859 TEST_P(AiksTest, CanRenderMaskBlurHugeSigma) {
2860 Canvas canvas;
2861 canvas.DrawCircle({400, 400}, 300,
2862 {.color = Color::Green(),
2863 .mask_blur_descriptor = Paint::MaskBlurDescriptor{
2864 .style = FilterContents::BlurStyle::kNormal,
2865 .sigma = Sigma(99999),
2866 }});
2867 canvas.Restore();
2868
2869 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2870 }
2871
2872 TEST_P(AiksTest, CanRenderBackdropBlurInteractive) {
2873 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
2874 auto [a, b] = IMPELLER_PLAYGROUND_LINE(Point(50, 50), Point(300, 200), 30,
2875 Color::White(), Color::White());
2876
2877 Canvas canvas;
2878 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
2879 canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()});
2880 canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()});
2881 canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()});
2882 canvas.ClipRRect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), 20);
2883 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
2884 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
2885 FilterContents::BlurStyle::kNormal,
2886 Entity::TileMode::kClamp));
2887 canvas.Restore();
2888
2889 return canvas.EndRecordingAsPicture();
2890 };
2891
2892 ASSERT_TRUE(OpenPlaygroundHere(callback));
2893 }
2894
2895 TEST_P(AiksTest, CanRenderBackdropBlur) {
2896 Canvas canvas;
2897 canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
2898 canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()});
2899 canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()});
2900 canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()});
2901 canvas.ClipRRect(Rect::MakeLTRB(75, 50, 375, 275), 20);
2902 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
2903 ImageFilter::MakeBlur(Sigma(30.0), Sigma(30.0),
2904 FilterContents::BlurStyle::kNormal,
2905 Entity::TileMode::kClamp));
2906 canvas.Restore();
2907
2908 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2909 }
2910
2911 TEST_P(AiksTest, CanRenderBackdropBlurHugeSigma) {
2912 Canvas canvas;
2913 canvas.DrawCircle({400, 400}, 300, {.color = Color::Green()});
2914 canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
2915 ImageFilter::MakeBlur(Sigma(999999), Sigma(999999),
2916 FilterContents::BlurStyle::kNormal,
2917 Entity::TileMode::kClamp));
2918 canvas.Restore();
2919
2920 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2921 }
2922
2923 TEST_P(AiksTest, CanRenderClippedBlur) {
2924 Canvas canvas;
2925 canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
2926 canvas.DrawCircle(
2927 {400, 400}, 200,
2928 {
2929 .color = Color::Green(),
2930 .image_filter = ImageFilter::MakeBlur(
2931 Sigma(20.0), Sigma(20.0), FilterContents::BlurStyle::kNormal,
2932 Entity::TileMode::kClamp),
2933 });
2934 canvas.Restore();
2935
2936 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2937 }
2938
2939 TEST_P(AiksTest, CanRenderForegroundBlendWithMaskBlur) {
2940 // This case triggers the ForegroundPorterDuffBlend path. The color filter
2941 // should apply to the color only, and respect the alpha mask.
2942 Canvas canvas;
2943 canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
2944 canvas.DrawCircle({400, 400}, 200,
2945 {
2946 .color = Color::White(),
2947 .color_filter = ColorFilter::MakeBlend(
2948 BlendMode::kSource, Color::Green()),
2949 .mask_blur_descriptor =
2950 Paint::MaskBlurDescriptor{
2951 .style = FilterContents::BlurStyle::kNormal,
2952 .sigma = Radius(20),
2953 },
2954 });
2955 canvas.Restore();
2956
2957 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2958 }
2959
2960 TEST_P(AiksTest, CanRenderForegroundAdvancedBlendWithMaskBlur) {
2961 // This case triggers the ForegroundAdvancedBlend path. The color filter
2962 // should apply to the color only, and respect the alpha mask.
2963 Canvas canvas;
2964 canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
2965 canvas.DrawCircle({400, 400}, 200,
2966 {
2967 .color = Color::Grey(),
2968 .color_filter = ColorFilter::MakeBlend(
2969 BlendMode::kColor, Color::Green()),
2970 .mask_blur_descriptor =
2971 Paint::MaskBlurDescriptor{
2972 .style = FilterContents::BlurStyle::kNormal,
2973 .sigma = Radius(20),
2974 },
2975 });
2976 canvas.Restore();
2977
2978 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2979 }
2980
2981 // Regression test for https://github.com/flutter/flutter/issues/126701 .
2982 TEST_P(AiksTest, CanRenderClippedRuntimeEffects) {
2983 if (GetParam() != PlaygroundBackend::kMetal) {
2984 GTEST_SKIP_("This backend doesn't support runtime effects.");
2985 }
2986
2987 auto runtime_stage =
2988 OpenAssetAsRuntimeStage("runtime_stage_example.frag.iplr");
2989 ASSERT_TRUE(runtime_stage->IsDirty());
2990
2991 struct FragUniforms {
2992 Vector2 iResolution;
2993 Scalar iTime;
2994 } frag_uniforms = {.iResolution = Vector2(400, 400), .iTime = 100.0};
2995 auto uniform_data = std::make_shared<std::vector<uint8_t>>();
2996 uniform_data->resize(sizeof(FragUniforms));
2997 memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
2998
2999 std::vector<RuntimeEffectContents::TextureInput> texture_inputs;
3000
3001 Paint paint;
3002 paint.color_source = ColorSource::MakeRuntimeEffect(
3003 runtime_stage, uniform_data, texture_inputs);
3004
3005 Canvas canvas;
3006 canvas.Save();
3007 canvas.ClipRRect(Rect{0, 0, 400, 400}, 10.0,
3008 Entity::ClipOperation::kIntersect);
3009 canvas.DrawRect(Rect{0, 0, 400, 400}, paint);
3010 canvas.Restore();
3011
3012 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3013 }
3014
3015 TEST_P(AiksTest, DrawPaintTransformsBounds) {
3016 if (GetParam() != PlaygroundBackend::kMetal) {
3017 GTEST_SKIP_("This backend doesn't support runtime effects.");
3018 }
3019
3020 auto runtime_stage = OpenAssetAsRuntimeStage("gradient.frag.iplr");
3021 ASSERT_TRUE(runtime_stage->IsDirty());
3022
3023 struct FragUniforms {
3024 Size size;
3025 } frag_uniforms = {.size = Size::MakeWH(400, 400)};
3026 auto uniform_data = std::make_shared<std::vector<uint8_t>>();
3027 uniform_data->resize(sizeof(FragUniforms));
3028 memcpy(uniform_data->data(), &frag_uniforms, sizeof(FragUniforms));
3029
3030 std::vector<RuntimeEffectContents::TextureInput> texture_inputs;
3031
3032 Paint paint;
3033 paint.color_source = ColorSource::MakeRuntimeEffect(
3034 runtime_stage, uniform_data, texture_inputs);
3035
3036 Canvas canvas;
3037 canvas.Save();
3038 canvas.Scale(GetContentScale());
3039 canvas.DrawPaint(paint);
3040 canvas.Restore();
3041
3042 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3043 }
3044
3045 TEST_P(AiksTest, CanDrawPoints) {
3046 std::vector<Point> points = {
3047 {0, 0}, //
3048 {100, 100}, //
3049 {100, 0}, //
3050 {0, 100}, //
3051 {0, 0}, //
3052 {48, 48}, //
3053 {52, 52}, //
3054 };
3055 std::vector<PointStyle> caps = {
3056 PointStyle::kRound,
3057 PointStyle::kSquare,
3058 };
3059 Paint paint;
3060 paint.color = Color::Yellow().WithAlpha(0.5);
3061
3062 Paint background;
3063 background.color = Color::Black();
3064
3065 Canvas canvas;
3066 canvas.DrawPaint(background);
3067 canvas.Translate({200, 200});
3068 canvas.DrawPoints(points, 10, paint, PointStyle::kRound);
3069 canvas.Translate({150, 0});
3070 canvas.DrawPoints(points, 10, paint, PointStyle::kSquare);
3071
3072 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3073 }
3074
3075 // Regression test for https://github.com/flutter/flutter/issues/127374.
3076 TEST_P(AiksTest, DrawAtlasWithColorAdvancedAndTransform) {
3077 // Draws the image as four squares stiched together.
3078 auto atlas = CreateTextureForFixture("bay_bridge.jpg");
3079 auto size = atlas->GetSize();
3080 auto image = std::make_shared<Image>(atlas);
3081 // Divide image into four quadrants.
3082 Scalar half_width = size.width / 2;
3083 Scalar half_height = size.height / 2;
3084 std::vector<Rect> texture_coordinates = {
3085 Rect::MakeLTRB(0, 0, half_width, half_height),
3086 Rect::MakeLTRB(half_width, 0, size.width, half_height),
3087 Rect::MakeLTRB(0, half_height, half_width, size.height),
3088 Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
3089 // Position quadrants adjacent to eachother.
3090 std::vector<Matrix> transforms = {
3091 Matrix::MakeTranslation({0, 0, 0}),
3092 Matrix::MakeTranslation({half_width, 0, 0}),
3093 Matrix::MakeTranslation({0, half_height, 0}),
3094 Matrix::MakeTranslation({half_width, half_height, 0})};
3095 std::vector<Color> colors = {Color::Red(), Color::Green(), Color::Blue(),
3096 Color::Yellow()};
3097
3098 Paint paint;
3099
3100 Canvas canvas;
3101 canvas.Scale({0.25, 0.25, 1.0});
3102 canvas.DrawAtlas(image, transforms, texture_coordinates, colors,
3103 BlendMode::kModulate, {}, std::nullopt, paint);
3104
3105 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3106 }
3107
3108 // Regression test for https://github.com/flutter/flutter/issues/127374.
3109 TEST_P(AiksTest, DrawAtlasAdvancedAndTransform) {
3110 // Draws the image as four squares stiched together.
3111 auto atlas = CreateTextureForFixture("bay_bridge.jpg");
3112 auto size = atlas->GetSize();
3113 auto image = std::make_shared<Image>(atlas);
3114 // Divide image into four quadrants.
3115 Scalar half_width = size.width / 2;
3116 Scalar half_height = size.height / 2;
3117 std::vector<Rect> texture_coordinates = {
3118 Rect::MakeLTRB(0, 0, half_width, half_height),
3119 Rect::MakeLTRB(half_width, 0, size.width, half_height),
3120 Rect::MakeLTRB(0, half_height, half_width, size.height),
3121 Rect::MakeLTRB(half_width, half_height, size.width, size.height)};
3122 // Position quadrants adjacent to eachother.
3123 std::vector<Matrix> transforms = {
3124 Matrix::MakeTranslation({0, 0, 0}),
3125 Matrix::MakeTranslation({half_width, 0, 0}),
3126 Matrix::MakeTranslation({0, half_height, 0}),
3127 Matrix::MakeTranslation({half_width, half_height, 0})};
3128
3129 Paint paint;
3130
3131 Canvas canvas;
3132 canvas.Scale({0.25, 0.25, 1.0});
3133 canvas.DrawAtlas(image, transforms, texture_coordinates, {},
3134 BlendMode::kModulate, {}, std::nullopt, paint);
3135
3136 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3137 }
3138
3139 TEST_P(AiksTest, CanDrawPointsWithTextureMap) {
3140 auto texture = CreateTextureForFixture("table_mountain_nx.png",
3141 /*enable_mipmapping=*/true);
3142
3143 std::vector<Point> points = {
3144 {0, 0}, //
3145 {100, 100}, //
3146 {100, 0}, //
3147 {0, 100}, //
3148 {0, 0}, //
3149 {48, 48}, //
3150 {52, 52}, //
3151 };
3152 std::vector<PointStyle> caps = {
3153 PointStyle::kRound,
3154 PointStyle::kSquare,
3155 };
3156 Paint paint;
3157 paint.color_source = ColorSource::MakeImage(texture, Entity::TileMode::kClamp,
3158 Entity::TileMode::kClamp, {}, {});
3159
3160 Canvas canvas;
3161 canvas.Translate({200, 200});
3162 canvas.DrawPoints(points, 100, paint, PointStyle::kRound);
3163 canvas.Translate({150, 0});
3164 canvas.DrawPoints(points, 100, paint, PointStyle::kSquare);
3165
3166 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3167 }
3168
3169 // This currently renders solid blue, as the support for text color sources was
3170 // moved into DLDispatching. Path data requires the SkTextBlobs which are not
3171 // used in impeller::TextFrames.
3172 TEST_P(AiksTest, TextForegroundShaderWithTransform) {
3173 auto mapping = OpenFixtureAsSkData("Roboto-Regular.ttf");
3174 ASSERT_NE(mapping, nullptr);
3175
3176 Scalar font_size = 100;
3177 SkFont sk_font(SkTypeface::MakeFromData(mapping), font_size);
3178
3179 Paint text_paint;
3180 text_paint.color = Color::Blue();
3181
3182 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
3183 Color{0.1294, 0.5882, 0.9529, 1.0}};
3184 std::vector<Scalar> stops = {
3185 0.0,
3186 1.0,
3187 };
3188 text_paint.color_source = ColorSource::MakeLinearGradient(
3189 {0, 0}, {100, 100}, std::move(colors), std::move(stops),
3190 Entity::TileMode::kRepeat, {});
3191
3192 Canvas canvas;
3193 canvas.Translate({100, 100});
3194 canvas.Rotate(Radians(kPi / 4));
3195
3196 auto blob = SkTextBlob::MakeFromString("Hello", sk_font);
3197 ASSERT_NE(blob, nullptr);
3198 auto frame = MakeTextFrameFromTextBlobSkia(blob);
3199 canvas.DrawTextFrame(frame, Point(), text_paint);
3200
3201 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3202 }
3203
3204 TEST_P(AiksTest, CanCanvasDrawPicture) {
3205 Canvas subcanvas;
3206 subcanvas.DrawRect(Rect::MakeLTRB(-100, -50, 100, 50),
3207 {.color = Color::CornflowerBlue()});
3208 auto picture = subcanvas.EndRecordingAsPicture();
3209
3210 Canvas canvas;
3211 canvas.Translate({200, 200});
3212 canvas.Rotate(Radians(kPi / 4));
3213 canvas.DrawPicture(picture);
3214
3215 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3216 }
3217
3218 TEST_P(AiksTest, CanCanvasDrawPictureWithAdvancedBlend) {
3219 Canvas subcanvas;
3220 subcanvas.DrawRect(
3221 Rect::MakeLTRB(-100, -50, 100, 50),
3222 {.color = Color::CornflowerBlue(), .blend_mode = BlendMode::kColorDodge});
3223 auto picture = subcanvas.EndRecordingAsPicture();
3224
3225 Canvas canvas;
3226 canvas.DrawPaint({.color = Color::Black()});
3227 canvas.DrawCircle(Point::MakeXY(150, 150), 25, {.color = Color::Red()});
3228 canvas.DrawPicture(picture);
3229
3230 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3231 }
3232
3233 TEST_P(AiksTest, CanCanvasDrawPictureWithBackdropFilter) {
3234 Canvas subcanvas;
3235 subcanvas.SaveLayer({}, {},
3236 ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
3237 FilterContents::BlurStyle::kNormal,
3238 Entity::TileMode::kDecal));
3239 auto image = std::make_shared<Image>(CreateTextureForFixture("kalimba.jpg"));
3240 Paint paint;
3241 paint.color = Color::Red().WithAlpha(0.5);
3242 subcanvas.DrawImage(image, Point::MakeXY(100.0, 100.0), paint);
3243
3244 auto picture = subcanvas.EndRecordingAsPicture();
3245
3246 Canvas canvas;
3247 canvas.DrawPaint({.color = Color::Black()});
3248 canvas.DrawCircle(Point::MakeXY(150, 150), 25, {.color = Color::Red()});
3249 canvas.DrawPicture(picture);
3250
3251 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3252 }
3253
3254 TEST_P(AiksTest, DrawPictureWithText) {
3255 Canvas subcanvas;
3256 ASSERT_TRUE(RenderTextInCanvasSkia(
3257 GetContext(), subcanvas,
3258 "the quick brown fox jumped over the lazy dog!.?", "Roboto-Regular.ttf"));
3259 subcanvas.Translate({0, 10});
3260 subcanvas.Scale(Vector2(3, 3));
3261 ASSERT_TRUE(RenderTextInCanvasSkia(
3262 GetContext(), subcanvas,
3263 "the quick brown fox jumped over the very big lazy dog!.?",
3264 "Roboto-Regular.ttf"));
3265 auto picture = subcanvas.EndRecordingAsPicture();
3266
3267 Canvas canvas;
3268 canvas.Scale(Vector2(.2, .2));
3269 canvas.Save();
3270 canvas.Translate({200, 200});
3271 canvas.Scale(Vector2(3.5, 3.5)); // The text must not be blurry after this.
3272 canvas.DrawPicture(picture);
3273 canvas.Restore();
3274
3275 canvas.Scale(Vector2(1.5, 1.5));
3276 ASSERT_TRUE(RenderTextInCanvasSkia(
3277 GetContext(), canvas,
3278 "the quick brown fox jumped over the smaller lazy dog!.?",
3279 "Roboto-Regular.ttf"));
3280 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3281 }
3282
3283 TEST_P(AiksTest, DrawPictureClipped) {
3284 Canvas subcanvas;
3285 subcanvas.ClipRRect(Rect::MakeLTRB(100, 100, 400, 400), 15);
3286 subcanvas.DrawPaint({.color = Color::Red()});
3287 auto picture = subcanvas.EndRecordingAsPicture();
3288
3289 Canvas canvas;
3290 canvas.DrawPaint({.color = Color::CornflowerBlue()});
3291
3292 // Draw a red RRect via DrawPicture.
3293 canvas.DrawPicture(picture);
3294
3295 // Draw over the picture with a larger green rectangle, completely covering it
3296 // up.
3297 canvas.ClipRRect(Rect::MakeLTRB(100, 100, 400, 400).Expand(20), 15);
3298 canvas.DrawPaint({.color = Color::Green()});
3299
3300 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3301 }
3302
3303 TEST_P(AiksTest, MatrixSaveLayerFilter) {
3304 Canvas canvas;
3305 canvas.DrawPaint({.color = Color::Black()});
3306 canvas.SaveLayer({}, std::nullopt);
3307 {
3308 canvas.DrawCircle(Point(200, 200), 100,
3309 {.color = Color::Green().WithAlpha(0.5),
3310 .blend_mode = BlendMode::kPlus});
3311 // Should render a second circle, centered on the bottom-right-most edge of
3312 // the circle.
3313 canvas.SaveLayer({.image_filter = ImageFilter::MakeMatrix(
3314 Matrix::MakeTranslation(Vector2(1, 1) *
3315 (200 + 100 * k1OverSqrt2)) *
3316 Matrix::MakeScale(Vector2(1, 1) * 0.5) *
3317 Matrix::MakeTranslation(Vector2(-200, -200)),
3318 SamplerDescriptor{})},
3319 std::nullopt);
3320 canvas.DrawCircle(Point(200, 200), 100,
3321 {.color = Color::Green().WithAlpha(0.5),
3322 .blend_mode = BlendMode::kPlus});
3323 canvas.Restore();
3324 }
3325 canvas.Restore();
3326
3327 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3328 }
3329
3330 TEST_P(AiksTest, MatrixBackdropFilter) {
3331 Canvas canvas;
3332 canvas.DrawPaint({.color = Color::Black()});
3333 canvas.SaveLayer({}, std::nullopt);
3334 {
3335 canvas.DrawCircle(Point(200, 200), 100,
3336 {.color = Color::Green().WithAlpha(0.5),
3337 .blend_mode = BlendMode::kPlus});
3338 // Should render a second circle, centered on the bottom-right-most edge of
3339 // the circle.
3340 canvas.SaveLayer(
3341 {}, std::nullopt,
3342 ImageFilter::MakeMatrix(
3343 Matrix::MakeTranslation(Vector2(1, 1) * (100 + 100 * k1OverSqrt2)) *
3344 Matrix::MakeScale(Vector2(1, 1) * 0.5) *
3345 Matrix::MakeTranslation(Vector2(-100, -100)),
3346 SamplerDescriptor{}));
3347 canvas.Restore();
3348 }
3349 canvas.Restore();
3350
3351 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3352 }
3353
3354 TEST_P(AiksTest, SolidColorApplyColorFilter) {
3355 auto contents = SolidColorContents();
3356 contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75));
3357 auto result = contents.ApplyColorFilter([](const Color& color) {
3358 return color.Blend(Color::LimeGreen().WithAlpha(0.75), BlendMode::kScreen);
3359 });
3360 ASSERT_TRUE(result);
3361 ASSERT_COLOR_NEAR(contents.GetColor(),
3362 Color(0.433247, 0.879523, 0.825324, 0.75));
3363 }
3364
3365 #define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \
3366 TEST_P(AiksTest, name##GradientApplyColorFilter) { \
3367 auto contents = name##GradientContents(); \
3368 contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \
3369 auto result = contents.ApplyColorFilter([](const Color& color) { \
3370 return color.Blend(Color::LimeGreen().WithAlpha(0.75), \
3371 BlendMode::kScreen); \
3372 }); \
3373 ASSERT_TRUE(result); \
3374 \
3375 std::vector<Color> expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \
3376 ASSERT_COLORS_NEAR(contents.GetColors(), expected); \
3377 }
3378
3379 APPLY_COLOR_FILTER_GRADIENT_TEST(Linear);
3380 APPLY_COLOR_FILTER_GRADIENT_TEST(Radial);
3381 APPLY_COLOR_FILTER_GRADIENT_TEST(Conical);
3382 APPLY_COLOR_FILTER_GRADIENT_TEST(Sweep);
3383
3384 TEST_P(AiksTest, DrawScaledTextWithPerspectiveNoSaveLayer) {
3385 Canvas canvas;
3386 // clang-format off
3387 canvas.Transform(Matrix(
3388 2.000000, 0.000000, 0.000000, 0.000000,
3389 1.445767, 2.637070, -0.507928, 0.001524,
3390 -2.451887, -0.534662, 0.861399, -0.002584,
3391 1063.481934, 1025.951416, -48.300270, 1.144901
3392 ));
3393 // clang-format on
3394
3395 ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), canvas, "Hello world",
3396 "Roboto-Regular.ttf"));
3397
3398 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3399 }
3400
3401 TEST_P(AiksTest, DrawScaledTextWithPerspectiveSaveLayer) {
3402 Canvas canvas;
3403 Paint save_paint;
3404 canvas.SaveLayer(save_paint);
3405 // clang-format off
3406 canvas.Transform(Matrix(
3407 2.000000, 0.000000, 0.000000, 0.000000,
3408 1.445767, 2.637070, -0.507928, 0.001524,
3409 -2.451887, -0.534662, 0.861399, -0.002584,
3410 1063.481934, 1025.951416, -48.300270, 1.144901
3411 ));
3412 // clang-format on
3413
3414 ASSERT_TRUE(RenderTextInCanvasSkia(GetContext(), canvas, "Hello world",
3415 "Roboto-Regular.ttf"));
3416 }
3417
3418 TEST_P(AiksTest, PipelineBlendSingleParameter) {
3419 Canvas canvas;
3420
3421 // Should render a green square in the middle of a blue circle.
3422 canvas.SaveLayer({});
3423 {
3424 canvas.Translate(Point(100, 100));
3425 canvas.DrawCircle(Point(200, 200), 200, {.color = Color::Blue()});
3426 canvas.ClipRect(Rect(100, 100, 200, 200));
3427 canvas.DrawCircle(Point(200, 200), 200,
3428 {
3429 .color = Color::Green(),
3430 .blend_mode = BlendMode::kSourceOver,
3431 .image_filter = ImageFilter::MakeFromColorFilter(
3432 *ColorFilter::MakeBlend(BlendMode::kDestination,
3433 Color::White())),
3434 });
3435 canvas.Restore();
3436 }
3437
3438 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3439 }
3440
3441 TEST_P(AiksTest, ClippedBlurFilterRendersCorrectlyInteractive) {
3442 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
3443 auto point = IMPELLER_PLAYGROUND_POINT(Point(400, 400), 20, Color::Green());
3444
3445 Canvas canvas;
3446 canvas.Translate(point - Point(400, 400));
3447 Paint paint;
3448 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
3449 .style = FilterContents::BlurStyle::kNormal,
3450 .sigma = Radius{120 * 3},
3451 };
3452 paint.color = Color::Red();
3453 PathBuilder builder{};
3454 builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
3455 canvas.DrawPath(builder.TakePath(), paint);
3456 return canvas.EndRecordingAsPicture();
3457 };
3458 ASSERT_TRUE(OpenPlaygroundHere(callback));
3459 }
3460
3461 TEST_P(AiksTest, ClippedBlurFilterRendersCorrectly) {
3462 Canvas canvas;
3463 canvas.Translate(Point(0, -400));
3464 Paint paint;
3465 paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
3466 .style = FilterContents::BlurStyle::kNormal,
3467 .sigma = Radius{120 * 3},
3468 };
3469 paint.color = Color::Red();
3470 PathBuilder builder{};
3471 builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
3472 canvas.DrawPath(builder.TakePath(), paint);
3473 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3474 }
3475
3476 TEST_P(AiksTest, CaptureContext) {
3477 auto capture_context = CaptureContext::MakeAllowlist({"TestDocument"});
3478
3479 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
3480 Canvas canvas;
3481
3482 capture_context.Rewind();
3483 auto document = capture_context.GetDocument("TestDocument");
3484
3485 auto color = document.AddColor("Background color", Color::CornflowerBlue());
3486 canvas.DrawPaint({.color = color});
3487
3488 ImGui::Begin("TestDocument", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
3489 document.GetElement()->properties.Iterate([](CaptureProperty& property) {
3490 property.Invoke({.color = [](CaptureColorProperty& p) {
3491 ImGui::ColorEdit4(p.label.c_str(), reinterpret_cast<float*>(&p.value));
3492 }});
3493 });
3494 ImGui::End();
3495
3496 return canvas.EndRecordingAsPicture();
3497 };
3498 OpenPlaygroundHere(callback);
3499 }
3500
3501 TEST_P(AiksTest, CaptureInactivatedByDefault) {
3502 ASSERT_FALSE(GetContext()->capture.IsActive());
3503 }
3504
3505 // Regression test for https://github.com/flutter/flutter/issues/134678.
3506 TEST_P(AiksTest, ReleasesTextureOnTeardown) {
3507 auto context = GetContext();
3508 std::weak_ptr<Texture> weak_texture;
3509
3510 {
3511 auto texture = CreateTextureForFixture("table_mountain_nx.png");
3512
3513 Canvas canvas;
3514 canvas.Scale(GetContentScale());
3515 canvas.Translate({100.0f, 100.0f, 0});
3516
3517 Paint paint;
3518 paint.color_source = ColorSource::MakeImage(
3519 texture, Entity::TileMode::kClamp, Entity::TileMode::kClamp, {}, {});
3520 canvas.DrawRect({0, 0, 600, 600}, paint);
3521
3522 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3523 }
3524
3525 // See https://github.com/flutter/flutter/issues/134751.
3526 //
3527 // If the fence waiter was working this may not be released by the end of the
3528 // scope above. Adding a manual shutdown so that future changes to the fence
3529 // waiter will not flake this test.
3530 context->Shutdown();
3531
3532 // The texture should be released by now.
3533 ASSERT_TRUE(weak_texture.expired()) << "When the texture is no longer in use "
3534 "by the backend, it should be "
3535 "released.";
3536 }
3537
3538 // Regression test for https://github.com/flutter/flutter/issues/135441 .
3539 TEST_P(AiksTest, VerticesGeometryUVPositionData) {
3540 Canvas canvas;
3541 Paint paint;
3542 auto texture = CreateTextureForFixture("table_mountain_nx.png");
3543
3544 paint.color_source = ColorSource::MakeImage(texture, Entity::TileMode::kClamp,
3545 Entity::TileMode::kClamp, {}, {});
3546
3547 auto vertices = {Point(0, 0), Point(texture->GetSize().width, 0),
3548 Point(0, texture->GetSize().height)};
3549 std::vector<uint16_t> indices = {0u, 1u, 2u};
3550 std::vector<Point> texture_coordinates = {};
3551 std::vector<Color> vertex_colors = {};
3552 auto geometry = std::make_shared<VerticesGeometry>(
3553 vertices, indices, texture_coordinates, vertex_colors,
3554 Rect::MakeLTRB(0, 0, 1, 1), VerticesGeometry::VertexMode::kTriangleStrip);
3555
3556 canvas.DrawVertices(geometry, BlendMode::kSourceOver, paint);
3557 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3558 }
3559
3560 TEST_P(AiksTest, ClearBlendWithBlur) {
3561 Canvas canvas;
3562 Paint white;
3563 white.color = Color::Blue();
3564 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white);
3565
3566 Paint clear;
3567 clear.blend_mode = BlendMode::kClear;
3568 clear.mask_blur_descriptor = Paint::MaskBlurDescriptor{
3569 .style = FilterContents::BlurStyle::kNormal,
3570 .sigma = Sigma(20),
3571 };
3572
3573 canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear);
3574
3575 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3576 }
3577
3578 TEST_P(AiksTest, ClearBlend) {
3579 Canvas canvas;
3580 Paint white;
3581 white.color = Color::Blue();
3582 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white);
3583
3584 Paint clear;
3585 clear.blend_mode = BlendMode::kClear;
3586
3587 canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear);
3588 }
3589
3590 TEST_P(AiksTest, MatrixImageFilterMagnify) {
3591 Canvas canvas;
3592 canvas.Scale(GetContentScale());
3593 auto image = std::make_shared<Image>(CreateTextureForFixture("airplane.jpg"));
3594 canvas.Translate({600, -200});
3595 canvas.SaveLayer({
3596 .image_filter = std::make_shared<MatrixImageFilter>(
3597 Matrix{
3598 2, 0, 0, 0, //
3599 0, 2, 0, 0, //
3600 0, 0, 2, 0, //
3601 0, 0, 0, 1 //
3602 },
3603 SamplerDescriptor{}),
3604 });
3605 canvas.DrawImage(image, {0, 0}, Paint{.color = Color(1.0, 1.0, 1.0, 0.5)});
3606 canvas.Restore();
3607
3608 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3609 }
3610
3611 // This should be solid red, if you see a little red box this is broken.
3612 TEST_P(AiksTest, ClearColorOptimizationWhenSubpassIsBiggerThanParentPass) {
3613 Canvas canvas;
3614 canvas.Scale(GetContentScale());
3615 canvas.DrawRect(Rect::MakeLTRB(200, 200, 300, 300), {.color = Color::Red()});
3616 canvas.SaveLayer({
3617 .image_filter = std::make_shared<MatrixImageFilter>(
3618 Matrix::MakeScale({2, 2, 1}), SamplerDescriptor{}),
3619 });
3620 // Draw a rectangle that would fully cover the parent pass size, but not
3621 // the subpass that it is rendered in.
3622 canvas.DrawRect(Rect::MakeLTRB(0, 0, 400, 400), {.color = Color::Green()});
3623 // Draw a bigger rectangle to force the subpass to be bigger.
3624 canvas.DrawRect(Rect::MakeLTRB(0, 0, 800, 800), {.color = Color::Red()});
3625 canvas.Restore();
3626
3627 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3628 }
3629
3630 } // namespace testing
3631 } // namespace impeller
BLEND_MODE_TEST
#define BLEND_MODE_TEST(blend_mode)
Definition: aiks_unittests.cc:2605
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::Canvas::EndRecordingAsPicture
Picture EndRecordingAsPicture()
Definition: canvas.cc:513
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:2524
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:153
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:549
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:592
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:532
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:467
impeller::Canvas::DrawPicture
void DrawPicture(const Picture &picture)
Definition: canvas.cc:437
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:77
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:482
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:145
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:55
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:271
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:249
APPLY_COLOR_FILTER_GRADIENT_TEST
#define APPLY_COLOR_FILTER_GRADIENT_TEST(name)
Definition: aiks_unittests.cc:3365
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:81
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:91
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:653
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:173
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:183
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:161
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:101
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:124
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:315
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:157
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:128
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:112
impeller::Canvas::DrawRect
void DrawRect(Rect rect, const Paint &paint)
Definition: canvas.cc:229
impeller::Degrees
Definition: scalar.h:43
impeller::testing::TEST_P
TEST_P(AiksTest, ClearColorOptimizationWhenSubpassIsBiggerThanParentPass)
Definition: aiks_unittests.cc:3612
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:418
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:294
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:141
impeller::Canvas::ClipPath
void ClipPath(const Path &path, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition: canvas.cc:284
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