Skip to content

Commit

Permalink
Standardize around blur sigma<->radius conversion factor (flutter#123)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdero authored and dnfield committed Apr 27, 2022
1 parent 93035a4 commit 20148b2
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 36 deletions.
18 changes: 10 additions & 8 deletions impeller/entity/contents/filters/filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -61,26 +61,28 @@ std::shared_ptr<FilterContents> FilterContents::MakeBlend(

std::shared_ptr<FilterContents> FilterContents::MakeDirectionalGaussianBlur(
FilterInput::Ref input,
Vector2 blur_vector,
Sigma sigma,
Vector2 direction,
BlurStyle blur_style,
FilterInput::Ref source_override) {
auto blur = std::make_shared<DirectionalGaussianBlurFilterContents>();
blur->SetInputs({input});
blur->SetBlurVector(blur_vector);
blur->SetSigma(sigma);
blur->SetDirection(direction);
blur->SetBlurStyle(blur_style);
blur->SetSourceOverride(source_override);
return blur;
}

std::shared_ptr<FilterContents> FilterContents::MakeGaussianBlur(
FilterInput::Ref input,
Scalar sigma_x,
Scalar sigma_y,
Sigma sigma_x,
Sigma sigma_y,
BlurStyle blur_style) {
auto x_blur =
MakeDirectionalGaussianBlur(input, Point(sigma_x, 0), BlurStyle::kNormal);
auto y_blur = MakeDirectionalGaussianBlur(
FilterInput::Make(x_blur), Point(0, sigma_y), blur_style, input);
auto x_blur = MakeDirectionalGaussianBlur(input, sigma_x, Point(1, 0),
BlurStyle::kNormal);
auto y_blur = MakeDirectionalGaussianBlur(FilterInput::Make(x_blur), sigma_y,
Point(0, 1), blur_style, input);
return y_blur;
}

Expand Down
58 changes: 55 additions & 3 deletions impeller/entity/contents/filters/filter_contents.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,71 @@ class FilterContents : public Contents {
kInner,
};

/// For filters that use a Gaussian distribution, this is the `Radius` size to
/// use per `Sigma` (standard deviation).
///
/// This cutoff (sqrt(3)) is taken from Flutter and Skia (where the
/// multiplicative inverse of this constant is used (1 / sqrt(3)):
/// https://api.flutter.dev/flutter/dart-ui/Shadow/convertRadiusToSigma.html
///
/// In practice, this value is somewhat arbitrary, and can be changed to a
/// higher number to integrate more of the Gaussian function and render higher
/// quality blurs (with exponentially diminishing returns for the same sigma
/// input). Making this value any lower results in a noticable loss of
/// quality in the blur.
constexpr static float kKernelRadiusPerSigma = 1.73205080757;

struct Radius;

/// @brief In filters that use Gaussian distributions, "sigma" is a size of
/// one standard deviation in terms of the local space pixel grid of
/// the filter input. In other words, this determines how wide the
/// distribution stretches.
struct Sigma {
Scalar sigma = 0.0;

constexpr Sigma() = default;

explicit constexpr Sigma(Scalar p_sigma) : sigma(p_sigma) {}

constexpr operator Radius() const {
return Radius{sigma > 0.5f ? (sigma - 0.5f) * kKernelRadiusPerSigma
: 0.0f};
};
};

/// @brief For convolution filters, the "radius" is the size of the
/// convolution kernel to use on the local space pixel grid of the
/// filter input.
/// For Gaussian blur kernels, this unit has a linear
/// relationship with `Sigma`. See `kKernelRadiusPerSigma` for
/// details on how this relationship works.
struct Radius {
Scalar radius = 0.0;

constexpr Radius() = default;

explicit constexpr Radius(Scalar p_radius) : radius(p_radius) {}

constexpr operator Sigma() const {
return Sigma{radius > 0 ? radius / kKernelRadiusPerSigma + 0.5f : 0.0f};
};
};

static std::shared_ptr<FilterContents> MakeBlend(Entity::BlendMode blend_mode,
FilterInput::Vector inputs);

static std::shared_ptr<FilterContents> MakeDirectionalGaussianBlur(
FilterInput::Ref input,
Vector2 blur_vector,
Sigma sigma,
Vector2 direction,
BlurStyle blur_style = BlurStyle::kNormal,
FilterInput::Ref alpha_mask = nullptr);

static std::shared_ptr<FilterContents> MakeGaussianBlur(
FilterInput::Ref input,
Scalar sigma_x,
Scalar sigma_y,
Sigma sigma_x,
Sigma sigma_y,
BlurStyle blur_style = BlurStyle::kNormal);

FilterContents();
Expand Down
38 changes: 27 additions & 11 deletions impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,23 @@ DirectionalGaussianBlurFilterContents::DirectionalGaussianBlurFilterContents() =
DirectionalGaussianBlurFilterContents::
~DirectionalGaussianBlurFilterContents() = default;

void DirectionalGaussianBlurFilterContents::SetBlurVector(Vector2 blur_vector) {
if (blur_vector.GetLengthSquared() < kEhCloseEnough) {
blur_vector_ = Vector2(0, kEhCloseEnough);
void DirectionalGaussianBlurFilterContents::SetSigma(Sigma sigma) {
if (sigma.sigma < kEhCloseEnough) {
// This cutoff is an implementation detail of the blur that's tied to the
// fragment shader. When the blur is set to 0, having a value slightly above
// zero makes the shader do 1 finite sample to pass the image through with
// no blur (while retaining correct alpha mask behavior).
blur_sigma_ = Sigma{kEhCloseEnough};
return;
}
blur_vector_ = blur_vector;
blur_sigma_ = sigma;
}

void DirectionalGaussianBlurFilterContents::SetDirection(Vector2 direction) {
blur_direction_ = direction.Normalize();
if (blur_direction_.IsZero()) {
blur_direction_ = Vector2(0, 1);
}
}

void DirectionalGaussianBlurFilterContents::SetBlurStyle(BlurStyle blur_style) {
Expand Down Expand Up @@ -93,8 +104,8 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter(
return false;
}

auto transformed_blur =
entity.GetTransformation().TransformDirection(blur_vector_);
auto transformed_blur = entity.GetTransformation().TransformDirection(
blur_direction_ * blur_sigma_.sigma);

// LTRB
Scalar uv[4] = {
Expand Down Expand Up @@ -142,7 +153,8 @@ bool DirectionalGaussianBlurFilterContents::RenderFilter(

VS::FrameInfo frame_info;
frame_info.texture_size = Point(input_bounds->size);
frame_info.blur_radius = transformed_blur.GetLength();
frame_info.blur_sigma = transformed_blur.GetLength();
frame_info.blur_radius = Radius{Sigma{frame_info.blur_sigma}}.radius;
frame_info.blur_direction = transformed_blur.Normalize();
frame_info.src_factor = src_color_factor_;
frame_info.inner_blur_factor = inner_blur_factor_;
Expand Down Expand Up @@ -174,10 +186,14 @@ std::optional<Rect> DirectionalGaussianBlurFilterContents::GetCoverage(
return std::nullopt;
}

auto transformed_blur =
entity.GetTransformation().TransformDirection(blur_vector_).Abs();
auto extent = bounds->size + transformed_blur * 2;
return Rect(bounds->origin - transformed_blur, Size(extent.x, extent.y));
auto transformed_blur_vector =
entity.GetTransformation()
.TransformDirection(blur_direction_ *
ceil(Radius{blur_sigma_}.radius))
.Abs();
auto extent = bounds->size + transformed_blur_vector * 2;
return Rect(bounds->origin - transformed_blur_vector,
Size(extent.x, extent.y));
}

} // namespace impeller
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ class DirectionalGaussianBlurFilterContents final : public FilterContents {

~DirectionalGaussianBlurFilterContents() override;

void SetBlurVector(Vector2 blur_vector);
void SetSigma(Sigma sigma);

void SetDirection(Vector2 direction);

void SetBlurStyle(BlurStyle blur_style);

Expand All @@ -34,8 +36,8 @@ class DirectionalGaussianBlurFilterContents final : public FilterContents {
const Entity& entity,
RenderPass& pass,
const Rect& bounds) const override;

Vector2 blur_vector_;
Sigma blur_sigma_;
Vector2 blur_direction_;
BlurStyle blur_style_ = BlurStyle::kNormal;
bool src_color_factor_ = false;
bool inner_blur_factor_ = true;
Expand Down
3 changes: 2 additions & 1 deletion impeller/entity/entity_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,8 @@ TEST_F(EntityTest, GaussianBlurFilter) {
Entity::BlendMode::kPlus, FilterInput::Make({boston, bridge, bridge}));

auto blur = FilterContents::MakeGaussianBlur(
FilterInput::Make(blend), blur_amount[0], blur_amount[1],
FilterInput::Make(blend), FilterContents::Sigma{blur_amount[0]},
FilterContents::Sigma{blur_amount[1]},
blur_styles[selected_blur_style]);

ISize input_size = boston->GetSize();
Expand Down
22 changes: 12 additions & 10 deletions impeller/entity/shaders/gaussian_blur.frag
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ in vec2 v_texture_coords;
in vec2 v_src_texture_coords;
in vec2 v_texture_size;
in vec2 v_blur_direction;
in float v_blur_sigma;
in float v_blur_radius;
in float v_src_factor;
in float v_inner_blur_factor;
in float v_outer_blur_factor;

out vec4 frag_color;

const float kTwoPi = 6.283185307179586;
const float kSqrtTwoPi = 2.50662827463;

float Gaussian(float x) {
float stddev = v_blur_radius * 0.5;
float xnorm = x / stddev;
return exp(-0.5 * xnorm * xnorm) / (kTwoPi * stddev * stddev);
float variance = v_blur_sigma * v_blur_sigma;
return exp(-0.5 * x * x / variance) / (kSqrtTwoPi * v_blur_sigma);
}

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

void main() {
vec4 total = vec4(0);
float total_gaussian = 0;
vec4 total_color = vec4(0);
float gaussian_integral = 0;
vec2 blur_uv_offset = v_blur_direction / v_texture_size;

for (float i = -v_blur_radius; i <= v_blur_radius; i++) {
float gaussian = Gaussian(i);
total_gaussian += gaussian;
total += gaussian * SampleWithBorder(texture_sampler,
v_texture_coords + blur_uv_offset * i);
gaussian_integral += gaussian;
total_color +=
gaussian * SampleWithBorder(texture_sampler,
v_texture_coords + blur_uv_offset * i);
}

vec4 blur_color = total / total_gaussian;
vec4 blur_color = total_color / gaussian_integral;

vec4 src_color = SampleWithBorder(alpha_mask_sampler, v_src_texture_coords);
float blur_factor = v_inner_blur_factor * float(src_color.a > 0) +
Expand Down
3 changes: 3 additions & 0 deletions impeller/entity/shaders/gaussian_blur.vert
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ uniform FrameInfo {
vec2 texture_size;

vec2 blur_direction;
float blur_sigma;
float blur_radius;

float src_factor;
Expand All @@ -23,6 +24,7 @@ out vec2 v_texture_coords;
out vec2 v_src_texture_coords;
out vec2 v_texture_size;
out vec2 v_blur_direction;
out float v_blur_sigma;
out float v_blur_radius;
out float v_src_factor;
out float v_inner_blur_factor;
Expand All @@ -34,6 +36,7 @@ void main() {
v_src_texture_coords = src_texture_coords;
v_texture_size = frame_info.texture_size;
v_blur_direction = frame_info.blur_direction;
v_blur_sigma = frame_info.blur_sigma;
v_blur_radius = frame_info.blur_radius;
v_src_factor = frame_info.src_factor;
v_inner_blur_factor = frame_info.inner_blur_factor;
Expand Down

0 comments on commit 20148b2

Please sign in to comment.