Flutter Impeller
dl_golden_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 
6 
7 #include "display_list/dl_color.h"
8 #include "display_list/dl_paint.h"
9 #include "display_list/geometry/dl_geometry_types.h"
10 #include "display_list/geometry/dl_path_builder.h"
11 #include "flutter/display_list/dl_builder.h"
12 #include "flutter/impeller/display_list/testing/render_text_in_canvas.h"
13 #include "flutter/impeller/display_list/testing/rmse.h"
14 #include "flutter/testing/testing.h"
15 #include "gtest/gtest.h"
16 
17 namespace flutter {
18 namespace testing {
19 
20 using impeller::Degrees;
23 using impeller::Point;
24 using impeller::Radians;
25 using impeller::Scalar;
26 
28 
29 TEST_P(DlGoldenTest, CanDrawPaint) {
30  auto draw = [](DlCanvas* canvas,
31  const std::vector<std::unique_ptr<DlImage>>& images) {
32  canvas->Scale(0.2, 0.2);
33  DlPaint paint;
34  paint.setColor(DlColor::kCyan());
35  canvas->DrawPaint(paint);
36  };
37 
38  DisplayListBuilder builder;
39  draw(&builder, /*images=*/{});
40 
41  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
42 }
43 
44 TEST_P(DlGoldenTest, CanRenderImage) {
45  auto draw = [](DlCanvas* canvas, const std::vector<sk_sp<DlImage>>& images) {
46  FML_CHECK(images.size() >= 1);
47  DlPaint paint;
48  paint.setColor(DlColor::kRed());
49  canvas->DrawImage(images[0], DlPoint(100.0, 100.0),
50  DlImageSampling::kLinear, &paint);
51  };
52 
53  DisplayListBuilder builder;
54  std::vector<sk_sp<DlImage>> images;
55  images.emplace_back(CreateDlImageForFixture("kalimba.jpg"));
56  draw(&builder, images);
57 
58  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
59 }
60 
61 // Asserts that subpass rendering of MatrixImageFilters works.
62 // https://github.com/flutter/flutter/issues/147807
63 TEST_P(DlGoldenTest, Bug147807) {
64  Point content_scale = GetContentScale();
65  auto draw = [content_scale](DlCanvas* canvas,
66  const std::vector<sk_sp<DlImage>>& images) {
67  canvas->Scale(content_scale.x, content_scale.y);
68  DlPaint paint;
69  paint.setColor(DlColor(0xfffef7ff));
70  canvas->DrawRect(DlRect::MakeLTRB(0, 0, 375, 667), paint);
71  paint.setColor(DlColor(0xffff9800));
72  canvas->DrawRect(DlRect::MakeLTRB(0, 0, 187.5, 333.5), paint);
73  paint.setColor(DlColor(0xff9c27b0));
74  canvas->DrawRect(DlRect::MakeLTRB(187.5, 0, 375, 333.5), paint);
75  paint.setColor(DlColor(0xff4caf50));
76  canvas->DrawRect(DlRect::MakeLTRB(0, 333.5, 187.5, 667), paint);
77  paint.setColor(DlColor(0xfff44336));
78  canvas->DrawRect(DlRect::MakeLTRB(187.5, 333.5, 375, 667), paint);
79 
80  canvas->Save();
81  {
82  canvas->ClipRoundRect(
83  DlRoundRect::MakeOval(DlRect::MakeLTRB(201.25, 10, 361.25, 170)),
84  DlClipOp::kIntersect, true);
85  DlRect save_layer_bounds = DlRect::MakeLTRB(201.25, 10, 361.25, 170);
86  auto backdrop =
87  DlImageFilter::MakeMatrix(DlMatrix::MakeRow(3, 0, 0.0, -280, //
88  0, 3, 0.0, -920, //
89  0, 0, 1.0, 0.0, //
90  0, 0, 0.0, 1.0),
91  DlImageSampling::kLinear);
92  canvas->SaveLayer(save_layer_bounds, /*paint=*/nullptr, backdrop.get());
93  {
94  canvas->Translate(201.25, 10);
95  auto paint = DlPaint()
96  .setAntiAlias(true)
97  .setColor(DlColor(0xff2196f3))
98  .setStrokeWidth(5)
99  .setDrawStyle(DlDrawStyle::kStroke);
100  canvas->DrawCircle(DlPoint(80, 80), 80, paint);
101  }
102  canvas->Restore();
103  }
104  canvas->Restore();
105  };
106 
107  DisplayListBuilder builder;
108  std::vector<sk_sp<DlImage>> images;
109  draw(&builder, images);
110 
111  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
112 }
113 
114 TEST_P(DlGoldenTest, FractionalDilation) {
115  auto draw = [](DlCanvas* canvas) {
116  const DlRect rect = DlRect::MakeLTRB(32, 32, 382, 382);
117  DlPaint layer_paint;
118  layer_paint.setImageFilter(DlImageFilter::MakeDilate(3.95f, 3.95f));
119  canvas->SaveLayer(rect.Expand(8.0f), &layer_paint);
120  canvas->DrawRect(rect, DlPaint().setColor(DlColor::kRed()));
121  canvas->Restore();
122  canvas->DrawRect(rect, DlPaint().setColor(DlColor::kBlack()));
123  };
124 
125  DisplayListBuilder builder;
126  builder.Scale(GetContentScale().x, GetContentScale().y);
127  builder.DrawColor(DlColor::kWhite(), DlBlendMode::kSrc);
128 
129  draw(&builder);
130 
131  builder.Translate(440, 0);
132  builder.Scale(1.3f, 1.3f);
133  draw(&builder);
134 
135  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
136 }
137 
138 namespace {
139 void DrawBlurGrid(DlCanvas* canvas) {
140  DlPaint paint;
141  paint.setColor(DlColor(0xfffef7ff));
142  Scalar width = 150;
143  Scalar height = 150;
144  Scalar gap = 80;
145  std::vector<Scalar> blur_radii = {10, 30, 50};
146  for (size_t i = 0; i < blur_radii.size(); ++i) {
147  Scalar blur_radius = blur_radii[i];
148  auto blur_filter = std::make_shared<flutter::DlBlurMaskFilter>(
149  flutter::DlBlurStyle::kNormal, blur_radius);
150  paint.setMaskFilter(blur_filter);
151  Scalar yval = gap + i * (gap + height);
152  canvas->DrawRoundRect(
153  DlRoundRect::MakeNinePatch(DlRect::MakeXYWH(gap, yval, width, height),
154  10, 10, 10, 10),
155  paint);
156  canvas->DrawRoundRect(
157  DlRoundRect::MakeNinePatch(
158  DlRect::MakeXYWH(2.0 * gap + width, yval, width, height), //
159  9, 10, 10, 10),
160  paint);
161  }
162 }
163 } // namespace
164 
165 TEST_P(DlGoldenTest, GaussianVsRRectBlur) {
166  Point content_scale = GetContentScale();
167  auto draw = [content_scale](DlCanvas* canvas,
168  const std::vector<sk_sp<DlImage>>& images) {
169  canvas->Scale(content_scale.x, content_scale.y);
170  canvas->DrawPaint(DlPaint().setColor(DlColor(0xff112233)));
171  DrawBlurGrid(canvas);
172  };
173 
174  DisplayListBuilder builder;
175  std::vector<sk_sp<DlImage>> images;
176  draw(&builder, images);
177 
178  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
179 }
180 
181 TEST_P(DlGoldenTest, GaussianVsRRectBlurScaled) {
182  Point content_scale = GetContentScale();
183  auto draw = [content_scale](DlCanvas* canvas,
184  const std::vector<sk_sp<DlImage>>& images) {
185  canvas->Scale(content_scale.x, content_scale.y);
186  canvas->DrawPaint(DlPaint().setColor(DlColor(0xff112233)));
187  canvas->Scale(0.33, 0.33);
188  DrawBlurGrid(canvas);
189  };
190 
191  DisplayListBuilder builder;
192  std::vector<sk_sp<DlImage>> images;
193  draw(&builder, images);
194 
195  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
196 }
197 
198 TEST_P(DlGoldenTest, GaussianVsRRectBlurScaledRotated) {
199  Point content_scale = GetContentScale();
200  auto draw = [content_scale](DlCanvas* canvas,
201  const std::vector<sk_sp<DlImage>>& images) {
202  canvas->Scale(content_scale.x, content_scale.y);
203  canvas->Translate(200, 200);
204  canvas->DrawPaint(DlPaint().setColor(DlColor(0xff112233)));
205  canvas->Scale(0.33, 0.33);
206  canvas->Translate(300, 300);
207  canvas->Rotate(45);
208  canvas->Translate(-300, -300);
209  DrawBlurGrid(canvas);
210  };
211 
212  DisplayListBuilder builder;
213  std::vector<sk_sp<DlImage>> images;
214  draw(&builder, images);
215 
216  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
217 }
218 
219 TEST_P(DlGoldenTest, FastVsGeneralGaussianMaskBlur) {
220  DisplayListBuilder builder;
221  builder.Scale(GetContentScale().x, GetContentScale().y);
222  builder.DrawColor(DlColor::kWhite(), DlBlendMode::kSrc);
223 
224  auto blur_sigmas = std::array{5.0f, 10.0f, 20.0f};
225  auto blur_colors = std::array{
226  DlColor::kBlue(),
227  DlColor::kGreen(),
228  DlColor::kMaroon(),
229  };
230 
231  auto make_rrect_path = [](const DlRect& rect, DlScalar rx,
232  DlScalar ry) -> DlPath {
233  auto add_corner = [](DlPathBuilder& path_builder, DlPoint corner,
234  DlVector2 relative_from, DlVector2 relative_to,
235  bool first) {
236  static const auto magic = DlPathBuilder::kArcApproximationMagic;
237 
238  if (first) {
239  path_builder.MoveTo(corner + relative_from);
240  } else {
241  path_builder.LineTo(corner + relative_from);
242  }
243  // These fractions should be (1 - magic) to make a proper rrect
244  // path, but historically these equations were as written here.
245  // On the plus side, they ensure that we will not optimize this
246  // path as "Hey, look, it's an RRect", but the DrawPath gaussians
247  // will otherwise not be identical to the versions drawn with
248  // DrawRoundRect
249  path_builder.CubicCurveTo(corner + relative_from * magic,
250  corner + relative_to * magic,
251  corner + relative_to);
252  };
253 
254  DlPathBuilder path_builder;
255  add_corner(path_builder, rect.GetRightTop(), //
256  DlVector2(-rx, 0.0f), DlVector2(0.0f, ry), true);
257  add_corner(path_builder, rect.GetRightBottom(), //
258  DlVector2(0.0f, -ry), DlVector2(-rx, 0.0f), false);
259  add_corner(path_builder, rect.GetLeftBottom(), //
260  DlVector2(rx, 0.0f), DlVector2(0.0f, -ry), false);
261  add_corner(path_builder, rect.GetLeftTop(), //
262  DlVector2(0.0f, ry), DlVector2(rx, 0.0f), false);
263  return path_builder.TakePath();
264  };
265 
266  for (size_t i = 0; i < blur_sigmas.size(); i++) {
267  auto rect = DlRect::MakeXYWH(i * 320.0f + 50.0f, 50.0f, 100.0f, 100.0f);
268  DlPaint paint = DlPaint() //
269  .setColor(blur_colors[i])
270  .setMaskFilter(DlBlurMaskFilter::Make(
271  DlBlurStyle::kNormal, blur_sigmas[i]));
272 
273  builder.DrawRoundRect(DlRoundRect::MakeRectXY(rect, 10.0f, 10.0f), paint);
274  rect = rect.Shift(150.0f, 0.0f);
275  builder.DrawPath(make_rrect_path(rect, 10.0f, 10.0f), paint);
276  rect = rect.Shift(-150.0f, 0.0f);
277 
278  rect = rect.Shift(0.0f, 200.0f);
279  builder.DrawRoundRect(DlRoundRect::MakeRectXY(rect, 10.0f, 30.0f), paint);
280  rect = rect.Shift(150.0f, 0.0f);
281  builder.DrawPath(make_rrect_path(rect, 10.0f, 20.0f), paint);
282  rect = rect.Shift(-150.0f, 0.0f);
283 
284  rect = rect.Shift(0.0f, 200.0f);
285  builder.DrawRoundRect(DlRoundRect::MakeRectXY(rect, 30.0f, 10.0f), paint);
286  rect = rect.Shift(150.0f, 0.0f);
287  builder.DrawPath(make_rrect_path(rect, 20.0f, 10.0f), paint);
288  rect = rect.Shift(-150.0f, 0.0f);
289  }
290 
291  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
292 }
293 
294 TEST_P(DlGoldenTest, DashedLinesTest) {
295  Point content_scale = GetContentScale();
296  auto draw = [content_scale](DlCanvas* canvas,
297  const std::vector<sk_sp<DlImage>>& images) {
298  canvas->Scale(content_scale.x, content_scale.y);
299  canvas->DrawPaint(DlPaint().setColor(DlColor::kWhite()));
300 
301  auto draw_one = [canvas](DlStrokeCap cap, Scalar x, Scalar y,
302  Scalar dash_on, Scalar dash_off) {
303  Point center = Point(x, y);
304  Scalar inner = 20.0f;
305  Scalar outer = 100.0f;
306  DlPaint thick_paint = DlPaint()
307  .setColor(DlColor::kBlue())
308  .setStrokeCap(cap)
309  .setStrokeWidth(8.0f);
310  DlPaint middle_paint = DlPaint()
311  .setColor(DlColor::kGreen())
312  .setStrokeCap(cap)
313  .setStrokeWidth(5.0f);
314  DlPaint thin_paint = DlPaint()
315  .setColor(DlColor::kMagenta())
316  .setStrokeCap(cap)
317  .setStrokeWidth(2.0f);
318  for (int degrees = 0; degrees < 360; degrees += 30) {
319  Point delta = Point(1.0f, 0.0f).Rotate(Degrees(degrees));
320  canvas->DrawDashedLine(center + inner * delta, center + outer * delta,
321  dash_on, dash_off, thick_paint);
322  canvas->DrawDashedLine(center + inner * delta, center + outer * delta,
323  dash_on, dash_off, middle_paint);
324  canvas->DrawDashedLine(center + inner * delta, center + outer * delta,
325  dash_on, dash_off, thin_paint);
326  }
327  };
328 
329  draw_one(DlStrokeCap::kButt, 150.0f, 150.0f, 15.0f, 10.0f);
330  draw_one(DlStrokeCap::kSquare, 400.0f, 150.0f, 15.0f, 10.0f);
331  draw_one(DlStrokeCap::kRound, 150.0f, 400.0f, 15.0f, 10.0f);
332  draw_one(DlStrokeCap::kRound, 400.0f, 400.0f, 0.0f, 11.0f);
333 
334  // Make sure the rendering op responds appropriately to clipping
335  canvas->Save();
336  DlPathBuilder path_builder;
337  path_builder.MoveTo(DlPoint(275.0f, 225.0f));
338  path_builder.LineTo(DlPoint(325.0f, 275.0f));
339  path_builder.LineTo(DlPoint(275.0f, 325.0f));
340  path_builder.LineTo(DlPoint(225.0f, 275.0f));
341  canvas->ClipPath(path_builder.TakePath());
342  canvas->DrawColor(DlColor::kYellow());
343  draw_one(DlStrokeCap::kRound, 275.0f, 275.0f, 15.0f, 10.0f);
344  canvas->Restore();
345  };
346 
347  DisplayListBuilder builder;
348  std::vector<sk_sp<DlImage>> images;
349  draw(&builder, images);
350 
351  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
352 }
353 
354 TEST_P(DlGoldenTest, SaveLayerAtFractionalValue) {
355  // Draws a stroked rounded rect at a fractional pixel value. The coverage must
356  // be adjusted so that we still have room to draw it, even though it lies on
357  // the fractional bounds of the saveLayer.
358  DisplayListBuilder builder;
359  builder.DrawPaint(DlPaint().setColor(DlColor::kWhite()));
360  auto save_paint = DlPaint().setAlpha(100);
361  builder.SaveLayer(std::nullopt, &save_paint);
362 
363  builder.DrawRoundRect(DlRoundRect::MakeRectRadius(
364  DlRect::MakeLTRB(10.5, 10.5, 200.5, 200.5), 10),
365  DlPaint()
366  .setDrawStyle(DlDrawStyle::kStroke)
367  .setStrokeWidth(1.5)
368  .setColor(DlColor::kBlack()));
369  builder.DrawCircle(DlPoint::MakeXY(100, 100), 50.5,
370  DlPaint().setColor(DlColor::kAqua()));
371  builder.DrawCircle(DlPoint::MakeXY(110, 110), 50.5,
372  DlPaint().setColor(DlColor::kCyan()));
373 
374  builder.Restore();
375 
376  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
377 }
378 
379 namespace {
380 int32_t CalculateMaxY(const impeller::testing::Screenshot* img) {
381  const uint32_t* ptr = reinterpret_cast<const uint32_t*>(img->GetBytes());
382  int32_t max_y = 0;
383  for (uint32_t i = 0; i < img->GetHeight(); ++i) {
384  for (uint32_t j = 0; j < img->GetWidth(); ++j) {
385  uint32_t pixel = *ptr++;
386  if ((pixel & 0x00ffffff) != 0) {
387  max_y = std::max(max_y, static_cast<int32_t>(i));
388  }
389  }
390  }
391  return max_y;
392 }
393 
394 int32_t CalculateSpaceBetweenUI(const impeller::testing::Screenshot* img) {
395  const uint32_t* ptr = reinterpret_cast<const uint32_t*>(img->GetBytes());
396  ptr += img->GetWidth() * static_cast<int32_t>(img->GetHeight() / 2.0);
397  std::vector<size_t> boundaries;
398  uint32_t value = *ptr++;
399  for (size_t i = 1; i < img->GetWidth(); ++i) {
400  if (((*ptr & 0x00ffffff) != 0) != ((value & 0x00ffffff) != 0)) {
401  boundaries.push_back(i);
402  }
403  value = *ptr++;
404  }
405 
406  assert(boundaries.size() == 6);
407  return boundaries[4] - boundaries[3];
408 }
409 } // namespace
410 
411 TEST_P(DlGoldenTest, BaselineHE) {
412  SetWindowSize(impeller::ISize(1024, 200));
414  auto callback = [&](const char* text,
415  impeller::Scalar scale) -> sk_sp<DisplayList> {
416  DisplayListBuilder builder;
417  DlPaint paint;
418  paint.setColor(DlColor::ARGB(1, 0, 0, 0));
419  builder.DrawPaint(paint);
420  builder.Scale(scale, scale);
421  RenderTextInCanvasSkia(&builder, text, "Roboto-Regular.ttf",
422  DlPoint::MakeXY(10, 300),
423  TextRenderOptions{
424  .font_size = font_size,
425  });
426  return builder.Build();
427  };
428 
429  std::unique_ptr<impeller::testing::Screenshot> right =
430  MakeScreenshot(callback("h", 0.444));
431  if (!right) {
432  GTEST_SKIP() << "making screenshots not supported.";
433  }
434  std::unique_ptr<impeller::testing::Screenshot> left =
435  MakeScreenshot(callback("e", 0.444));
436 
437  int32_t left_max_y = CalculateMaxY(left.get());
438  int32_t right_max_y = CalculateMaxY(right.get());
439  int32_t y_diff = std::abs(left_max_y - right_max_y);
440  EXPECT_TRUE(y_diff <= 2) << "y diff: " << y_diff;
441 }
442 
443 TEST_P(DlGoldenTest, MaintainsSpace) {
444  SetWindowSize(impeller::ISize(1024, 200));
446  auto callback = [&](const char* text,
447  impeller::Scalar scale) -> sk_sp<DisplayList> {
448  DisplayListBuilder builder;
449  DlPaint paint;
450  paint.setColor(DlColor::ARGB(1, 0, 0, 0));
451  builder.DrawPaint(paint);
452  builder.Scale(scale, scale);
453  RenderTextInCanvasSkia(&builder, text, "Roboto-Regular.ttf",
454  DlPoint::MakeXY(10, 300),
455  TextRenderOptions{
456  .font_size = font_size,
457  });
458  return builder.Build();
459  };
460 
461  std::optional<int32_t> last_space;
462  for (int i = 0; i <= 100; ++i) {
463  Scalar scale = 0.440 + i / 1000.0;
464  std::unique_ptr<impeller::testing::Screenshot> right =
465  MakeScreenshot(callback("ui", scale));
466  if (!right) {
467  GTEST_SKIP() << "making screenshots not supported.";
468  }
469 
470  int32_t space = CalculateSpaceBetweenUI(right.get());
471  if (last_space.has_value()) {
472  int32_t diff = abs(space - *last_space);
473  EXPECT_TRUE(diff <= 1)
474  << "i:" << i << " space:" << space << " last_space:" << *last_space;
475  }
476  last_space = space;
477  }
478 }
479 
480 namespace {
481 struct LeftmostIntensity {
482  int32_t x;
483  int32_t value;
484 };
485 
486 /// Returns the highest value in the green channel for leftmost column that
487 /// isn't all black.
488 LeftmostIntensity CalculateLeftmostIntensity(
489  const impeller::testing::Screenshot* img) {
490  LeftmostIntensity result = {.x = static_cast<int32_t>(img->GetWidth()),
491  .value = 0};
492  const uint32_t* ptr = reinterpret_cast<const uint32_t*>(img->GetBytes());
493  for (size_t i = 0; i < img->GetHeight(); ++i) {
494  for (int32_t j = 0; j < static_cast<int32_t>(img->GetWidth()); ++j) {
495  if (((*ptr & 0x00ffffff) != 0)) {
496  if (j < result.x) {
497  result.x = j;
498  result.value = (*ptr & 0xff00) >> 8;
499  } else if (j == result.x) {
500  result.value =
501  std::max(static_cast<int32_t>(*ptr & 0xff), result.value);
502  }
503  }
504  ptr++;
505  }
506  }
507  return result;
508 }
509 } // namespace
510 
511 // Checks that the left most edge of the glyph is fading out as we push
512 // it to the right by fractional pixels.
513 TEST_P(DlGoldenTest, Subpixel) {
514  SetWindowSize(impeller::ISize(1024, 200));
516  auto callback = [&](Scalar offset_x) -> sk_sp<DisplayList> {
517  DisplayListBuilder builder;
518  DlPaint paint;
519  paint.setColor(DlColor::ARGB(1, 0, 0, 0));
520  builder.DrawPaint(paint);
521  RenderTextInCanvasSkia(&builder, "ui", "Roboto-Regular.ttf",
522  DlPoint::MakeXY(offset_x, 180),
523  TextRenderOptions{
524  .font_size = font_size,
525  .is_subpixel = true,
526  });
527  return builder.Build();
528  };
529 
530  LeftmostIntensity intensity[5];
531  for (int i = 0; i <= 4; ++i) {
532  Scalar offset = 10 + (i / 4.0);
533  std::unique_ptr<impeller::testing::Screenshot> right =
534  MakeScreenshot(callback(offset));
535  if (!right) {
536  GTEST_SKIP() << "making screenshots not supported.";
537  }
538  intensity[i] = CalculateLeftmostIntensity(right.get());
539  ASSERT_NE(intensity[i].value, 0);
540  }
541  for (int i = 1; i < 5; ++i) {
542  EXPECT_TRUE(intensity[i].x - intensity[i - 1].x == 1 ||
543  intensity[i].value < intensity[i - 1].value)
544  << i;
545  }
546  EXPECT_EQ(intensity[4].x - intensity[0].x, 1);
547 }
548 
549 // Checks that the left most edge of the glyph is fading out as we push
550 // it to the right by fractional pixels.
551 TEST_P(DlGoldenTest, SubpixelScaled) {
552  SetWindowSize(impeller::ISize(1024, 200));
554  Scalar scalar = 0.75;
555  auto callback = [&](Scalar offset_x) -> sk_sp<DisplayList> {
556  DisplayListBuilder builder;
557  builder.Scale(scalar, scalar);
558  DlPaint paint;
559  paint.setColor(DlColor::ARGB(1, 0, 0, 0));
560  builder.DrawPaint(paint);
561  RenderTextInCanvasSkia(&builder, "ui", "Roboto-Regular.ttf",
562  DlPoint::MakeXY(offset_x, 180),
563  TextRenderOptions{
564  .font_size = font_size,
565  .is_subpixel = true,
566  });
567  return builder.Build();
568  };
569 
570  LeftmostIntensity intensity[5];
571  Scalar offset_fraction = 0.25 / scalar;
572  for (int i = 0; i <= 4; ++i) {
573  Scalar offset = 10 + (offset_fraction * i);
574  std::unique_ptr<impeller::testing::Screenshot> right =
575  MakeScreenshot(callback(offset));
576  if (!right) {
577  GTEST_SKIP() << "making screenshots not supported.";
578  }
579  intensity[i] = CalculateLeftmostIntensity(right.get());
580  ASSERT_NE(intensity[i].value, 0);
581  }
582  for (int i = 1; i < 5; ++i) {
583  EXPECT_TRUE(intensity[i].x - intensity[i - 1].x == 1 ||
584  intensity[i].value < intensity[i - 1].value)
585  << i;
586  }
587  EXPECT_EQ(intensity[4].x - intensity[0].x, 1);
588 }
589 
590 // Checks that the left most edge of the glyph is fading out as we push
591 // it to the right by fractional pixels.
592 TEST_P(DlGoldenTest, SubpixelScaledTranslated) {
593  SetWindowSize(impeller::ISize(1024, 200));
595  Scalar scalar = 0.75;
596  auto callback = [&](Scalar offset_x) -> sk_sp<DisplayList> {
597  DisplayListBuilder builder;
598  builder.Scale(scalar, scalar);
599  DlPaint paint;
600  paint.setColor(DlColor::ARGB(1, 0, 0, 0));
601  builder.DrawPaint(paint);
602  builder.Translate(offset_x, 180);
603  RenderTextInCanvasSkia(&builder, "ui", "Roboto-Regular.ttf",
604  DlPoint::MakeXY(0, 0),
605  TextRenderOptions{
606  .font_size = font_size,
607  .is_subpixel = true,
608  });
609  return builder.Build();
610  };
611 
612  LeftmostIntensity intensity[5];
613  Scalar offset_fraction = 0.25 / scalar;
614  for (int i = 0; i <= 4; ++i) {
615  Scalar offset = 10 + (offset_fraction * i);
616  std::unique_ptr<impeller::testing::Screenshot> right =
617  MakeScreenshot(callback(offset));
618  if (!right) {
619  GTEST_SKIP() << "making screenshots not supported.";
620  }
621  intensity[i] = CalculateLeftmostIntensity(right.get());
622  ASSERT_NE(intensity[i].value, 0);
623  }
624  for (int i = 1; i < 5; ++i) {
625  EXPECT_TRUE(intensity[i].x - intensity[i - 1].x == 1 ||
626  intensity[i].value < intensity[i - 1].value)
627  << i;
628  }
629  EXPECT_EQ(intensity[4].x - intensity[0].x, 1);
630 }
631 
632 } // namespace testing
633 } // namespace flutter
virtual size_t GetHeight() const =0
Returns the height of the image in pixels.
virtual const uint8_t * GetBytes() const =0
Access raw data of the screenshot.
virtual size_t GetWidth() const =0
Returns the width of the image in pixels.
int32_t value
int32_t x
Vector2 blur_radius
Blur radius in source pixels based on scaled_sigma.
TEST_P(DlGoldenTest, TextBlurMaskFilterRespectCTM)
INSTANTIATE_PLAYGROUND_SUITE(DlGoldenTest)
bool RenderTextInCanvasSkia(const std::shared_ptr< Context > &context, DisplayListBuilder &canvas, const std::string &text, const std::string_view &font_fixture, const TextRenderOptions &options={}, const std::optional< SkFont > &font=std::nullopt)
flutter::DlRect DlRect
Definition: dl_dispatcher.h:30
float Scalar
Definition: scalar.h:19
TPoint< Scalar > Point
Definition: point.h:426
flutter::DlPoint DlPoint
Definition: dl_dispatcher.h:29
flutter::DlPath DlPath
Definition: dl_dispatcher.h:34
PlaygroundBackend
Definition: playground.h:27
flutter::DlScalar DlScalar
Definition: dl_dispatcher.h:28
constexpr TPoint Rotate(const Radians &angle) const
Definition: point.h:315
Scalar font_size