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