Flutter Impeller
dl_golden_blur_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 "flutter/display_list/dl_builder.h"
8 #include "flutter/display_list/effects/dl_mask_filter.h"
10 #include "flutter/testing/testing.h"
11 #include "gtest/gtest.h"
13 #include "txt/platform.h"
14 
15 namespace flutter {
16 namespace testing {
17 
18 using impeller::Font;
19 
20 namespace {
21 struct TextRenderOptions {
22  bool stroke = false;
23  SkScalar font_size = 50;
24  DlColor color = DlColor::kYellow();
25  std::shared_ptr<DlMaskFilter> mask_filter;
26 };
27 
28 bool RenderTextInCanvasSkia(DlCanvas* canvas,
29  const std::string& text,
30  const std::string_view& font_fixture,
31  SkPoint position,
32  const TextRenderOptions& options = {}) {
33  auto c_font_fixture = std::string(font_fixture);
34  auto mapping = flutter::testing::OpenFixtureAsSkData(c_font_fixture.c_str());
35  if (!mapping) {
36  return false;
37  }
38  sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
39  SkFont sk_font(font_mgr->makeFromData(mapping), options.font_size);
40  auto blob = SkTextBlob::MakeFromString(text.c_str(), sk_font);
41  if (!blob) {
42  return false;
43  }
44 
45  auto frame = impeller::MakeTextFrameFromTextBlobSkia(blob);
46 
47  DlPaint text_paint;
48  text_paint.setColor(options.color);
49  text_paint.setMaskFilter(options.mask_filter);
50  // text_paint.mask_blur_descriptor = options.mask_blur_descriptor;
51  // text_paint.stroke_width = 1;
52  // text_paint.style =
53  // options.stroke ? Paint::Style::kStroke : Paint::Style::kFill;
54  canvas->DrawTextFrame(frame, position.x(), position.y(), text_paint);
55  return true;
56 }
57 
58 } // namespace
59 
60 TEST_P(DlGoldenTest, TextBlurMaskFilterRespectCTM) {
61  impeller::Point content_scale = GetContentScale();
62  auto draw = [&](DlCanvas* canvas,
63  const std::vector<std::unique_ptr<DlImage>>& images) {
64  canvas->DrawColor(DlColor(0xff111111));
65  canvas->Scale(content_scale.x, content_scale.y);
66  canvas->Scale(2, 2);
67  TextRenderOptions options;
68  options.mask_filter =
69  DlBlurMaskFilter::Make(DlBlurStyle::kNormal, /*sigma=*/10,
70  /*respect_ctm=*/true);
71  ASSERT_TRUE(RenderTextInCanvasSkia(canvas, "hello world",
72  "Roboto-Regular.ttf",
73  SkPoint::Make(101, 101), options));
74  options.mask_filter = nullptr;
75  options.color = DlColor::kRed();
76  ASSERT_TRUE(RenderTextInCanvasSkia(canvas, "hello world",
77  "Roboto-Regular.ttf",
78  SkPoint::Make(100, 100), options));
79  };
80 
81  DisplayListBuilder builder;
82  draw(&builder, /*images=*/{});
83 
84  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
85 }
86 
87 TEST_P(DlGoldenTest, TextBlurMaskFilterDisrespectCTM) {
88  impeller::Point content_scale = GetContentScale();
89  auto draw = [&](DlCanvas* canvas,
90  const std::vector<std::unique_ptr<DlImage>>& images) {
91  canvas->DrawColor(DlColor(0xff111111));
92  canvas->Scale(content_scale.x, content_scale.y);
93  canvas->Scale(2, 2);
94  TextRenderOptions options;
95  options.mask_filter =
96  DlBlurMaskFilter::Make(DlBlurStyle::kNormal, /*sigma=*/10,
97  /*respect_ctm=*/false);
98  ASSERT_TRUE(RenderTextInCanvasSkia(canvas, "hello world",
99  "Roboto-Regular.ttf",
100  SkPoint::Make(101, 101), options));
101  options.mask_filter = nullptr;
102  options.color = DlColor::kRed();
103  ASSERT_TRUE(RenderTextInCanvasSkia(canvas, "hello world",
104  "Roboto-Regular.ttf",
105  SkPoint::Make(100, 100), options));
106  };
107 
108  DisplayListBuilder builder;
109  draw(&builder, /*images=*/{});
110 
111  ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
112 }
113 
114 namespace {
115 double CalculateDistance(const uint8_t* left, const uint8_t* right) {
116  double diff[4] = {
117  static_cast<double>(left[0]) - right[0], //
118  static_cast<double>(left[1]) - right[1], //
119  static_cast<double>(left[2]) - right[2], //
120  static_cast<double>(left[3]) - right[3] //
121  };
122  return sqrt((diff[0] * diff[0]) + //
123  (diff[1] * diff[1]) + //
124  (diff[2] * diff[2]) + //
125  (diff[3] * diff[3]));
126 }
127 
128 double RMSE(const impeller::testing::Screenshot* left,
129  const impeller::testing::Screenshot* right) {
130  FML_CHECK(left);
131  FML_CHECK(right);
132  FML_CHECK(left->GetWidth() == right->GetWidth());
133  FML_CHECK(left->GetHeight() == right->GetHeight());
134 
135  int64_t samples = left->GetWidth() * left->GetHeight();
136  double tally = 0;
137 
138  const uint8_t* left_ptr = left->GetBytes();
139  const uint8_t* right_ptr = right->GetBytes();
140  for (int64_t i = 0; i < samples; ++i, left_ptr += 4, right_ptr += 4) {
141  double distance = CalculateDistance(left_ptr, right_ptr);
142  tally += distance * distance;
143  }
144 
145  return sqrt(tally / static_cast<double>(samples));
146 }
147 } // namespace
148 
149 // This is a test to make sure that we don't regress "shimmering" in the
150 // gaussian blur. Shimmering is abrupt changes in signal when making tiny
151 // changes to the blur parameters.
152 //
153 // See also:
154 // - https://github.com/flutter/flutter/issues/152195
155 TEST_P(DlGoldenTest, ShimmerTest) {
156  impeller::Point content_scale = GetContentScale();
157  auto draw = [&](DlCanvas* canvas, const std::vector<sk_sp<DlImage>>& images,
158  float sigma) {
159  canvas->DrawColor(DlColor(0xff111111));
160  canvas->Scale(content_scale.x, content_scale.y);
161 
162  DlPaint paint;
163  canvas->DrawImage(images[0], SkPoint::Make(10.135, 10.36334),
164  DlImageSampling::kLinear, &paint);
165 
166  SkRect save_layer_bounds = SkRect::MakeLTRB(0, 0, 1024, 768);
167  DlBlurImageFilter blur(sigma, sigma, DlTileMode::kDecal);
168  canvas->ClipRect(SkRect::MakeLTRB(11.125, 10.3737, 911.25, 755.3333));
169  canvas->SaveLayer(&save_layer_bounds, /*paint=*/nullptr, &blur);
170  canvas->Restore();
171  };
172 
173  std::vector<sk_sp<DlImage>> images;
174  images.emplace_back(CreateDlImageForFixture("boston.jpg"));
175 
176  auto make_screenshot = [&](float sigma) {
177  DisplayListBuilder builder;
178  draw(&builder, images, sigma);
179 
180  std::unique_ptr<impeller::testing::Screenshot> screenshot =
181  MakeScreenshot(builder.Build());
182  return screenshot;
183  };
184 
185  float start_sigma = 10.0f;
186  std::unique_ptr<impeller::testing::Screenshot> left =
187  make_screenshot(start_sigma);
188  if (!left) {
189  GTEST_SKIP() << "making screenshots not supported.";
190  }
191 
192  double average_rmse = 0.0;
193  const int32_t sample_count = 200;
194  for (int i = 1; i <= sample_count; ++i) {
195  float sigma = start_sigma + (i / 2.f);
196  std::unique_ptr<impeller::testing::Screenshot> right =
197  make_screenshot(sigma);
198  double rmse = RMSE(left.get(), right.get());
199  average_rmse += rmse;
200 
201  // To debug this output the frames can be written out to disk then
202  // transformed to a video with ffmpeg.
203  //
204  // ## save images command
205  // std::stringstream ss;
206  // ss << "_" << std::setw(3) << std::setfill('0') << (i - 1);
207  // SaveScreenshot(std::move(left), ss.str());
208  //
209  // ## ffmpeg command
210  // ```
211  // ffmpeg -framerate 30 -pattern_type glob -i '*.png' \
212  // -c:v libx264 -pix_fmt yuv420p out.mp4
213  // ```
214  left = std::move(right);
215  }
216 
217  average_rmse = average_rmse / sample_count;
218 
219  // This is a somewhat arbitrary threshold. It could be increased if we wanted.
220  // In the problematic cases previously we should values like 28. Before
221  // increasing this you should manually inspect the behavior in
222  // `AiksTest.GaussianBlurAnimatedBackdrop`. Average RMSE is a able to catch
223  // shimmer but it isn't perfect.
224  EXPECT_TRUE(average_rmse < 1.0) << "average_rmse: " << average_rmse;
225  // An average rmse of 0 would mean that the blur isn't blurring.
226  EXPECT_TRUE(average_rmse >= 0.0) << "average_rmse: " << average_rmse;
227 }
228 
229 } // namespace testing
230 } // namespace flutter
impeller::testing::RenderTextInCanvasSkia
bool RenderTextInCanvasSkia(const std::shared_ptr< Context > &context, DisplayListBuilder &canvas, const std::string &text, const std::string_view &font_fixture, const TextRenderOptions &options={})
Definition: aiks_dl_text_unittests.cc:41
impeller::testing::Screenshot::GetWidth
virtual size_t GetWidth() const =0
Returns the width of the image in pixels.
impeller::TPoint::y
Type y
Definition: point.h:31
impeller::Font
Describes a typeface along with any modifications to its intrinsic properties.
Definition: font.h:35
font_size
SkScalar font_size
Definition: dl_golden_blur_unittests.cc:23
impeller::testing::Screenshot
Definition: screenshot.h:16
screenshot.h
flutter::testing::TEST_P
TEST_P(DlGoldenTest, TextBlurMaskFilterRespectCTM)
Definition: dl_golden_blur_unittests.cc:60
impeller::saturated::distance
SI distance
Definition: saturated_math.h:57
text_frame_skia.h
dl_golden_unittests.h
flutter
Definition: dl_golden_blur_unittests.cc:15
impeller::MakeTextFrameFromTextBlobSkia
std::shared_ptr< TextFrame > MakeTextFrameFromTextBlobSkia(const sk_sp< SkTextBlob > &blob)
Definition: text_frame_skia.cc:42
impeller::TPoint::x
Type x
Definition: point.h:30
impeller::testing::Screenshot::GetBytes
virtual const uint8_t * GetBytes() const =0
Access raw data of the screenshot.
mask_filter
std::shared_ptr< DlMaskFilter > mask_filter
Definition: dl_golden_blur_unittests.cc:25
impeller::TPoint< Scalar >
impeller::testing::Screenshot::GetHeight
virtual size_t GetHeight() const =0
Returns the height of the image in pixels.
color
DlColor color
Definition: dl_golden_blur_unittests.cc:24
stroke
bool stroke
Definition: dl_golden_blur_unittests.cc:22
impeller::DlPlayground
Definition: dl_playground.h:16