| 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
5 #include "impeller/display_list/dl_golden_unittests.h"
6
7 #include "flutter/display_list/dl_builder.h"
8 #include "flutter/display_list/effects/dl_mask_filter.h"
9 #include "flutter/impeller/golden_tests/screenshot.h"
10 #include "flutter/testing/testing.h"
11 #include "gtest/gtest.h"
12 #include "impeller/typographer/backends/skia/text_frame_skia.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