Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
4 changes: 2 additions & 2 deletions impeller/aiks/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -909,8 +909,8 @@ void Canvas::DrawTextFrame(const std::shared_ptr<TextFrame>& text_frame,
// needs to be reworked in order to interact correctly with
// mask filters.
// https://github.com/flutter/flutter/issues/133297
entity.SetContents(
paint.WithFilters(paint.WithMaskBlur(std::move(text_contents), true)));
entity.SetContents(paint.WithFilters(paint.WithMaskBlur(
std::move(text_contents), true, GetCurrentTransform())));

AddRenderEntityToCurrentPass(std::move(entity));
}
Expand Down
4 changes: 2 additions & 2 deletions impeller/aiks/experimental_canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,8 @@ void ExperimentalCanvas::DrawTextFrame(
// needs to be reworked in order to interact correctly with
// mask filters.
// https://github.com/flutter/flutter/issues/133297
entity.SetContents(
paint.WithFilters(paint.WithMaskBlur(std::move(text_contents), true)));
entity.SetContents(paint.WithFilters(paint.WithMaskBlur(
std::move(text_contents), true, GetCurrentTransform())));

AddRenderEntityToCurrentPass(std::move(entity), false);
}
Expand Down
18 changes: 13 additions & 5 deletions impeller/aiks/paint.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,11 @@ std::shared_ptr<Contents> Paint::WithFiltersForSubpassTarget(
}

