mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Standardize around blur sigma<->radius conversion factor (flutter/engine#123)
This commit is contained in:
parent
7ba73f2b09
commit
da54caee10
@ -61,12 +61,14 @@ 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;
|
||||
@ -74,13 +76,13 @@ std::shared_ptr<FilterContents> FilterContents::MakeDirectionalGaussianBlur(
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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) {
|
||||
@ -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] = {
|
||||
@ -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_;
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -16,6 +16,7 @@ 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;
|
||||
@ -23,12 +24,11 @@ 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.
|
||||
@ -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) +
|
||||
|
||||
@ -7,6 +7,7 @@ uniform FrameInfo {
|
||||
vec2 texture_size;
|
||||
|
||||
vec2 blur_direction;
|
||||
float blur_sigma;
|
||||
float blur_radius;
|
||||
|
||||
float src_factor;
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user