Flutter Impeller
gaussian_blur_filter_contents_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/testing/testing.h"
6 #include "fml/status_or.h"
7 #include "gmock/gmock.h"
14 #include "impeller/renderer/testing/mocks.h"
15 
16 #if FML_OS_MACOSX
17 #define IMPELLER_RAND arc4random
18 #else
19 #define IMPELLER_RAND rand
20 #endif
21 
22 namespace impeller {
23 namespace testing {
24 
25 namespace {
26 
27 // Use newtonian method to give the closest answer to target where
28 // f(x) is less than the target. We do this because the value is `ceil`'d to
29 // grab fractional pixels.
30 fml::StatusOr<float> LowerBoundNewtonianMethod(
31  const std::function<float(float)>& func,
32  float target,
33  float guess,
34  float tolerance) {
35  const double delta = 1e-6;
36  double x = guess;
37  double fx;
38  static const int kMaxIterations = 1000;
39  int count = 0;
40 
41  do {
42  fx = func(x) - target;
43  double derivative = (func(x + delta) - func(x)) / delta;
44  x = x - fx / derivative;
45  if (++count > kMaxIterations) {
46  return fml::Status(fml::StatusCode::kDeadlineExceeded,
47  "Did not converge on answer.");
48  }
49  } while (std::abs(fx) > tolerance ||
50  fx < 0.0); // fx < 0.0 makes this lower bound.
51 
52  return x;
53 }
54 
55 Scalar GetCoefficient(const Vector4& vec) {
56  return vec.z;
57 }
58 
59 Vector2 GetUVOffset(const Vector4& vec) {
60  return vec.xy();
61 }
62 
63 fml::StatusOr<Scalar> CalculateSigmaForBlurRadius(
64  Scalar radius,
65  const Matrix& effect_transform) {
66  auto f = [effect_transform](Scalar x) -> Scalar {
67  Vector2 scaled_sigma = (effect_transform.Basis() *
70  .Abs();
74  return std::max(blur_radius.x, blur_radius.y);
75  };
76  // The newtonian method is used here since inverting the function is
77  // non-trivial because of conditional logic and would be fragile to changes.
78  return LowerBoundNewtonianMethod(f, radius, 2.f, 0.001f);
79 }
80 
81 } // namespace
82 
84  public:
85  /// Create a texture that has been cleared to transparent black.
86  std::shared_ptr<Texture> MakeTexture(ISize size) {
87  std::shared_ptr<CommandBuffer> command_buffer =
88  GetContentContext()->GetContext()->CreateCommandBuffer();
89  if (!command_buffer) {
90  return nullptr;
91  }
92 
93  auto render_target = GetContentContext()->MakeSubpass(
94  "Clear Subpass", size, command_buffer,
95  [](const ContentContext&, RenderPass&) { return true; });
96 
97  if (!GetContentContext()
98  ->GetContext()
99  ->GetCommandQueue()
100  ->Submit(/*buffers=*/{command_buffer})
101  .ok()) {
102  return nullptr;
103  }
104 
105  if (render_target.ok()) {
106  return render_target.value().GetRenderTargetTexture();
107  }
108  return nullptr;
109  }
110 };
112 
115  /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal,
116  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
117  /*mask_geometry=*/nullptr);
118  EXPECT_EQ(contents.GetSigmaX(), 0.0);
119  EXPECT_EQ(contents.GetSigmaY(), 0.0);
120 }
121 
124  /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal,
125  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
126  /*mask_geometry=*/nullptr);
127  FilterInput::Vector inputs = {};
128  Entity entity;
129  std::optional<Rect> coverage =
130  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
131  ASSERT_FALSE(coverage.has_value());
132 }
133 
136  /*sigma_x=*/0.0, /*sigma_y=*/0.0, Entity::TileMode::kDecal,
137  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
138  /*mask_geometry=*/nullptr);
139  FilterInput::Vector inputs = {
140  FilterInput::Make(Rect::MakeLTRB(10, 10, 110, 110))};
141  Entity entity;
142  std::optional<Rect> coverage =
143  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
144 
145  ASSERT_EQ(coverage, Rect::MakeLTRB(10, 10, 110, 110));
146 }
147 
149  fml::StatusOr<Scalar> sigma_radius_1 =
150  CalculateSigmaForBlurRadius(1.0, Matrix());
151  ASSERT_TRUE(sigma_radius_1.ok());
153  /*sigma_x=*/sigma_radius_1.value(),
154  /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal,
155  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
156  /*mask_geometry=*/nullptr);
157  FilterInput::Vector inputs = {
158  FilterInput::Make(Rect::MakeLTRB(100, 100, 200, 200))};
159  Entity entity;
160  std::optional<Rect> coverage =
161  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
162 
163  EXPECT_TRUE(coverage.has_value());
164  if (coverage.has_value()) {
165  EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201));
166  }
167 }
168 
169 TEST_P(GaussianBlurFilterContentsTest, CoverageWithTexture) {
170  fml::StatusOr<Scalar> sigma_radius_1 =
171  CalculateSigmaForBlurRadius(1.0, Matrix());
172  ASSERT_TRUE(sigma_radius_1.ok());
174  /*sigma_X=*/sigma_radius_1.value(),
175  /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal,
176  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
177  /*mask_geometry=*/nullptr);
178  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
179  FilterInput::Vector inputs = {FilterInput::Make(texture)};
180  Entity entity;
181  entity.SetTransform(Matrix::MakeTranslation({100, 100, 0}));
182  std::optional<Rect> coverage =
183  contents.GetFilterCoverage(inputs, entity, /*effect_transform=*/Matrix());
184 
185  EXPECT_TRUE(coverage.has_value());
186  if (coverage.has_value()) {
187  EXPECT_RECT_NEAR(coverage.value(), Rect::MakeLTRB(99, 99, 201, 201));
188  }
189 }
190 
191 TEST_P(GaussianBlurFilterContentsTest, CoverageWithEffectTransform) {
192  Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0});
193  fml::StatusOr<Scalar> sigma_radius_1 =
194  CalculateSigmaForBlurRadius(1.0, effect_transform);
195  ASSERT_TRUE(sigma_radius_1.ok());
197  /*sigma_x=*/sigma_radius_1.value(),
198  /*sigma_y=*/sigma_radius_1.value(), Entity::TileMode::kDecal,
199  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
200  /*mask_geometry=*/nullptr);
201  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
202  FilterInput::Vector inputs = {FilterInput::Make(texture)};
203  Entity entity;
204  entity.SetTransform(Matrix::MakeTranslation({100, 100, 0}));
205  std::optional<Rect> coverage =
206  contents.GetFilterCoverage(inputs, entity, effect_transform);
207  EXPECT_TRUE(coverage.has_value());
208  if (coverage.has_value()) {
209  EXPECT_RECT_NEAR(coverage.value(),
210  Rect::MakeLTRB(100 - 1, 100 - 1, 200 + 1, 200 + 1));
211  }
212 }
213 
214 TEST(GaussianBlurFilterContentsTest, FilterSourceCoverage) {
215  fml::StatusOr<Scalar> sigma_radius_1 =
216  CalculateSigmaForBlurRadius(1.0, Matrix());
217  ASSERT_TRUE(sigma_radius_1.ok());
218  auto contents = std::make_unique<GaussianBlurFilterContents>(
219  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
220  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
221  /*mask_geometry=*/nullptr);
222  std::optional<Rect> coverage = contents->GetFilterSourceCoverage(
223  /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}),
224  /*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200));
225  EXPECT_TRUE(coverage.has_value());
226  if (coverage.has_value()) {
227  EXPECT_RECT_NEAR(coverage.value(),
228  Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2));
229  }
230 }
231 
232 TEST(GaussianBlurFilterContentsTest, FilterSourceCoverageNegativeScale) {
233  fml::StatusOr<Scalar> sigma_radius_1 =
234  CalculateSigmaForBlurRadius(1.0, Matrix());
235  ASSERT_TRUE(sigma_radius_1.ok());
236  auto contents = std::make_unique<GaussianBlurFilterContents>(
237  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
238  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
239  /*mask_geometry=*/nullptr);
240 
241  // Negative scale should still result in an expanded coverage rect.
242  std::optional<Rect> coverage = contents->GetFilterSourceCoverage(
243  /*effect_transform=*/Matrix::MakeScale({-2.0, 2.0, 1.0}),
244  /*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200));
245  ASSERT_TRUE(coverage.has_value());
246  if (coverage.has_value()) {
247  EXPECT_RECT_NEAR(coverage.value(),
248  Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2));
249  }
250 }
251 
252 TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) {
253  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1.0f), 1);
254  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(2.0f), 1);
255  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(3.0f), 1);
256  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(4.0f), 1);
257  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(16.0f), 0.25);
258  // Hang on to 1/8 as long as possible.
259  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(95.0f), 0.125);
260  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(96.0f), 0.0625);
261  // Downsample clamped to 1/16th.
262  EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1024.0f), 0.0625);
263 }
264 
265 TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) {
266  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
267  fml::StatusOr<Scalar> sigma_radius_1 =
268  CalculateSigmaForBlurRadius(1.0, Matrix());
269  ASSERT_TRUE(sigma_radius_1.ok());
270  auto contents = std::make_unique<GaussianBlurFilterContents>(
271  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
272  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
273  /*mask_geometry=*/nullptr);
274  contents->SetInputs({FilterInput::Make(texture)});
275  std::shared_ptr<ContentContext> renderer = GetContentContext();
276 
277  Entity entity;
278  std::optional<Entity> result =
279  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
280  EXPECT_TRUE(result.has_value());
281  if (result.has_value()) {
282  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
283  std::optional<Rect> result_coverage = result.value().GetCoverage();
284  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
285  EXPECT_TRUE(result_coverage.has_value());
286  EXPECT_TRUE(contents_coverage.has_value());
287  if (result_coverage.has_value() && contents_coverage.has_value()) {
288  EXPECT_TRUE(RectNear(contents_coverage.value(),
289  Rect::MakeLTRB(-1, -1, 101, 101)));
290  EXPECT_TRUE(
291  RectNear(result_coverage.value(), Rect::MakeLTRB(-1, -1, 101, 101)));
292  }
293  }
294 }
295 
297  RenderCoverageMatchesGetCoverageTranslate) {
298  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
299  fml::StatusOr<Scalar> sigma_radius_1 =
300  CalculateSigmaForBlurRadius(1.0, Matrix());
301  ASSERT_TRUE(sigma_radius_1.ok());
302  auto contents = std::make_unique<GaussianBlurFilterContents>(
303  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
304  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
305  /*mask_geometry=*/nullptr);
306  contents->SetInputs({FilterInput::Make(texture)});
307  std::shared_ptr<ContentContext> renderer = GetContentContext();
308 
309  Entity entity;
310  entity.SetTransform(Matrix::MakeTranslation({100, 200, 0}));
311  std::optional<Entity> result =
312  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
313 
314  EXPECT_TRUE(result.has_value());
315  if (result.has_value()) {
316  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
317  std::optional<Rect> result_coverage = result.value().GetCoverage();
318  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
319  EXPECT_TRUE(result_coverage.has_value());
320  EXPECT_TRUE(contents_coverage.has_value());
321  if (result_coverage.has_value() && contents_coverage.has_value()) {
322  EXPECT_TRUE(RectNear(contents_coverage.value(),
323  Rect::MakeLTRB(99, 199, 201, 301)));
324  EXPECT_TRUE(
325  RectNear(result_coverage.value(), Rect::MakeLTRB(99, 199, 201, 301)));
326  }
327  }
328 }
329 
331  RenderCoverageMatchesGetCoverageRotated) {
332  std::shared_ptr<Texture> texture = MakeTexture(ISize(400, 300));
333  fml::StatusOr<Scalar> sigma_radius_1 =
334  CalculateSigmaForBlurRadius(1.0, Matrix());
335  auto contents = std::make_unique<GaussianBlurFilterContents>(
336  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
337  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
338  /*mask_geometry=*/nullptr);
339  contents->SetInputs({FilterInput::Make(texture)});
340  std::shared_ptr<ContentContext> renderer = GetContentContext();
341 
342  Entity entity;
343  // Rotate around the top left corner, then push it over to (100, 100).
344  entity.SetTransform(Matrix::MakeTranslation({400, 100, 0}) *
346  std::optional<Entity> result =
347  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
348  EXPECT_TRUE(result.has_value());
349  if (result.has_value()) {
350  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
351  std::optional<Rect> result_coverage = result.value().GetCoverage();
352  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
353  EXPECT_TRUE(result_coverage.has_value());
354  EXPECT_TRUE(contents_coverage.has_value());
355  if (result_coverage.has_value() && contents_coverage.has_value()) {
356  EXPECT_TRUE(RectNear(contents_coverage.value(),
357  Rect::MakeLTRB(99, 99, 401, 501)));
358  EXPECT_TRUE(
359  RectNear(result_coverage.value(), Rect::MakeLTRB(99, 99, 401, 501)));
360  }
361  }
362 }
363 
365  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
366  auto filter_input = FilterInput::Make(texture);
367  Entity entity;
369  filter_input, entity, Rect::MakeSize(ISize(100, 100)), ISize(100, 100));
370  std::optional<Rect> uvs_bounds = Rect::MakePointBounds(uvs);
371  EXPECT_TRUE(uvs_bounds.has_value());
372  if (uvs_bounds.has_value()) {
373  EXPECT_TRUE(RectNear(uvs_bounds.value(), Rect::MakeXYWH(0, 0, 1, 1)));
374  }
375 }
376 
377 TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithDestinationRect) {
378  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
379  auto texture_contents = std::make_shared<TextureContents>();
380  texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
381  texture_contents->SetTexture(texture);
382  texture_contents->SetDestinationRect(Rect::MakeXYWH(
383  50, 40, texture->GetSize().width, texture->GetSize().height));
384 
385  fml::StatusOr<Scalar> sigma_radius_1 =
386  CalculateSigmaForBlurRadius(1.0, Matrix());
387  auto contents = std::make_unique<GaussianBlurFilterContents>(
388  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
389  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
390  /*mask_geometry=*/nullptr);
391  contents->SetInputs({FilterInput::Make(texture_contents)});
392  std::shared_ptr<ContentContext> renderer = GetContentContext();
393 
394  Entity entity;
395  std::optional<Entity> result =
396  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
397  EXPECT_TRUE(result.has_value());
398  if (result.has_value()) {
399  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
400  std::optional<Rect> result_coverage = result.value().GetCoverage();
401  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
402  EXPECT_TRUE(result_coverage.has_value());
403  EXPECT_TRUE(contents_coverage.has_value());
404  if (result_coverage.has_value() && contents_coverage.has_value()) {
405  EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
406  EXPECT_TRUE(RectNear(result_coverage.value(),
407  Rect::MakeLTRB(49.f, 39.f, 151.f, 141.f)));
408  }
409  }
410 }
411 
413  TextureContentsWithDestinationRectScaled) {
414  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
415  auto texture_contents = std::make_shared<TextureContents>();
416  texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
417  texture_contents->SetTexture(texture);
418  texture_contents->SetDestinationRect(Rect::MakeXYWH(
419  50, 40, texture->GetSize().width, texture->GetSize().height));
420 
421  fml::StatusOr<Scalar> sigma_radius_1 =
422  CalculateSigmaForBlurRadius(1.0, Matrix());
423  auto contents = std::make_unique<GaussianBlurFilterContents>(
424  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
425  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
426  /*mask_geometry=*/nullptr);
427  contents->SetInputs({FilterInput::Make(texture_contents)});
428  std::shared_ptr<ContentContext> renderer = GetContentContext();
429 
430  Entity entity;
431  entity.SetTransform(Matrix::MakeScale({2.0, 2.0, 1.0}));
432  std::optional<Entity> result =
433  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
434  EXPECT_TRUE(result.has_value());
435  if (result.has_value()) {
436  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
437  std::optional<Rect> result_coverage = result.value().GetCoverage();
438  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
439  EXPECT_TRUE(result_coverage.has_value());
440  EXPECT_TRUE(contents_coverage.has_value());
441  if (result_coverage.has_value() && contents_coverage.has_value()) {
442  EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
443  // Scaling a blurred entity doesn't seem to scale the blur radius linearly
444  // when comparing results with rrect_blur. That's why this is not
445  // Rect::MakeXYWH(98.f, 78.f, 204.0f, 204.f).
446  EXPECT_TRUE(RectNear(contents_coverage.value(),
447  Rect::MakeXYWH(94.f, 74.f, 212.0f, 212.f)));
448  }
449  }
450 }
451 
452 TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithEffectTransform) {
453  Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0});
454  std::shared_ptr<Texture> texture = MakeTexture(ISize(100, 100));
455  auto texture_contents = std::make_shared<TextureContents>();
456  texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
457  texture_contents->SetTexture(texture);
458  texture_contents->SetDestinationRect(Rect::MakeXYWH(
459  50, 40, texture->GetSize().width, texture->GetSize().height));
460 
461  fml::StatusOr<Scalar> sigma_radius_1 =
462  CalculateSigmaForBlurRadius(1.0, effect_transform);
463  ASSERT_TRUE(sigma_radius_1.ok());
464  auto contents = std::make_unique<GaussianBlurFilterContents>(
465  sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
466  /*bounds=*/std::nullopt, FilterContents::BlurStyle::kNormal,
467  /*mask_geometry=*/nullptr);
468  contents->SetInputs({FilterInput::Make(texture_contents)});
469  contents->SetEffectTransform(effect_transform);
470  std::shared_ptr<ContentContext> renderer = GetContentContext();
471 
472  Entity entity;
473  std::optional<Entity> result =
474  contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
475  EXPECT_TRUE(result.has_value());
476  if (result.has_value()) {
477  EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSrcOver);
478  std::optional<Rect> result_coverage = result.value().GetCoverage();
479  std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
480  EXPECT_TRUE(result_coverage.has_value());
481  EXPECT_TRUE(contents_coverage.has_value());
482  if (result_coverage.has_value() && contents_coverage.has_value()) {
483  EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
484  EXPECT_TRUE(RectNear(contents_coverage.value(),
485  Rect::MakeXYWH(49.f, 39.f, 102.f, 102.f)));
486  }
487  }
488 }
489 
490 TEST(GaussianBlurFilterContentsTest, CalculateSigmaForBlurRadius) {
491  Scalar sigma = 1.0;
494  fml::StatusOr<Scalar> derived_sigma =
495  CalculateSigmaForBlurRadius(radius, Matrix());
496  ASSERT_TRUE(derived_sigma.ok());
497  EXPECT_NEAR(sigma, derived_sigma.value(), 0.01f);
498 }
499 
501  BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
502  .blur_sigma = 1,
503  .blur_radius = 5,
504  .step_size = 1};
505  KernelSamples samples = GenerateBlurInfo(parameters);
506  EXPECT_EQ(samples.sample_count, 11);
507 
508  // Coefficients should add up to 1.
509  Scalar tally = 0;
510  for (int i = 0; i < samples.sample_count; ++i) {
511  tally += samples.samples[i].coefficient;
512  }
513  EXPECT_FLOAT_EQ(tally, 1.0f);
514 
515  // Verify the shape of the curve.
516  for (int i = 0; i < 4; ++i) {
517  EXPECT_FLOAT_EQ(samples.samples[i].coefficient,
518  samples.samples[10 - i].coefficient);
519  EXPECT_TRUE(samples.samples[i + 1].coefficient >
520  samples.samples[i].coefficient);
521  }
522 }
523 
524 TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesSimple) {
525  KernelSamples kernel_samples = {
526  .sample_count = 5,
527  .samples =
528  {
529  {
530  .uv_offset = Vector2(-2, 0),
531  .coefficient = 0.1f,
532  },
533  {
534  .uv_offset = Vector2(-1, 0),
535  .coefficient = 0.2f,
536  },
537  {
538  .uv_offset = Vector2(0, 0),
539  .coefficient = 0.4f,
540  },
541  {
542  .uv_offset = Vector2(1, 0),
543  .coefficient = 0.2f,
544  },
545  {
546  .uv_offset = Vector2(2, 0),
547  .coefficient = 0.1f,
548  },
549  },
550  };
551 
552  GaussianBlurPipeline::FragmentShader::KernelSamples blur_info =
553  LerpHackKernelSamples(kernel_samples);
554  EXPECT_EQ(blur_info.sample_count, 3);
555 
556  KernelSample* samples = kernel_samples.samples;
557 
558  //////////////////////////////////////////////////////////////////////////////
559  // Check output kernel.
560 
561  EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[0]),
562  Point(-1.3333333, 0));
563  EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[0]), 0.3);
564 
565  EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[1]), Point(0, 0));
566  EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[1]), 0.4);
567 
568  EXPECT_POINT_NEAR(GetUVOffset(blur_info.sample_data[2]), Point(1.333333, 0));
569  EXPECT_FLOAT_EQ(GetCoefficient(blur_info.sample_data[2]), 0.3);
570 
571  //////////////////////////////////////////////////////////////////////////////
572  // Check output of fast kernel versus original kernel.
573 
574  Scalar data[5] = {0.25, 0.5, 0.5, 1.0, 0.2};
575  Scalar original_output =
576  samples[0].coefficient * data[0] + samples[1].coefficient * data[1] +
577  samples[2].coefficient * data[2] + samples[3].coefficient * data[3] +
578  samples[4].coefficient * data[4];
579 
580  auto lerp = [](const Point& point, Scalar left, Scalar right) {
581  Scalar int_part;
582  Scalar fract = fabsf(modf(point.x, &int_part));
583  if (point.x < 0) {
584  return left * fract + right * (1.0 - fract);
585  } else {
586  return left * (1.0 - fract) + right * fract;
587  }
588  };
589  Scalar fast_output =
590  /*1st*/ lerp(GetUVOffset(blur_info.sample_data[0]), data[0], data[1]) *
591  GetCoefficient(blur_info.sample_data[0]) +
592  /*2nd*/ data[2] * GetCoefficient(blur_info.sample_data[1]) +
593  /*3rd*/ lerp(GetUVOffset(blur_info.sample_data[2]), data[3], data[4]) *
594  GetCoefficient(blur_info.sample_data[2]);
595 
596  EXPECT_NEAR(original_output, fast_output, 0.01);
597 }
598 
599 TEST(GaussianBlurFilterContentsTest, LerpHackKernelSamplesComplex) {
600  Scalar sigma = 10.0f;
601  int32_t blur_radius = static_cast<int32_t>(
603  BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
604  .blur_sigma = sigma,
605  .blur_radius = blur_radius,
606  .step_size = 1};
607  KernelSamples kernel_samples = GenerateBlurInfo(parameters);
608  EXPECT_EQ(kernel_samples.sample_count, 33);
609  GaussianBlurPipeline::FragmentShader::KernelSamples fast_kernel_samples =
610  LerpHackKernelSamples(kernel_samples);
611  EXPECT_EQ(fast_kernel_samples.sample_count, 17);
612  float data[33];
613  srand(0);
614  for (int i = 0; i < 33; i++) {
615  data[i] = 255.0 * static_cast<double>(IMPELLER_RAND()) / RAND_MAX;
616  }
617 
618  auto sampler = [data](Point point) -> Scalar {
619  FML_CHECK(point.y == 0.0f);
620  FML_CHECK(point.x >= -16);
621  FML_CHECK(point.x <= 16);
622  Scalar fint_part;
623  Scalar fract = fabsf(modf(point.x, &fint_part));
624  if (fract == 0) {
625  int32_t int_part = static_cast<int32_t>(fint_part) + 16;
626  return data[int_part];
627  } else {
628  int32_t left = static_cast<int32_t>(floor(point.x)) + 16;
629  int32_t right = static_cast<int32_t>(ceil(point.x)) + 16;
630  if (point.x < 0) {
631  return fract * data[left] + (1.0 - fract) * data[right];
632  } else {
633  return (1.0 - fract) * data[left] + fract * data[right];
634  }
635  }
636  };
637 
638  Scalar output = 0.0;
639  for (int i = 0; i < kernel_samples.sample_count; ++i) {
640  auto sample = kernel_samples.samples[i];
641  output += sample.coefficient * sampler(sample.uv_offset);
642  }
643 
644  Scalar fast_output = 0.0;
645  for (int i = 0; i < fast_kernel_samples.sample_count; i++) {
646  fast_output += GetCoefficient(fast_kernel_samples.sample_data[i]) *
647  sampler(GetUVOffset(fast_kernel_samples.sample_data[i]));
648  }
649 
650  EXPECT_NEAR(output, fast_output, 0.1);
651 }
652 
654  Scalar sigma = 30.5f;
655  int32_t blur_radius = static_cast<int32_t>(
657  BlurParameters parameters = {.blur_uv_offset = Point(1, 0),
658  .blur_sigma = sigma,
659  .blur_radius = blur_radius,
660  .step_size = 1};
661  KernelSamples kernel_samples = GenerateBlurInfo(parameters);
662  GaussianBlurPipeline::FragmentShader::KernelSamples frag_kernel_samples =
663  LerpHackKernelSamples(kernel_samples);
664  EXPECT_TRUE(frag_kernel_samples.sample_count <= kGaussianBlurMaxKernelSize);
665 }
666 
667 } // namespace testing
668 } // namespace impeller
void SetTransform(const Matrix &transform)
Set the global transform matrix for this Entity.
Definition: entity.cc:62
std::shared_ptr< ContentContext > GetContentContext() const
@ kNormal
Blurred inside and outside.
static FilterInput::Ref Make(Variant input, bool msaa_enabled=true)
Definition: filter_input.cc:19
std::vector< FilterInput::Ref > Vector
Definition: filter_input.h:33
Performs a bidirectional Gaussian blur.
std::optional< Rect > GetFilterCoverage(const FilterInput::Vector &inputs, const Entity &entity, const Matrix &effect_transform) const override
Internal utility method for |GetLocalCoverage| that computes the output coverage of this filter acros...
static Quad CalculateUVs(const std::shared_ptr< FilterInput > &filter_input, const Entity &entity, const Rect &source_rect, const ISize &texture_size)
std::shared_ptr< Context > GetContext() const
Definition: playground.cc:94
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:30
std::shared_ptr< Texture > MakeTexture(ISize size)
Create a texture that has been cleared to transparent black.
int32_t x
Vector2 blur_radius
Blur radius in source pixels based on scaled_sigma.
Vector2 scaled_sigma
Sigma when considering an entity's scale and the effect transform.
inline ::testing::AssertionResult RectNear(impeller::Rect a, impeller::Rect b)
#define EXPECT_RECT_NEAR(a, b)
#define EXPECT_POINT_NEAR(a, b)
ScopedObject< Object > Create(CtorArgs &&... args)
Definition: object.h:161
TEST(AllocationSizeTest, CanCreateTypedAllocations)
TEST_P(AiksTest, DrawAtlasNoColor)
INSTANTIATE_PLAYGROUND_SUITE(AiksTest)
Point Vector2
Definition: point.h:430
static constexpr int32_t kGaussianBlurMaxKernelSize
float Scalar
Definition: scalar.h:19
TPoint< Scalar > Point
Definition: point.h:426
KernelSamples GenerateBlurInfo(BlurParameters parameters)
ISize64 ISize
Definition: size.h:162
std::array< Point, 4 > Quad
Definition: point.h:431
GaussianBlurPipeline::FragmentShader::KernelSamples LerpHackKernelSamples(KernelSamples parameters)
KernelSample samples[kMaxKernelSize]
A 4x4 matrix using column-major storage.
Definition: matrix.h:37
static constexpr Matrix MakeTranslation(const Vector3 &t)
Definition: matrix.h:95
static Matrix MakeRotationZ(Radians r)
Definition: matrix.h:223
static constexpr Matrix MakeScale(const Vector3 &s)
Definition: matrix.h:104
constexpr static std::optional< TRect > MakePointBounds(const U &value)
Definition: rect.h:165
constexpr static TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition: rect.h:136
constexpr static TRect MakeSize(const TSize< U > &size)
Definition: rect.h:150
constexpr static TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition: rect.h:129