std::shared_ptr<Contents> Paint::WithMaskBlur(std::shared_ptr<Contents> input,
bool is_solid_color) const {
bool is_solid_color,
const Matrix& ctm) const {
if (mask_blur_descriptor.has_value()) {
input = mask_blur_descriptor->CreateMaskBlur(FilterInput::Make(input),
is_solid_color);
is_solid_color, ctm);
}
return input;
}
Expand Down Expand Up @@ -203,12 +204,19 @@ std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur(

std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur(
const FilterInput::Ref& input,
bool is_solid_color) const {
bool is_solid_color,
const Matrix& ctm) const {
Vector2 blur_sigma(sigma.sigma, sigma.sigma);
if (!respect_ctm) {
blur_sigma /= Vector2(ctm.GetBasisX().Length(), ctm.GetBasisY().Length());
}
if (is_solid_color) {
return FilterContents::MakeGaussianBlur(input, sigma, sigma,
return FilterContents::MakeGaussianBlur(input, Sigma(blur_sigma.x),
Sigma(blur_sigma.y),
Entity::TileMode::kDecal, style);
}
return FilterContents::MakeBorderMaskBlur(input, sigma, sigma, style);
return FilterContents::MakeBorderMaskBlur(input, Sigma(blur_sigma.x),
Sigma(blur_sigma.y), style);
}

std::shared_ptr<ColorFilter> Paint::GetColorFilter() const {
Expand Down
9 changes: 7 additions & 2 deletions impeller/aiks/paint.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ struct Paint {
struct MaskBlurDescriptor {
FilterContents::BlurStyle style;
Sigma sigma;
/// Text mask blurs need to not apply the CTM to the blur kernel.
/// See: https://github.com/flutter/flutter/issues/115112
bool respect_ctm = true;

std::shared_ptr<FilterContents> CreateMaskBlur(
std::shared_ptr<ColorSourceContents> color_source_contents,
Expand All @@ -58,7 +61,8 @@ struct Paint {

std::shared_ptr<FilterContents> CreateMaskBlur(
const FilterInput::Ref& input,
bool is_solid_color) const;
bool is_solid_color,
const Matrix& ctm) const;
};

Color color = Color::Black();
Expand Down Expand Up @@ -104,7 +108,8 @@ struct Paint {
bool HasColorFilter() const;

std::shared_ptr<Contents> WithMaskBlur(std::shared_ptr<Contents> input,
bool is_solid_color) const;
bool is_solid_color,
const Matrix& ctm) const;

std::shared_ptr<FilterContents> WithImageFilter(
const FilterInput::Variant& input,
Expand Down
1 change: 1 addition & 0 deletions impeller/display_list/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ template("display_list_unittests_component") {
"aiks_dl_gradient_unittests.cc",
"aiks_dl_opacity_unittests.cc",
"aiks_dl_path_unittests.cc",
"dl_golden_blur_unittests.cc",
"dl_golden_unittests.cc",
"dl_playground.cc",
"dl_playground.h",
Expand Down
1 change: 1 addition & 0 deletions impeller/display_list/dl_dispatcher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ void DlDispatcherBase::setMaskFilter(const flutter::DlMaskFilter* filter) {
paint_.mask_blur_descriptor = {
.style = ToBlurStyle(blur->style()),
.sigma = Sigma(blur->sigma()),
.respect_ctm = blur->respectCTM(),
};
break;
}
Expand Down
102 changes: 102 additions & 0 deletions impeller/display_list/dl_golden_blur_unittests.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "impeller/display_list/dl_golden_unittests.h"

#include "flutter/display_list/dl_builder.h"
#include "flutter/display_list/effects/dl_mask_filter.h"
#include "flutter/testing/testing.h"
#include "gtest/gtest.h"
#include "impeller/typographer/backends/skia/text_frame_skia.h"
#include "txt/platform.h"

namespace flutter {
namespace testing {

using impeller::Font;

namespace {
struct TextRenderOptions {
bool stroke = false;
SkScalar font_size = 50;
DlColor color = DlColor::kYellow();
std::shared_ptr<DlMaskFilter> mask_filter;
};

bool RenderTextInCanvasSkia(DlCanvas* canvas,
const std::string& text,
const std::string_view& font_fixture,
SkPoint position,
TextRenderOptions options = {}) {
auto c_font_fixture = std::string(font_fixture);
auto mapping = flutter::testing::OpenFixtureAsSkData(c_font_fixture.c_str());
if (!mapping) {
return false;
}
sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager();
SkFont sk_font(font_mgr->makeFromData(mapping), options.font_size);
auto blob = SkTextBlob::MakeFromString(text.c_str(), sk_font);
if (!blob) {
return false;
}

auto frame = impeller::MakeTextFrameFromTextBlobSkia(blob);

DlPaint text_paint;
text_paint.setColor(options.color);
text_paint.setMaskFilter(options.mask_filter);
// text_paint.mask_blur_descriptor = options.mask_blur_descriptor;
// text_paint.stroke_width = 1;
// text_paint.style =
// options.stroke ? Paint::Style::kStroke : Paint::Style::kFill;
canvas->DrawTextFrame(frame, position.x(), position.y(), text_paint);
return true;
}

} // namespace

TEST_P(DlGoldenTest, TextBlurMaskFilterRespectCTM) {
impeller::Point content_scale = GetContentScale();
auto draw = [&](DlCanvas* canvas,
const std::vector<std::unique_ptr<DlImage>>& images) {
canvas->Scale(content_scale.x, content_scale.y);
canvas->Scale(2, 2);
TextRenderOptions options;
options.mask_filter =
DlBlurMaskFilter::Make(DlBlurStyle::kNormal, /*sigma=*/10,
/*respect_ctm=*/true);
ASSERT_TRUE(RenderTextInCanvasSkia(canvas, "hello world",
"Roboto-Regular.ttf",
SkPoint::Make(100, 100), options));
};

DisplayListBuilder builder;
draw(&builder, /*images=*/{});

ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}

TEST_P(DlGoldenTest, TextBlurMaskFilterDisrespectCTM) {
impeller::Point content_scale = GetContentScale();
auto draw = [&](DlCanvas* canvas,
const std::vector<std::unique_ptr<DlImage>>& images) {
canvas->Scale(content_scale.x, content_scale.y);
canvas->Scale(2, 2);
TextRenderOptions options;
options.mask_filter =
DlBlurMaskFilter::Make(DlBlurStyle::kNormal, /*sigma=*/10,
/*respect_ctm=*/false);
ASSERT_TRUE(RenderTextInCanvasSkia(canvas, "hello world",
"Roboto-Regular.ttf",
SkPoint::Make(100, 100), options));
};

DisplayListBuilder builder;
draw(&builder, /*images=*/{});

ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}

} // namespace testing
} // namespace flutter
54 changes: 44 additions & 10 deletions impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,22 @@ std::optional<Rect> GaussianBlurFilterContents::GetFilterCoverage(
return expanded_source_coverage.TransformBounds(entity.GetTransform());
}

namespace {
Vector2 ExtractScale(const Matrix& matrix) {
Vector2 entity_scale_x = matrix * Vector2(1.0, 0.0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matrix::Decompose here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at that and it was way more calculations for things we just throw away. It's doing things like calculating the determinant. It returns an optional result probably for this reason. This one is guaranteed to have a result too.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These matrix mults are just recomputing the basis vectors, which could just be pulled out of the matrix instead: return Vector2(matrix.GetBasisX().Length(), matrix.GetBasisY().Length())

Matrix::GetScale does the same thing, but includes the Z basis and returns a Vector3. Adding a Matrix::GetScaleXY seems like a good idea since we're doing this in a few places now.

Vector2 entity_scale_y = matrix * Vector2(0.0, 1.0);
return Vector2(entity_scale_x.GetLength(), entity_scale_y.GetLength());
}
} // namespace

// A brief overview how this works:
// 1) Snapshot the filter input.
// 2) Perform downsample pass. This also inserts the gutter around the input
// snapshot since the blur can render outside the bounds of the snapshot.
// 3) Perform 1D horizontal blur pass.
// 4) Perform 1D vertical blur pass.
// 5) Apply the blur style to the blur result. This may just mask the output or
// draw the original snapshot over the result.
std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
const FilterInput::Vector& inputs,
const ContentContext& renderer,
Expand All @@ -390,7 +406,16 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
return std::nullopt;
}

// Source space here is scaled by the entity's transform. This is a
// requirement for text to be rendered correctly. You can think of this as
// "scaled source space" or "un-rotated local space". The entity's rotation is
// applied to the result of the blur as part of the result's transform.
const Vector2 source_space_scalar =
ExtractScale(entity.GetTransform().Basis());

Vector2 scaled_sigma = (effect_transform.Basis() *
Matrix::MakeScale({source_space_scalar.x,
source_space_scalar.y, 1}) * //
Vector2(ScaleSigma(sigma_x_), ScaleSigma(sigma_y_)))
.Abs();
scaled_sigma.x = std::min(scaled_sigma.x, kMaxSigma);
Expand All @@ -417,11 +442,13 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
}

Entity snapshot_entity = entity.Clone();
snapshot_entity.SetTransform(Matrix());
snapshot_entity.SetTransform(
Matrix::MakeScale({source_space_scalar.x, source_space_scalar.y, 1}));
std::optional<Rect> source_expanded_coverage_hint;
if (expanded_coverage_hint.has_value()) {
source_expanded_coverage_hint =
expanded_coverage_hint->TransformBounds(entity.GetTransform().Invert());
source_expanded_coverage_hint = expanded_coverage_hint->TransformBounds(
Matrix::MakeScale({source_space_scalar.x, source_space_scalar.y, 1}) *
entity.GetTransform().Invert());
}

std::optional<Snapshot> input_snapshot =
Expand All @@ -436,7 +463,10 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
Entity result =
Entity::FromSnapshot(input_snapshot.value(),
entity.GetBlendMode()); // No blur to render.
result.SetTransform(entity.GetTransform() * input_snapshot->transform);
result.SetTransform(entity.GetTransform() *
Matrix::MakeScale({1.f / source_space_scalar.x,
1.f / source_space_scalar.y, 1}) *
input_snapshot->transform);
return result;
}

Expand Down Expand Up @@ -562,12 +592,16 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
MinMagFilter::kLinear, SamplerAddressMode::kClampToEdge);

Entity blur_output_entity = Entity::FromSnapshot(
Snapshot{.texture = pass3_out.value().GetRenderTargetTexture(),
.transform = entity.GetTransform() * input_snapshot->transform *
padding_snapshot_adjustment *
Matrix::MakeScale(1 / effective_scalar),
.sampler_descriptor = sampler_desc,
.opacity = input_snapshot->opacity},
Snapshot{
.texture = pass3_out.value().GetRenderTargetTexture(),
.transform = entity.GetTransform() * //
Matrix::MakeScale({1.f / source_space_scalar.x,
1.f / source_space_scalar.y, 1}) * //
input_snapshot->transform * //
padding_snapshot_adjustment * //
Matrix::MakeScale(1 / effective_scalar),
.sampler_descriptor = sampler_desc,
.opacity = input_snapshot->opacity},
entity.GetBlendMode());

return ApplyBlurStyle(mask_blur_style_, entity, inputs[0],
Expand Down