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