| Flutter Impeller |

aiks_gradient_unittests.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "flutter/impeller/aiks/aiks_unittests.h"
6
7 #include "impeller/aiks/canvas.h"
8 #include "impeller/entity/contents/conical_gradient_contents.h"
9 #include "impeller/entity/contents/linear_gradient_contents.h"
10 #include "impeller/entity/contents/radial_gradient_contents.h"
11 #include "impeller/entity/contents/sweep_gradient_contents.h"
12 #include "impeller/geometry/geometry_asserts.h"
13 #include "impeller/geometry/path_builder.h"
14 #include "impeller/playground/widgets.h"
15 #include "third_party/imgui/imgui.h"
16
17 ////////////////////////////////////////////////////////////////////////////////
18 // This is for tests of Canvas that are interested the results of rendering
19 // gradients.
20 ////////////////////////////////////////////////////////////////////////////////
21
22 namespace impeller {
23 namespace testing {
24
25 namespace {
26 void CanRenderLinearGradient(AiksTest* aiks_test, Entity::TileMode tile_mode) {
27 Canvas canvas;
28 canvas.Scale(aiks_test->GetContentScale());
29 Paint paint;
30 canvas.Translate({100.0f, 0, 0});
31
32 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
33 Color{0.1294, 0.5882, 0.9529, 0.0}};
34 std::vector<Scalar> stops = {0.0, 1.0};
35
36 paint.color_source = ColorSource::MakeLinearGradient(
37 {0, 0}, {200, 200}, std::move(colors), std::move(stops), tile_mode, {});
38
39 paint.color = Color(1.0, 1.0, 1.0, 1.0);
40 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
41 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
42 }
43 } // namespace
44
45 TEST_P(AiksTest, CanRenderLinearGradientClamp) {
46 CanRenderLinearGradient(this, Entity::TileMode::kClamp);
47 }
48 TEST_P(AiksTest, CanRenderLinearGradientRepeat) {
49 CanRenderLinearGradient(this, Entity::TileMode::kRepeat);
50 }
51 TEST_P(AiksTest, CanRenderLinearGradientMirror) {
52 CanRenderLinearGradient(this, Entity::TileMode::kMirror);
53 }
54 TEST_P(AiksTest, CanRenderLinearGradientDecal) {
55 CanRenderLinearGradient(this, Entity::TileMode::kDecal);
56 }
57
58 TEST_P(AiksTest, CanRenderLinearGradientDecalWithColorFilter) {
59 Canvas canvas;
60 canvas.Scale(GetContentScale());
61 Paint paint;
62 canvas.Translate({100.0f, 0, 0});
63
64 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
65 Color{0.1294, 0.5882, 0.9529, 0.0}};
66 std::vector<Scalar> stops = {0.0, 1.0};
67
68 paint.color_source = ColorSource::MakeLinearGradient(
69 {0, 0}, {200, 200}, std::move(colors), std::move(stops),
70 Entity::TileMode::kDecal, {});
71 // Overlay the gradient with 25% green. This should appear as the entire
72 // rectangle being drawn with 25% green, including the border area outside the
73 // decal gradient.
74 paint.color_filter = ColorFilter::MakeBlend(BlendMode::kSourceOver,
75 Color::Green().WithAlpha(0.25));
76
77 paint.color = Color(1.0, 1.0, 1.0, 1.0);
78 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
79 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
80 }
81
82 static void CanRenderLinearGradientWithDithering(AiksTest* aiks_test,
83 bool use_dithering) {
84 Canvas canvas;
85 Paint paint;
86 canvas.Translate({100.0, 100.0, 0});
87
88 // 0xffcccccc --> 0xff333333, taken from
89 // https://github.com/flutter/flutter/issues/118073#issue-1521699748
90 std::vector<Color> colors = {Color{0.8, 0.8, 0.8, 1.0},
91 Color{0.2, 0.2, 0.2, 1.0}};
92 std::vector<Scalar> stops = {0.0, 1.0};
93
94 paint.color_source = ColorSource::MakeLinearGradient(
95 {0, 0}, {800, 500}, std::move(colors), std::move(stops),
96 Entity::TileMode::kClamp, {});
97 paint.dither = use_dithering;
98 canvas.DrawRect(Rect::MakeXYWH(0, 0, 800, 500), paint);
99 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
100 }
101
102 TEST_P(AiksTest, CanRenderLinearGradientWithDitheringDisabled) {
103 CanRenderLinearGradientWithDithering(this, false);
104 }
105
106 TEST_P(AiksTest, CanRenderLinearGradientWithDitheringEnabled) {
107 CanRenderLinearGradientWithDithering(this, true);
108 } // namespace
109
110 static void CanRenderRadialGradientWithDithering(AiksTest* aiks_test,
111 bool use_dithering) {
112 Canvas canvas;
113 Paint paint;
114 canvas.Translate({100.0, 100.0, 0});
115
116 // #FFF -> #000
117 std::vector<Color> colors = {Color{1.0, 1.0, 1.0, 1.0},
118 Color{0.0, 0.0, 0.0, 1.0}};
119 std::vector<Scalar> stops = {0.0, 1.0};
120
121 paint.color_source = ColorSource::MakeRadialGradient(
122 {600, 600}, 600, std::move(colors), std::move(stops),
123 Entity::TileMode::kClamp, {});
124 paint.dither = use_dithering;
125 canvas.DrawRect(Rect::MakeXYWH(0, 0, 1200, 1200), paint);
126 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
127 }
128
129 TEST_P(AiksTest, CanRenderRadialGradientWithDitheringDisabled) {
130 CanRenderRadialGradientWithDithering(this, false);
131 }
132
133 TEST_P(AiksTest, CanRenderRadialGradientWithDitheringEnabled) {
134 CanRenderRadialGradientWithDithering(this, true);
135 }
136
137 static void CanRenderSweepGradientWithDithering(AiksTest* aiks_test,
138 bool use_dithering) {
139 Canvas canvas;
140 canvas.Scale(aiks_test->GetContentScale());
141 Paint paint;
142 canvas.Translate({100.0, 100.0, 0});
143
144 // #FFF -> #000
145 std::vector<Color> colors = {Color{1.0, 1.0, 1.0, 1.0},
146 Color{0.0, 0.0, 0.0, 1.0}};
147 std::vector<Scalar> stops = {0.0, 1.0};
148
149 paint.color_source = ColorSource::MakeSweepGradient(
150 {100, 100}, Degrees(45), Degrees(135), std::move(colors),
151 std::move(stops), Entity::TileMode::kMirror, {});
152 paint.dither = use_dithering;
153
154 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
155 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
156 }
157
158 TEST_P(AiksTest, CanRenderSweepGradientWithDitheringDisabled) {
159 CanRenderSweepGradientWithDithering(this, false);
160 }
161
162 TEST_P(AiksTest, CanRenderSweepGradientWithDitheringEnabled) {
163 CanRenderSweepGradientWithDithering(this, true);
164 }
165
166 static void CanRenderConicalGradientWithDithering(AiksTest* aiks_test,
167 bool use_dithering) {
168 Canvas canvas;
169 canvas.Scale(aiks_test->GetContentScale());
170 Paint paint;
171 canvas.Translate({100.0, 100.0, 0});
172
173 // #FFF -> #000
174 std::vector<Color> colors = {Color{1.0, 1.0, 1.0, 1.0},
175 Color{0.0, 0.0, 0.0, 1.0}};
176 std::vector<Scalar> stops = {0.0, 1.0};
177
178 paint.color_source = ColorSource::MakeConicalGradient(
179 {100, 100}, 100, std::move(colors), std::move(stops), {0, 1}, 0,
180 Entity::TileMode::kMirror, {});
181 paint.dither = use_dithering;
182
183 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
184 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
185 }
186
187 TEST_P(AiksTest, CanRenderConicalGradientWithDitheringDisabled) {
188 CanRenderConicalGradientWithDithering(this, false);
189 }
190
191 TEST_P(AiksTest, CanRenderConicalGradientWithDitheringEnabled) {
192 CanRenderConicalGradientWithDithering(this, true);
193 }
194
195 namespace {
196 void CanRenderLinearGradientWithOverlappingStops(AiksTest* aiks_test,
197 Entity::TileMode tile_mode) {
198 Canvas canvas;
199 Paint paint;
200 canvas.Translate({100.0, 100.0, 0});
201
202 std::vector<Color> colors = {
203 Color{0.9568, 0.2627, 0.2118, 1.0}, Color{0.9568, 0.2627, 0.2118, 1.0},
204 Color{0.1294, 0.5882, 0.9529, 1.0}, Color{0.1294, 0.5882, 0.9529, 1.0}};
205 std::vector<Scalar> stops = {0.0, 0.5, 0.5, 1.0};
206
207 paint.color_source = ColorSource::MakeLinearGradient(
208 {0, 0}, {500, 500}, std::move(colors), std::move(stops), tile_mode, {});
209
210 paint.color = Color(1.0, 1.0, 1.0, 1.0);
211 canvas.DrawRect(Rect::MakeXYWH(0, 0, 500, 500), paint);
212 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
213 }
214 } // namespace
215
216 // Only clamp is necessary. All tile modes are the same output.
217 TEST_P(AiksTest, CanRenderLinearGradientWithOverlappingStopsClamp) {
218 CanRenderLinearGradientWithOverlappingStops(this, Entity::TileMode::kClamp);
219 }
220
221 namespace {
222 void CanRenderLinearGradientManyColors(AiksTest* aiks_test,
223 Entity::TileMode tile_mode) {
224 Canvas canvas;
225 canvas.Scale(aiks_test->GetContentScale());
226 Paint paint;
227 canvas.Translate({100, 100, 0});
228
229 std::vector<Color> colors = {
230 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
231 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
232 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
233 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
234 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
235 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
236 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
237 std::vector<Scalar> stops = {
238 0.0,
239 (1.0 / 6.0) * 1,
240 (1.0 / 6.0) * 2,
241 (1.0 / 6.0) * 3,
242 (1.0 / 6.0) * 4,
243 (1.0 / 6.0) * 5,
244 1.0,
245 };
246
247 paint.color_source = ColorSource::MakeLinearGradient(
248 {0, 0}, {200, 200}, std::move(colors), std::move(stops), tile_mode, {});
249
250 paint.color = Color(1.0, 1.0, 1.0, 1.0);
251 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
252 canvas.Restore();
253 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
254 }
255 } // namespace
256
257 TEST_P(AiksTest, CanRenderLinearGradientManyColorsClamp) {
258 CanRenderLinearGradientManyColors(this, Entity::TileMode::kClamp);
259 }
260 TEST_P(AiksTest, CanRenderLinearGradientManyColorsRepeat) {
261 CanRenderLinearGradientManyColors(this, Entity::TileMode::kRepeat);
262 }
263 TEST_P(AiksTest, CanRenderLinearGradientManyColorsMirror) {
264 CanRenderLinearGradientManyColors(this, Entity::TileMode::kMirror);
265 }
266 TEST_P(AiksTest, CanRenderLinearGradientManyColorsDecal) {
267 CanRenderLinearGradientManyColors(this, Entity::TileMode::kDecal);
268 }
269
270 namespace {
271 void CanRenderLinearGradientWayManyColors(AiksTest* aiks_test,
272 Entity::TileMode tile_mode) {
273 Canvas canvas;
274 Paint paint;
275 canvas.Translate({100.0, 100.0, 0});
276 auto color = Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0};
277 std::vector<Color> colors;
278 std::vector<Scalar> stops;
279 auto current_stop = 0.0;
280 for (int i = 0; i < 2000; i++) {
281 colors.push_back(color);
282 stops.push_back(current_stop);
283 current_stop += 1 / 2000.0;
284 }
285 stops[2000 - 1] = 1.0;
286
287 paint.color_source = ColorSource::MakeLinearGradient(
288 {0, 0}, {200, 200}, std::move(colors), std::move(stops), tile_mode, {});
289
290 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
291 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
292 }
293 } // namespace
294
295 // Only test clamp on purpose since they all look the same.
296 TEST_P(AiksTest, CanRenderLinearGradientWayManyColorsClamp) {
297 CanRenderLinearGradientWayManyColors(this, Entity::TileMode::kClamp);
298 }
299
300 TEST_P(AiksTest, CanRenderLinearGradientManyColorsUnevenStops) {
301 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
302 const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
303 const Entity::TileMode tile_modes[] = {
304 Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
305 Entity::TileMode::kMirror, Entity::TileMode::kDecal};
306
307 static int selected_tile_mode = 0;
308 static Matrix matrix = {
309 1, 0, 0, 0, //
310 0, 1, 0, 0, //
311 0, 0, 1, 0, //
312 0, 0, 0, 1 //
313 };
314 if (AiksTest::ImGuiBegin("Controls", nullptr,
315 ImGuiWindowFlags_AlwaysAutoResize)) {
316 ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
317 sizeof(tile_mode_names) / sizeof(char*));
318 std::string label = "##1";
319 for (int i = 0; i < 4; i++) {
320 ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float,
321 &(matrix.vec[i]), 4, NULL, NULL, "%.2f", 0);
322 label[2]++;
323 }
324 ImGui::End();
325 }
326
327 Canvas canvas;
328 Paint paint;
329 canvas.Translate({100.0, 100.0, 0});
330 auto tile_mode = tile_modes[selected_tile_mode];
331
332 std::vector<Color> colors = {
333 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
334 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
335 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
336 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
337 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
338 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
339 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
340 std::vector<Scalar> stops = {
341 0.0, 2.0 / 62.0, 4.0 / 62.0, 8.0 / 62.0, 16.0 / 62.0, 32.0 / 62.0, 1.0,
342 };
343
344 paint.color_source = ColorSource::MakeLinearGradient(
345 {0, 0}, {200, 200}, std::move(colors), std::move(stops), tile_mode, {});
346
347 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
348 return canvas.EndRecordingAsPicture();
349 };
350 ASSERT_TRUE(OpenPlaygroundHere(callback));
351 }
352
353 TEST_P(AiksTest, CanRenderLinearGradientMaskBlur) {
354 Canvas canvas;
355
356 Paint paint = {
357 .color = Color::White(),
358 .color_source = ColorSource::MakeLinearGradient(
359 {200, 200}, {400, 400},
360 {Color::Red(), Color::White(), Color::Red(), Color::White(),
361 Color::Red(), Color::White(), Color::Red(), Color::White(),
362 Color::Red(), Color::White(), Color::Red()},
363 {0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0},
364 Entity::TileMode::kClamp, {}),
365 .mask_blur_descriptor =
366 Paint::MaskBlurDescriptor{
367 .style = FilterContents::BlurStyle::kNormal,
368 .sigma = Sigma(20),
369 },
370 };
371
372 canvas.DrawCircle({300, 300}, 200, paint);
373 canvas.DrawRect(Rect::MakeLTRB(100, 300, 500, 600), paint);
374
375 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
376 }
377
378 TEST_P(AiksTest, CanRenderRadialGradient) {
379 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
380 const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
381 const Entity::TileMode tile_modes[] = {
382 Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
383 Entity::TileMode::kMirror, Entity::TileMode::kDecal};
384
385 static int selected_tile_mode = 0;
386 static Matrix matrix = {
387 1, 0, 0, 0, //
388 0, 1, 0, 0, //
389 0, 0, 1, 0, //
390 0, 0, 0, 1 //
391 };
392 if (AiksTest::ImGuiBegin("Controls", nullptr,
393 ImGuiWindowFlags_AlwaysAutoResize)) {
394 ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
395 sizeof(tile_mode_names) / sizeof(char*));
396 std::string label = "##1";
397 for (int i = 0; i < 4; i++) {
398 ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float,
399 &(matrix.vec[i]), 4, NULL, NULL, "%.2f", 0);
400 label[2]++;
401 }
402 ImGui::End();
403 }
404
405 Canvas canvas;
406 Paint paint;
407 canvas.Translate({100.0, 100.0, 0});
408 auto tile_mode = tile_modes[selected_tile_mode];
409
410 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
411 Color{0.1294, 0.5882, 0.9529, 1.0}};
412 std::vector<Scalar> stops = {0.0, 1.0};
413
414 paint.color_source = ColorSource::MakeRadialGradient(
415 {100, 100}, 100, std::move(colors), std::move(stops), tile_mode, {});
416
417 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
418 return canvas.EndRecordingAsPicture();
419 };
420 ASSERT_TRUE(OpenPlaygroundHere(callback));
421 }
422
423 TEST_P(AiksTest, CanRenderRadialGradientManyColors) {
424 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
425 const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
426 const Entity::TileMode tile_modes[] = {
427 Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
428 Entity::TileMode::kMirror, Entity::TileMode::kDecal};
429
430 static int selected_tile_mode = 0;
431 static Matrix matrix = {
432 1, 0, 0, 0, //
433 0, 1, 0, 0, //
434 0, 0, 1, 0, //
435 0, 0, 0, 1 //
436 };
437 if (AiksTest::ImGuiBegin("Controls", nullptr,
438 ImGuiWindowFlags_AlwaysAutoResize)) {
439 ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
440 sizeof(tile_mode_names) / sizeof(char*));
441 std::string label = "##1";
442 for (int i = 0; i < 4; i++) {
443 ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float,
444 &(matrix.vec[i]), 4, NULL, NULL, "%.2f", 0);
445 label[2]++;
446 }
447 ImGui::End();
448 }
449
450 Canvas canvas;
451 Paint paint;
452 canvas.Translate({100.0, 100.0, 0});
453 auto tile_mode = tile_modes[selected_tile_mode];
454
455 std::vector<Color> colors = {
456 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
457 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
458 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
459 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
460 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
461 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
462 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
463 std::vector<Scalar> stops = {
464 0.0,
465 (1.0 / 6.0) * 1,
466 (1.0 / 6.0) * 2,
467 (1.0 / 6.0) * 3,
468 (1.0 / 6.0) * 4,
469 (1.0 / 6.0) * 5,
470 1.0,
471 };
472
473 paint.color_source = ColorSource::MakeRadialGradient(
474 {100, 100}, 100, std::move(colors), std::move(stops), tile_mode, {});
475
476 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
477 return canvas.EndRecordingAsPicture();
478 };
479 ASSERT_TRUE(OpenPlaygroundHere(callback));
480 }
481
482 namespace {
483 void CanRenderSweepGradient(AiksTest* aiks_test, Entity::TileMode tile_mode) {
484 Canvas canvas;
485 canvas.Scale(aiks_test->GetContentScale());
486 Paint paint;
487 canvas.Translate({100, 100, 0});
488
489 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
490 Color{0.1294, 0.5882, 0.9529, 1.0}};
491 std::vector<Scalar> stops = {0.0, 1.0};
492
493 paint.color_source = ColorSource::MakeSweepGradient(
494 {100, 100}, Degrees(45), Degrees(135), std::move(colors),
495 std::move(stops), tile_mode, {});
496
497 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
498 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
499 }
500 } // namespace
501
502 TEST_P(AiksTest, CanRenderSweepGradientClamp) {
503 CanRenderSweepGradient(this, Entity::TileMode::kClamp);
504 }
505 TEST_P(AiksTest, CanRenderSweepGradientRepeat) {
506 CanRenderSweepGradient(this, Entity::TileMode::kRepeat);
507 }
508 TEST_P(AiksTest, CanRenderSweepGradientMirror) {
509 CanRenderSweepGradient(this, Entity::TileMode::kMirror);
510 }
511 TEST_P(AiksTest, CanRenderSweepGradientDecal) {
512 CanRenderSweepGradient(this, Entity::TileMode::kDecal);
513 }
514
515 namespace {
516 void CanRenderSweepGradientManyColors(AiksTest* aiks_test,
517 Entity::TileMode tile_mode) {
518 Canvas canvas;
519 Paint paint;
520 canvas.Translate({100.0, 100.0, 0});
521
522 std::vector<Color> colors = {
523 Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
524 Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
525 Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
526 Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
527 Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
528 Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
529 Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
530 std::vector<Scalar> stops = {
531 0.0,
532 (1.0 / 6.0) * 1,
533 (1.0 / 6.0) * 2,
534 (1.0 / 6.0) * 3,
535 (1.0 / 6.0) * 4,
536 (1.0 / 6.0) * 5,
537 1.0,
538 };
539
540 paint.color_source = ColorSource::MakeSweepGradient(
541 {100, 100}, Degrees(45), Degrees(135), std::move(colors),
542 std::move(stops), tile_mode, {});
543
544 canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
545 ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
546 }
547 } // namespace
548
549 TEST_P(AiksTest, CanRenderSweepGradientManyColorsClamp) {
550 CanRenderSweepGradientManyColors(this, Entity::TileMode::kClamp);
551 }
552 TEST_P(AiksTest, CanRenderSweepGradientManyColorsRepeat) {
553 CanRenderSweepGradientManyColors(this, Entity::TileMode::kRepeat);
554 }
555 TEST_P(AiksTest, CanRenderSweepGradientManyColorsMirror) {
556 CanRenderSweepGradientManyColors(this, Entity::TileMode::kMirror);
557 }
558 TEST_P(AiksTest, CanRenderSweepGradientManyColorsDecal) {
559 CanRenderSweepGradientManyColors(this, Entity::TileMode::kDecal);
560 }
561
562 TEST_P(AiksTest, CanRenderConicalGradient) {
563 Scalar size = 256;
564 Canvas canvas;
565 Paint paint;
566 paint.color = Color::White();
567 canvas.DrawRect(Rect::MakeXYWH(0, 0, size * 3, size * 3), paint);
568 std::vector<Color> colors = {Color::MakeRGBA8(0xF4, 0x43, 0x36, 0xFF),
569 Color::MakeRGBA8(0xFF, 0xEB, 0x3B, 0xFF),
570 Color::MakeRGBA8(0x4c, 0xAF, 0x50, 0xFF),
571 Color::MakeRGBA8(0x21, 0x96, 0xF3, 0xFF)};
572 std::vector<Scalar> stops = {0.0, 1.f / 3.f, 2.f / 3.f, 1.0};
573 std::array<std::tuple<Point, float, Point, float>, 8> array{
574 std::make_tuple(Point{size / 2.f, size / 2.f}, 0.f,
575 Point{size / 2.f, size / 2.f}, size / 2.f),
576 std::make_tuple(Point{size / 2.f, size / 2.f}, size / 4.f,
577 Point{size / 2.f, size / 2.f}, size / 2.f),
578 std::make_tuple(Point{size / 4.f, size / 4.f}, 0.f,
579 Point{size / 2.f, size / 2.f}, size / 2.f),
580 std::make_tuple(Point{size / 4.f, size / 4.f}, size / 2.f,
581 Point{size / 2.f, size / 2.f}, 0),
582 std::make_tuple(Point{size / 4.f, size / 4.f}, size / 4.f,
583 Point{size / 2.f, size / 2.f}, size / 2.f),
584 std::make_tuple(Point{size / 4.f, size / 4.f}, size / 16.f,
585 Point{size / 2.f, size / 2.f}, size / 8.f),
586 std::make_tuple(Point{size / 4.f, size / 4.f}, size / 8.f,
587 Point{size / 2.f, size / 2.f}, size / 16.f),
588 std::make_tuple(Point{size / 8.f, size / 8.f}, size / 8.f,
589 Point{size / 2.f, size / 2.f}, size / 8.f),
590 };
591 for (int i = 0; i < 8; i++) {
592 canvas.Save();
593 canvas.Translate({(i % 3) * size, i / 3 * size, 0});
594 paint.color_source = ColorSource::MakeConicalGradient(
595 std::get<0>(array[i]), std::get<1>(array[i]), colors, stops,
596 std::get<2>(array[i]), std::get<3>(array[i]), Entity::TileMode::kClamp,
597 {});
598 canvas.DrawRect(Rect::MakeXYWH(0, 0, size, size), paint);
599 canvas.Restore();
600 }
601 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
602 }
603
604 TEST_P(AiksTest, CanRenderGradientDecalWithBackground) {
605 std::vector<Color> colors = {Color::MakeRGBA8(0xF4, 0x43, 0x36, 0xFF),
606 Color::MakeRGBA8(0xFF, 0xEB, 0x3B, 0xFF),
607 Color::MakeRGBA8(0x4c, 0xAF, 0x50, 0xFF),
608 Color::MakeRGBA8(0x21, 0x96, 0xF3, 0xFF)};
609 std::vector<Scalar> stops = {0.0, 1.f / 3.f, 2.f / 3.f, 1.0};
610
611 std::array<ColorSource, 3> color_sources = {
612 ColorSource::MakeLinearGradient({0, 0}, {100, 100}, colors, stops,
613 Entity::TileMode::kDecal, {}),
614 ColorSource::MakeRadialGradient({100, 100}, 100, colors, stops,
615 Entity::TileMode::kDecal, {}),
616 ColorSource::MakeSweepGradient({100, 100}, Degrees(45), Degrees(135),
617 colors, stops, Entity::TileMode::kDecal,
618 {}),
619 };
620
621 Canvas canvas;
622 Paint paint;
623 paint.color = Color::White();
624 canvas.DrawRect(Rect::MakeLTRB(0, 0, 605, 205), paint);
625 for (int i = 0; i < 3; i++) {
626 canvas.Save();
627 canvas.Translate({i * 200.0f, 0, 0});
628 paint.color_source = color_sources[i];
629 canvas.DrawRect(Rect::MakeLTRB(0, 0, 200, 200), paint);
630 canvas.Restore();
631 }
632 ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
633 }
634
635 #define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \
636 TEST_P(AiksTest, name##GradientApplyColorFilter) { \
637 auto contents = name##GradientContents(); \
638 contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \
639 auto result = contents.ApplyColorFilter([](const Color& color) { \
640 return color.Blend(Color::LimeGreen().WithAlpha(0.75), \
641 BlendMode::kScreen); \
642 }); \
643 ASSERT_TRUE(result); \
644 \
645 std::vector<Color> expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \
646 ASSERT_COLORS_NEAR(contents.GetColors(), expected); \
647 }
648
649 APPLY_COLOR_FILTER_GRADIENT_TEST(Linear);
650 APPLY_COLOR_FILTER_GRADIENT_TEST(Radial);
651 APPLY_COLOR_FILTER_GRADIENT_TEST(Conical);
652 APPLY_COLOR_FILTER_GRADIENT_TEST(Sweep);
653
654 TEST_P(AiksTest, GradientStrokesRenderCorrectly) {
655 // Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
656 auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
657 static float scale = 3;
658 static bool add_circle_clip = true;
659 const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
660 const Entity::TileMode tile_modes[] = {
661 Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
662 Entity::TileMode::kMirror, Entity::TileMode::kDecal};
663 static int selected_tile_mode = 0;
664 static float alpha = 1;
665
666 if (AiksTest::ImGuiBegin("Controls", nullptr,
667 ImGuiWindowFlags_AlwaysAutoResize)) {
668 ImGui::SliderFloat("Scale", &scale, 0, 6);
669 ImGui::Checkbox("Circle clip", &add_circle_clip);
670 ImGui::SliderFloat("Alpha", &alpha, 0, 1);
671 ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
672 sizeof(tile_mode_names) / sizeof(char*));
673 ImGui::End();
674 }
675
676 Canvas canvas;
677 canvas.Scale(GetContentScale());
678 Paint paint;
679 paint.color = Color::White();
680 canvas.DrawPaint(paint);
681
682 paint.style = Paint::Style::kStroke;
683 paint.color = Color(1.0, 1.0, 1.0, alpha);
684 paint.stroke_width = 10;
685 auto tile_mode = tile_modes[selected_tile_mode];
686
687 std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
688 Color{0.1294, 0.5882, 0.9529, 1.0}};
689 std::vector<Scalar> stops = {0.0, 1.0};
690
691 paint.color_source = ColorSource::MakeLinearGradient(
692 {0, 0}, {50, 50}, std::move(colors), std::move(stops), tile_mode, {});
693
694 Path path = PathBuilder{}
695 .MoveTo({20, 20})
696 .QuadraticCurveTo({60, 20}, {60, 60})
697 .Close()
698 .MoveTo({60, 20})
699 .QuadraticCurveTo({60, 60}, {20, 60})
700 .TakePath();
701
702 canvas.Scale(Vector2(scale, scale));
703
704 if (add_circle_clip) {
705 static PlaygroundPoint circle_clip_point_a(Point(60, 300), 20,
706 Color::Red());
707 static PlaygroundPoint circle_clip_point_b(Point(600, 300), 20,
708 Color::Red());
709 auto [handle_a, handle_b] =
710 DrawPlaygroundLine(circle_clip_point_a, circle_clip_point_b);
711
712 auto screen_to_canvas = canvas.GetCurrentTransform().Invert();
713 Point point_a = screen_to_canvas * handle_a * GetContentScale();
714 Point point_b = screen_to_canvas * handle_b * GetContentScale();
715
716 Point middle = (point_a + point_b) / 2;
717 auto radius = point_a.GetDistance(middle);
718 canvas.ClipPath(PathBuilder{}.AddCircle(middle, radius).TakePath());
719 }
720
721 for (auto join : {Join::kBevel, Join::kRound, Join::kMiter}) {
722 paint.stroke_join = join;
723 for (auto cap : {Cap::kButt, Cap::kSquare, Cap::kRound}) {
724 paint.stroke_cap = cap;
725 canvas.DrawPath(path, paint);
726 canvas.Translate({80, 0});
727 }
728 canvas.Translate({-240, 60});
729 }
730
731 return canvas.EndRecordingAsPicture();
732 };
733
734 ASSERT_TRUE(OpenPlaygroundHere(callback));
735 }
736
737 } // namespace testing
738 } // namespace impeller
impeller::Paint::stroke_cap
Cap stroke_cap
Definition: paint.h:60
impeller::AiksPlayground
Definition: aiks_playground.h:18
impeller::Entity::TileMode::kClamp
@ kClamp
impeller::Canvas::EndRecordingAsPicture
Picture EndRecordingAsPicture()
Definition: canvas.cc:756
impeller::Cap::kRound
@ kRound
impeller::Cap::kSquare
@ kSquare
impeller::Scalar
float Scalar
Definition: scalar.h:18
impeller::AiksContext
Definition: aiks_context.h:20
impeller::Color::Red
static constexpr Color Red()
Definition: color.h:264
impeller::testing::CanRenderSweepGradientWithDithering
static void CanRenderSweepGradientWithDithering(AiksTest *aiks_test, bool use_dithering)
Definition: aiks_gradient_unittests.cc:137
geometry_asserts.h
impeller::Paint::Style::kStroke
@ kStroke
aiks_unittests.h
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:23
impeller::testing::AiksTest
AiksPlayground AiksTest
Definition: aiks_unittests.h:17
impeller::TRect< Scalar >::MakeXYWH
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
impeller::Color::MakeRGBA8
static constexpr Color MakeRGBA8(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Definition: color.h:154
impeller::Color
Definition: color.h:124
impeller::Paint::color
Color color
Definition: paint.h:55
impeller::Entity::TileMode::kDecal
@ kDecal
impeller::testing::CanRenderConicalGradientWithDithering
static void CanRenderConicalGradientWithDithering(AiksTest *aiks_test, bool use_dithering)
Definition: aiks_gradient_unittests.cc:166
impeller::Canvas
Definition: canvas.h:58
impeller::PathBuilder
Definition: path_builder.h:14
impeller::Paint::MaskBlurDescriptor::style
FilterContents::BlurStyle style
Definition: paint.h:40
impeller::Vector2
Point Vector2
Definition: point.h:320
impeller::Paint::MaskBlurDescriptor
Definition: paint.h:39
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:138
impeller::FilterContents::BlurStyle::kNormal
@ kNormal
Blurred inside and outside.
sweep_gradient_contents.h
impeller::Cap::kButt
@ kButt
impeller::Entity::TileMode::kRepeat
@ kRepeat
impeller::Paint::color_source
ColorSource color_source
Definition: paint.h:56
impeller::Canvas::DrawRect
void DrawRect(const Rect &rect, const Paint &paint)
Definition: canvas.cc:441
impeller::Canvas::GetCurrentTransform
const Matrix & GetCurrentTransform() const
Definition: canvas.cc:247
impeller::Join::kMiter
@ kMiter
impeller::Matrix::vec
Vector4 vec[4]
Definition: matrix.h:41
impeller::Entity::TileMode::kMirror
@ kMirror
path_builder.h
impeller::Paint::color_filter
std::shared_ptr< ColorFilter > color_filter
Definition: paint.h:68
impeller::Point
TPoint< Scalar > Point
Definition: point.h:316
impeller::Canvas::Scale
void Scale(const Vector2 &scale)
Definition: canvas.cc:264
impeller::Path
Paths are lightweight objects that describe a collection of linear, quadratic, or cubic segments....
Definition: path.h:51
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:74
impeller::ColorFilter::MakeBlend
static std::shared_ptr< ColorFilter > MakeBlend(BlendMode blend_mode, Color color)
Definition: color_filter.cc:23
widgets.h
impeller::Canvas::Save
void Save()
Definition: canvas.cc:136
conical_gradient_contents.h
impeller::Paint::style
Style style
Definition: paint.h:63
impeller::Canvas::DrawCircle
void DrawCircle(const Point &center, Scalar radius, const Paint &paint)
Definition: canvas.cc:515
impeller::testing::CanRenderRadialGradientWithDithering
static void CanRenderRadialGradientWithDithering(AiksTest *aiks_test, bool use_dithering)
Definition: aiks_gradient_unittests.cc:110
impeller::Color::White
static constexpr Color White()
Definition: color.h:256
impeller::Sigma
In filters that use Gaussian distributions, "sigma" is a size of one standard deviation in terms of t...
Definition: sigma.h:32
impeller::Canvas::Restore
bool Restore()
Definition: canvas.cc:208
impeller::testing::CanRenderLinearGradientWithDithering
static void CanRenderLinearGradientWithDithering(AiksTest *aiks_test, bool use_dithering)
Definition: aiks_gradient_unittests.cc:82
canvas.h
impeller::Color::Green
static constexpr Color Green()
Definition: color.h:266
impeller::Canvas::DrawPath
void DrawPath(const Path &path, const Paint &paint)
Definition: canvas.cc:292
impeller::PathBuilder::TakePath
Path TakePath(FillType fill=FillType::kNonZero)
Definition: path_builder.cc:22
impeller::Canvas::DrawPaint
void DrawPaint(const Paint &paint)
Definition: canvas.cc:302
impeller::Join::kRound
@ kRound
impeller::Entity::TileMode
TileMode
Definition: entity.h:42
impeller::Matrix::Invert
Matrix Invert() const
Definition: matrix.cc:97
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:108
impeller::Close
void Close(PathBuilder *builder)
Definition: tessellator.cc:36
impeller::AiksPlayground::OpenPlaygroundHere
bool OpenPlaygroundHere(Picture picture)
Definition: aiks_playground.cc:30
impeller::Join::kBevel
@ kBevel
impeller::Playground::GetContentScale
Point GetContentScale() const
Definition: playground.cc:186
impeller::PlaygroundPoint
Definition: widgets.h:17
impeller::testing::TEST_P
TEST_P(AiksTest, CanRenderMaskBlurHugeSigma)
Definition: aiks_blur_unittests.cc:23
impeller::TPoint< Scalar >
impeller::PathBuilder::MoveTo
PathBuilder & MoveTo(Point point, bool relative=false)
Definition: path_builder.cc:33
scale
const Scalar scale
Definition: stroke_path_geometry.cc:297
impeller::AiksPlayground::ImGuiBegin
static bool ImGuiBegin(const char *name, bool *p_open, ImGuiWindowFlags flags)
Definition: aiks_playground.cc:59
impeller::Degrees
Definition: scalar.h:46
impeller::PathBuilder::AddCircle
PathBuilder & AddCircle(const Point &center, Scalar radius)
Definition: path_builder.cc:130
radial_gradient_contents.h
impeller::TPoint::GetDistance
constexpr Type GetDistance(const TPoint &p) const
Definition: point.h:200
impeller::TRect< Scalar >::MakeLTRB
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129
impeller::testing::APPLY_COLOR_FILTER_GRADIENT_TEST
APPLY_COLOR_FILTER_GRADIENT_TEST(Linear)
impeller
Definition: aiks_blur_unittests.cc:20
impeller::Paint::dither
bool dither
Definition: paint.h:57
impeller::Paint::stroke_width
Scalar stroke_width
Definition: paint.h:59
impeller::Matrix
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
impeller::BlendMode::kSourceOver
@ kSourceOver
impeller::DrawPlaygroundLine
std::tuple< Point, Point > DrawPlaygroundLine(PlaygroundPoint &point_a, PlaygroundPoint &point_b)
Definition: widgets.cc:50
linear_gradient_contents.h
impeller::Paint::stroke_join
Join stroke_join
Definition: paint.h:61
impeller::Canvas::Translate
void Translate(const Vector3 &offset)
Definition: canvas.cc:260
impeller::Canvas::ClipPath
void ClipPath(const Path &path, Entity::ClipOperation clip_op=Entity::ClipOperation::kIntersect)
Definition: canvas.cc:539