Skip to content

Commit 20148b2

Browse files
bderodnfield
authored andcommitted
Standardize around blur sigma<->radius conversion factor (#123)
1 parent 93035a4 commit 20148b2

7 files changed

+114
-36
lines changed

impeller/entity/contents/filters/filter_contents.cc

+10-8
Original file line numberDiff line numberDiff line change
@@ -61,26 +61,28 @@ std::shared_ptr<FilterContents> FilterContents::MakeBlend(
6161

6262
std::shared_ptr<FilterContents> FilterContents::MakeDirectionalGaussianBlur(
6363
FilterInput::Ref input,
64-
Vector2 blur_vector,
64+
Sigma sigma,
65+
Vector2 direction,
6566
BlurStyle blur_style,
6667
FilterInput::Ref source_override) {
6768
auto blur = std::make_shared<DirectionalGaussianBlurFilterContents>();
6869
blur->SetInputs({input});
69-
blur->SetBlurVector(blur_vector);
70+
blur->SetSigma(sigma);
71+
blur->SetDirection(direction);
7072
blur->SetBlurStyle(blur_style);
7173
blur->SetSourceOverride(source_override);
7274
return blur;
7375
}
7476

7577
std::shared_ptr<FilterContents> FilterContents::MakeGaussianBlur(
7678
FilterInput::Ref input,
77-
Scalar sigma_x,
78-
Scalar sigma_y,
79+
Sigma sigma_x,
80+
Sigma sigma_y,
7981
BlurStyle blur_style) {
80-
auto x_blur =
81-
MakeDirectionalGaussianBlur(input, Point(sigma_x, 0), BlurStyle::kNormal);
82-
auto y_blur = MakeDirectionalGaussianBlur(
83-
FilterInput::Make(x_blur), Point(0, sigma_y), blur_style, input);
82+
auto x_blur = MakeDirectionalGaussianBlur(input, sigma_x, Point(1, 0),
83+
BlurStyle::kNormal);
84+
auto y_blur = MakeDirectionalGaussianBlur(FilterInput::Make(x_blur), sigma_y,
85+
Point(0, 1), blur_style, input);
8486
return y_blur;
8587
}
8688

impeller/entity/contents/filters/filter_contents.h

+55-3
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,71 @@ class FilterContents : public Contents {
2929
kInner,
3030
};
3131

32+
/// For filters that use a Gaussian distribution, this is the `Radius` size to
33+
/// use per `Sigma` (standard deviation).
34+
///
35+
/// This cutoff (sqrt(3)) is taken from Flutter and Skia (where the
36+
/// multiplicative inverse of this constant is used (1 / sqrt(3)):
37+
/// https://api.flutter.dev/flutter/dart-ui/Shadow/convertRadiusToSigma.html
38+
///
39+
/// In practice, this value is somewhat arbitrary, and can be changed to a
40+
/// higher number to integrate more of the Gaussian function and render higher
41+
/// quality blurs (with exponentially diminishing returns for the same sigma
42+
/// input). Making this value any lower results in a noticable loss of
43+
/// quality in the blur.
44+
constexpr static float kKernelRadiusPerSigma = 1.73205080757;
45+
46+
struct Radius;
47+
48+
/// @brief In filters that use Gaussian distributions, "sigma" is a size of
49+
/// one standard deviation in terms of the local space pixel grid of
50+
/// the filter input. In other words, this determines how wide the
51+
/// distribution stretches.
52+
struct Sigma {
53+
Scalar sigma = 0.0;
54+
55+
constexpr Sigma() = default;
56+
57+
explicit constexpr Sigma(Scalar p_sigma) : sigma(p_sigma) {}
58+
59+
constexpr operator Radius() const {
60+
return Radius{sigma > 0.5f ? (sigma - 0.5f) * kKernelRadiusPerSigma
61+
: 0.0f};
62+
};
63+
};
64+
65+
/// @brief For convolution filters, the "radius" is the size of the
66+
/// convolution kernel to use on the local space pixel grid of the
67+
/// filter input.
68+
/// For Gaussian blur kernels, this unit has a linear
69+
/// relationship with `Sigma`. See `kKernelRadiusPerSigma` for
70+
/// details on how this relationship works.
71+
struct Radius {
72+
Scalar radius = 0.0;
73+
74+
constexpr Radius() = default;
75+
76+
explicit constexpr Radius(Scalar p_radius) : radius(p_radius) {}
77+
78+
constexpr operator Sigma() const {
79+
return Sigma{radius > 0 ? radius / kKernelRadiusPerSigma + 0.5f : 0.0f};
80+
};
81+
};
82+
3283
static std::shared_ptr<FilterContents> MakeBlend(Entity::BlendMode blend_mode,
3384
FilterInput::Vector inputs);
3485

3586
static std::shared_ptr<FilterContents> MakeDirectionalGaussianBlur(
3687
FilterInput::Ref input,
37-
Vector2 blur_vector,
88+
Sigma sigma,
89+
Vector2 direction,
3890
BlurStyle blur_style = BlurStyle::kNormal,
3991
FilterInput::Ref alpha_mask = nullptr);
4092

4193
static std::shared_ptr<FilterContents> MakeGaussianBlur(
4294
FilterInput::Ref input,
43-
Scalar sigma_x,
44-
Scalar sigma_y,
95+
Sigma sigma_x,
96+
Sigma sigma_y,
4597
BlurStyle blur_style = BlurStyle::kNormal);
4698

4799
FilterContents();

impeller/entity/contents/filters/gaussian_blur_filter_contents.cc

+27-11
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,23 @@ DirectionalGaussianBlurFilterContents::DirectionalGaussianBlurFilterContents() =
2222
DirectionalGaussianBlurFilterContents::
2323
~DirectionalGaussianBlurFilterContents() = default;
2424

25-
void DirectionalGaussianBlurFilterContents::SetBlurVector(Vector2 blur_vector) {
26-
if (blur_vector.GetLengthSquared() < kEhCloseEnough) {
27-
blur_vector_ = Vector2(0, kEhCloseEnough);
25+
void DirectionalGaussianBlurFilterContents::SetSigma(Sigma sigma) {
26+
if (sigma.sigma < kEhCloseEnough) {
27+
// This cutoff is an implementation detail of the blur that's tied to the
28+
// fragment shader. When the blur is set to 0, having a value slightly above
29+
// zero makes the shader do 1 finite sample to pass the image through with
30+
// no blur (while retaining correct alpha mask behavior).
31+
blur_sigma_ = Sigma{kEhCloseEnough};
2832
return;
2933
}
30-
blur_vector_ = blur_vector;
34+
blur_sigma_ = sigma;
35+
}
36+
37+
void DirectionalGaussianBlurFilterContents::SetDirection(Vector2 direction) {
38+
blur_direction_ = direction.Normalize();
39+
if (blur_direction_.IsZero()) {
40+
blur_direction_ = Vector2(0, 1);
41+
}
3142
}
3243

3344
void DirectionalGaussianBlurFilterContents::SetBlurStyle(BlurStyle blur_style) {
@@ -93,8 +104,8 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter(
93104
return false;
94105
}
95106

96-
auto transformed_blur =
97-
entity.GetTransformation().TransformDirection(blur_vector_);
107+
auto transformed_blur = entity.GetTransformation().TransformDirection(
108+
blur_direction_ * blur_sigma_.sigma);
98109

99110
// LTRB
100111
Scalar uv[4] = {
@@ -142,7 +153,8 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter(
142153

143154
VS::FrameInfo frame_info;
144155
frame_info.texture_size = Point(input_bounds->size);
145-
frame_info.blur_radius = transformed_blur.GetLength();
156+
frame_info.blur_sigma = transformed_blur.GetLength();
157+
frame_info.blur_radius = Radius{Sigma{frame_info.blur_sigma}}.radius;
146158
frame_info.blur_direction = transformed_blur.Normalize();
147159
frame_info.src_factor = src_color_factor_;
148160
frame_info.inner_blur_factor = inner_blur_factor_;
@@ -174,10 +186,14 @@ std::optional<Rect> DirectionalGaussianBlurFilterContents::GetCoverage(
174186
return std::nullopt;
175187
}
176188

177-
auto transformed_blur =
178-
entity.GetTransformation().TransformDirection(blur_vector_).Abs();
179-
auto extent = bounds->size + transformed_blur * 2;
180-
return Rect(bounds->origin - transformed_blur, Size(extent.x, extent.y));
189+
auto transformed_blur_vector =
190+
entity.GetTransformation()
191+
.TransformDirection(blur_direction_ *
192+
ceil(Radius{blur_sigma_}.radius))
193+
.Abs();
194+
auto extent = bounds->size + transformed_blur_vector * 2;
195+
return Rect(bounds->origin - transformed_blur_vector,
196+
Size(extent.x, extent.y));
181197
}
182198

183199
} // namespace impeller

impeller/entity/contents/filters/gaussian_blur_filter_contents.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ class DirectionalGaussianBlurFilterContents final : public FilterContents {
1818

1919
~DirectionalGaussianBlurFilterContents() override;
2020

21-
void SetBlurVector(Vector2 blur_vector);
21+
void SetSigma(Sigma sigma);
22+
23+
void SetDirection(Vector2 direction);
2224

2325
void SetBlurStyle(BlurStyle blur_style);
2426

@@ -34,8 +36,8 @@ class DirectionalGaussianBlurFilterContents final : public FilterContents {
3436
const Entity& entity,
3537
RenderPass& pass,
3638
const Rect& bounds) const override;
37-
38-
Vector2 blur_vector_;
39+
Sigma blur_sigma_;
40+
Vector2 blur_direction_;
3941
BlurStyle blur_style_ = BlurStyle::kNormal;
4042
bool src_color_factor_ = false;
4143
bool inner_blur_factor_ = true;

impeller/entity/entity_unittests.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,8 @@ TEST_F(EntityTest, GaussianBlurFilter) {
729729
Entity::BlendMode::kPlus, FilterInput::Make({boston, bridge, bridge}));
730730

731731
auto blur = FilterContents::MakeGaussianBlur(
732-
FilterInput::Make(blend), blur_amount[0], blur_amount[1],
732+
FilterInput::Make(blend), FilterContents::Sigma{blur_amount[0]},
733+
FilterContents::Sigma{blur_amount[1]},
733734
blur_styles[selected_blur_style]);
734735

735736
ISize input_size = boston->GetSize();

impeller/entity/shaders/gaussian_blur.frag

+12-10
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@ in vec2 v_texture_coords;
1616
in vec2 v_src_texture_coords;
1717
in vec2 v_texture_size;
1818
in vec2 v_blur_direction;
19+
in float v_blur_sigma;
1920
in float v_blur_radius;
2021
in float v_src_factor;
2122
in float v_inner_blur_factor;
2223
in float v_outer_blur_factor;
2324

2425
out vec4 frag_color;
2526

26-
const float kTwoPi = 6.283185307179586;
27+
const float kSqrtTwoPi = 2.50662827463;
2728

2829
float Gaussian(float x) {
29-
float stddev = v_blur_radius * 0.5;
30-
float xnorm = x / stddev;
31-
return exp(-0.5 * xnorm * xnorm) / (kTwoPi * stddev * stddev);
30+
float variance = v_blur_sigma * v_blur_sigma;
31+
return exp(-0.5 * x * x / variance) / (kSqrtTwoPi * v_blur_sigma);
3232
}
3333

3434
// Emulate SamplerAddressMode::ClampToBorder.
@@ -38,17 +38,19 @@ vec4 SampleWithBorder(sampler2D tex, vec2 uv) {
3838
}
3939

4040
void main() {
41-
vec4 total = vec4(0);
42-
float total_gaussian = 0;
41+
vec4 total_color = vec4(0);
42+
float gaussian_integral = 0;
4343
vec2 blur_uv_offset = v_blur_direction / v_texture_size;
44+
4445
for (float i = -v_blur_radius; i <= v_blur_radius; i++) {
4546
float gaussian = Gaussian(i);
46-
total_gaussian += gaussian;
47-
total += gaussian * SampleWithBorder(texture_sampler,
48-
v_texture_coords + blur_uv_offset * i);
47+
gaussian_integral += gaussian;
48+
total_color +=
49+
gaussian * SampleWithBorder(texture_sampler,
50+
v_texture_coords + blur_uv_offset * i);
4951
}
5052

51-
vec4 blur_color = total / total_gaussian;
53+
vec4 blur_color = total_color / gaussian_integral;
5254

5355
vec4 src_color = SampleWithBorder(alpha_mask_sampler, v_src_texture_coords);
5456
float blur_factor = v_inner_blur_factor * float(src_color.a > 0) +

impeller/entity/shaders/gaussian_blur.vert

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ uniform FrameInfo {
77
vec2 texture_size;
88

99
vec2 blur_direction;
10+
float blur_sigma;
1011
float blur_radius;
1112

1213
float src_factor;
@@ -23,6 +24,7 @@ out vec2 v_texture_coords;
2324
out vec2 v_src_texture_coords;
2425
out vec2 v_texture_size;
2526
out vec2 v_blur_direction;
27+
out float v_blur_sigma;
2628
out float v_blur_radius;
2729
out float v_src_factor;
2830
out float v_inner_blur_factor;
@@ -34,6 +36,7 @@ void main() {
3436
v_src_texture_coords = src_texture_coords;
3537
v_texture_size = frame_info.texture_size;
3638
v_blur_direction = frame_info.blur_direction;
39+
v_blur_sigma = frame_info.blur_sigma;
3740
v_blur_radius = frame_info.blur_radius;
3841
v_src_factor = frame_info.src_factor;
3942
v_inner_blur_factor = frame_info.inner_blur_factor;

0 commit comments

Comments
 (0)