Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
#include "impeller/aiks/paint_pass_delegate.h"
#include "impeller/aiks/testing/context_spy.h"
#include "impeller/entity/contents/color_source_contents.h"
#include "impeller/entity/contents/conical_gradient_contents.h"
#include "impeller/entity/contents/filters/inputs/filter_input.h"
#include "impeller/entity/contents/linear_gradient_contents.h"
#include "impeller/entity/contents/scene_contents.h"
#include "impeller/entity/contents/solid_color_contents.h"
#include "impeller/entity/contents/tiled_texture_contents.h"
Expand Down Expand Up @@ -393,6 +395,30 @@ TEST_P(AiksTest, CanRenderLinearGradientDecal) {
CanRenderLinearGradient(this, Entity::TileMode::kDecal);
}

TEST_P(AiksTest, CanRenderLinearGradientDecalWithColorFilter) {
Canvas canvas;
canvas.Scale(GetContentScale());
Paint paint;
canvas.Translate({100.0f, 0, 0});

std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
Color{0.1294, 0.5882, 0.9529, 0.0}};
std::vector<Scalar> stops = {0.0, 1.0};

paint.color_source = ColorSource::MakeLinearGradient(
{0, 0}, {200, 200}, std::move(colors), std::move(stops),
Entity::TileMode::kDecal, {});
// Overlay the gradient with 25% green. This should appear as the entire
// rectangle being drawn with 25% green, including the border area outside the
// decal gradient.
paint.color_filter = ColorFilter::MakeBlend(BlendMode::kSourceOver,
Color::Green().WithAlpha(0.25));

paint.color = Color(1.0, 1.0, 1.0, 1.0);
canvas.DrawRect({0, 0, 600, 600}, paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

namespace {
void CanRenderLinearGradientWithOverlappingStops(AiksTest* aiks_test,
Entity::TileMode tile_mode) {
Expand Down Expand Up @@ -2914,5 +2940,70 @@ TEST_P(AiksTest, MatrixBackdropFilter) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

TEST_P(AiksTest, SolidColorApplyColorFilter) {
auto contents = SolidColorContents();
contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75));
auto result = contents.ApplyColorFilter([](const Color& color) {
return color.Blend(Color::LimeGreen().WithAlpha(0.75), BlendMode::kScreen);
});
ASSERT_TRUE(result);
ASSERT_COLOR_NEAR(contents.GetColor(),
Color(0.433247, 0.879523, 0.825324, 0.75));
}

#define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \
TEST_P(AiksTest, name##GradientApplyColorFilter) { \
auto contents = name##GradientContents(); \
contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \
auto result = contents.ApplyColorFilter([](const Color& color) { \
return color.Blend(Color::LimeGreen().WithAlpha(0.75), \
BlendMode::kScreen); \
}); \
ASSERT_TRUE(result); \
\
std::vector<Color> expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \
ASSERT_COLORS_NEAR(contents.GetColors(), expected); \
}

APPLY_COLOR_FILTER_GRADIENT_TEST(Linear);
APPLY_COLOR_FILTER_GRADIENT_TEST(Radial);
APPLY_COLOR_FILTER_GRADIENT_TEST(Conical);
APPLY_COLOR_FILTER_GRADIENT_TEST(Sweep);

TEST_P(AiksTest, ClippedBlurFilterRendersCorrectlyInteractive) {
auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
auto point = IMPELLER_PLAYGROUND_POINT(Point(400, 400), 20, Color::Green());

Canvas canvas;
canvas.Translate(point - Point(400, 400));
Paint paint;
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Radius{120 * 3},
};
paint.color = Color::Red();
PathBuilder builder{};
builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
canvas.DrawPath(builder.TakePath(), paint);
return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}

TEST_P(AiksTest, ClippedBlurFilterRendersCorrectly) {
Canvas canvas;
canvas.Translate(Point(0, -400));
Paint paint;
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Radius{120 * 3},
};
paint.color = Color::Red();
PathBuilder builder{};
builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
canvas.DrawPath(builder.TakePath(), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}

} // namespace testing
} // namespace impeller
23 changes: 18 additions & 5 deletions impeller/aiks/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ void Canvas::DrawImageRect(const std::shared_ptr<Image>& image,
Entity entity;
entity.SetBlendMode(paint.blend_mode);
entity.SetStencilDepth(GetStencilDepth());
entity.SetContents(paint.WithFilters(contents, false));
entity.SetContents(paint.WithFilters(contents));
entity.SetTransformation(GetCurrentTransformation());

GetCurrentPass().AddEntity(entity);
Expand Down Expand Up @@ -537,8 +537,14 @@ void Canvas::DrawTextFrame(const TextFrame& text_frame,
color_text_contents->SetColorSourceContents(
paint.color_source.GetContents(paint));

entity.SetContents(
paint.WithFilters(std::move(color_text_contents), false));
// TODO(bdero): This mask blur application is a hack. It will always wind up
// doing a gaussian blur that affects the color source itself
// instead of just the mask. The color filter text support
// 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(color_text_contents), true)));

GetCurrentPass().AddEntity(entity);
return;
Expand All @@ -549,7 +555,14 @@ void Canvas::DrawTextFrame(const TextFrame& text_frame,
entity.SetTransformation(GetCurrentTransformation() *
Matrix::MakeTranslation(position));

entity.SetContents(paint.WithFilters(std::move(text_contents), true));
// TODO(bdero): This mask blur application is a hack. It will always wind up
// doing a gaussian blur that affects the color source itself
// instead of just the mask. The color filter text support
// 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)));

GetCurrentPass().AddEntity(entity);
}
Expand Down Expand Up @@ -658,7 +671,7 @@ void Canvas::DrawAtlas(const std::shared_ptr<Image>& atlas,
entity.SetTransformation(GetCurrentTransformation());
entity.SetStencilDepth(GetStencilDepth());
entity.SetBlendMode(paint.blend_mode);
entity.SetContents(paint.WithFilters(contents, false));
entity.SetContents(paint.WithFilters(contents));

GetCurrentPass().AddEntity(entity);
}
Expand Down
10 changes: 6 additions & 4 deletions impeller/aiks/color_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ BlendColorFilter::BlendColorFilter(BlendMode blend_mode, Color color)

BlendColorFilter::~BlendColorFilter() = default;

std::shared_ptr<ColorFilterContents> BlendColorFilter::GetColorFilter(
std::shared_ptr<ColorFilterContents> BlendColorFilter::WrapWithGPUColorFilter(
std::shared_ptr<FilterInput> input,
bool absorb_opacity) const {
auto filter =
Expand All @@ -67,7 +67,7 @@ MatrixColorFilter::MatrixColorFilter(ColorMatrix color_matrix)

MatrixColorFilter::~MatrixColorFilter() = default;

std::shared_ptr<ColorFilterContents> MatrixColorFilter::GetColorFilter(
std::shared_ptr<ColorFilterContents> MatrixColorFilter::WrapWithGPUColorFilter(
std::shared_ptr<FilterInput> input,
bool absorb_opacity) const {
auto filter =
Expand All @@ -90,7 +90,8 @@ SrgbToLinearColorFilter::SrgbToLinearColorFilter() = default;

SrgbToLinearColorFilter::~SrgbToLinearColorFilter() = default;

std::shared_ptr<ColorFilterContents> SrgbToLinearColorFilter::GetColorFilter(
std::shared_ptr<ColorFilterContents>
SrgbToLinearColorFilter::WrapWithGPUColorFilter(
std::shared_ptr<FilterInput> input,
bool absorb_opacity) const {
auto filter = ColorFilterContents::MakeSrgbToLinearFilter({std::move(input)});
Expand All @@ -111,7 +112,8 @@ LinearToSrgbColorFilter::LinearToSrgbColorFilter() = default;

LinearToSrgbColorFilter::~LinearToSrgbColorFilter() = default;

std::shared_ptr<ColorFilterContents> LinearToSrgbColorFilter::GetColorFilter(
std::shared_ptr<ColorFilterContents>
LinearToSrgbColorFilter::WrapWithGPUColorFilter(
std::shared_ptr<FilterInput> input,
bool absorb_opacity) const {
auto filter = ColorFilterContents::MakeSrgbToLinearFilter({std::move(input)});
Expand Down
21 changes: 16 additions & 5 deletions impeller/aiks/color_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ struct Paint;

class ColorFilter {
public:
/// A procedure that filters a given unpremultiplied color to produce a new
/// unpremultiplied color.
using ColorFilterProc = std::function<Color(Color)>;

ColorFilter();
Expand All @@ -32,10 +34,19 @@ class ColorFilter {

static std::shared_ptr<ColorFilter> MakeLinearToSrgb();

virtual std::shared_ptr<ColorFilterContents> GetColorFilter(
/// @brief Wraps the given filter input with a GPU-based filter that will
/// perform the color operation. The given input will first be
/// rendered to a texture and then filtered.
///
/// Note that this operation has no consideration for the original
/// geometry mask of the filter input. And the entire input texture is
/// treated as color information.
virtual std::shared_ptr<ColorFilterContents> WrapWithGPUColorFilter(
std::shared_ptr<FilterInput> input,
bool absorb_opacity) const = 0;

/// @brief Returns a function that can be used to filter unpremultiplied
/// Impeller Colors on the CPU.
virtual ColorFilterProc GetCPUColorFilterProc() const = 0;
};

Expand All @@ -50,7 +61,7 @@ class BlendColorFilter final : public ColorFilter {
~BlendColorFilter() override;

// |ColorFilter|
std::shared_ptr<ColorFilterContents> GetColorFilter(
std::shared_ptr<ColorFilterContents> WrapWithGPUColorFilter(
std::shared_ptr<FilterInput> input,
bool absorb_opacity) const override;

Expand All @@ -73,7 +84,7 @@ class MatrixColorFilter final : public ColorFilter {
~MatrixColorFilter() override;

// |ColorFilter|
std::shared_ptr<ColorFilterContents> GetColorFilter(
std::shared_ptr<ColorFilterContents> WrapWithGPUColorFilter(
std::shared_ptr<FilterInput> input,
bool absorb_opacity) const override;

Expand All @@ -95,7 +106,7 @@ class SrgbToLinearColorFilter final : public ColorFilter {
~SrgbToLinearColorFilter() override;

// |ColorFilter|
std::shared_ptr<ColorFilterContents> GetColorFilter(
std::shared_ptr<ColorFilterContents> WrapWithGPUColorFilter(
std::shared_ptr<FilterInput> input,
bool absorb_opacity) const override;

Expand All @@ -114,7 +125,7 @@ class LinearToSrgbColorFilter final : public ColorFilter {
~LinearToSrgbColorFilter() override;

// |ColorFilter|
std::shared_ptr<ColorFilterContents> GetColorFilter(
std::shared_ptr<ColorFilterContents> WrapWithGPUColorFilter(
std::shared_ptr<FilterInput> input,
bool absorb_opacity) const override;

Expand Down
3 changes: 2 additions & 1 deletion impeller/aiks/color_source.cc
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ ColorSource ColorSource::MakeImage(std::shared_ptr<Texture> texture,
if (paint.color_filter) {
TiledTextureContents::ColorFilterProc filter_proc =
[color_filter = paint.color_filter](FilterInput::Ref input) {
return color_filter->GetColorFilter(std::move(input), false);
return color_filter->WrapWithGPUColorFilter(std::move(input),
false);
};
contents->SetColorFilter(filter_proc);
}
Expand Down
66 changes: 52 additions & 14 deletions impeller/aiks/paint.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,32 @@ std::shared_ptr<Contents> Paint::CreateContentsForEntity(const Path& path,
std::shared_ptr<Contents> Paint::CreateContentsForGeometry(
std::shared_ptr<Geometry> geometry) const {
auto contents = color_source.GetContents(*this);

// Attempt to apply the color filter on the CPU first.
// Note: This is not just an optimization; some color sources rely on
// CPU-applied color filters to behave properly.
bool needs_color_filter = !!color_filter;
if (color_filter &&
contents->ApplyColorFilter(color_filter->GetCPUColorFilterProc())) {
needs_color_filter = false;
}

contents->SetGeometry(std::move(geometry));
if (mask_blur_descriptor.has_value()) {
return mask_blur_descriptor->CreateMaskBlur(contents);
// If there's a mask blur and we need to apply the color filter on the GPU,
// we need to be careful to only apply the color filter to the source
// colors. CreateMaskBlur is able to handle this case.
return mask_blur_descriptor->CreateMaskBlur(
contents, needs_color_filter ? color_filter : nullptr);
}

return contents;
}

std::shared_ptr<Contents> Paint::WithFilters(
std::shared_ptr<Contents> input,
std::optional<bool> is_solid_color) const {
bool is_solid_color_val = is_solid_color.value_or(color_source.GetType() ==
ColorSource::Type::kColor);
std::shared_ptr<Contents> input) const {
input = WithColorFilter(input, /*absorb_opacity=*/true);
input = WithInvertFilter(input);
input = WithMaskBlur(input, is_solid_color_val);
input = WithImageFilter(input, Matrix(), /*is_subpass=*/false);
return input;
}
Expand Down Expand Up @@ -89,11 +100,20 @@ std::shared_ptr<Contents> Paint::WithColorFilter(
if (color_source.GetType() == ColorSource::Type::kImage) {
return input;
}
if (color_filter) {
input =
color_filter->GetColorFilter(FilterInput::Make(input), absorb_opacity);

if (!color_filter) {
return input;
}
return input;

// Attempt to apply the color filter on the CPU first.
// Note: This is not just an optimization; some color sources rely on
// CPU-applied color filters to behave properly.
if (input->ApplyColorFilter(color_filter->GetCPUColorFilterProc())) {
return input;
}

return color_filter->WrapWithGPUColorFilter(FilterInput::Make(input),
absorb_opacity);
}

/// A color matrix which inverts colors.
Expand All @@ -119,7 +139,16 @@ std::shared_ptr<Contents> Paint::WithInvertFilter(
}

std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur(
std::shared_ptr<ColorSourceContents> color_source_contents) const {
std::shared_ptr<ColorSourceContents> color_source_contents,
const std::shared_ptr<ColorFilter>& color_filter) const {
// If it's a solid color and there is no color filter, then we can just get
// away with doing one Gaussian blur.
if (color_source_contents->IsSolidColor() && !color_filter) {
return FilterContents::MakeGaussianBlur(
FilterInput::Make(color_source_contents), sigma, sigma, style,
Entity::TileMode::kDecal, Matrix());
}

/// 1. Create an opaque white mask of the original geometry.

auto mask = std::make_shared<SolidColorContents>();
Expand All @@ -143,11 +172,20 @@ std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur(
color_source_contents->SetGeometry(
Geometry::MakeRect(*expanded_local_bounds));

/// 4. Composite the color source and mask together.
std::shared_ptr<Contents> color_contents = color_source_contents;

/// 4. Apply the user set color filter on the GPU, if applicable.

if (color_filter) {
color_contents = color_filter->WrapWithGPUColorFilter(
FilterInput::Make(color_source_contents), true);
}

/// 5. Composite the color source with the blurred mask.

return ColorFilterContents::MakeBlend(
BlendMode::kSourceIn, {FilterInput::Make(blurred_mask),
FilterInput::Make(color_source_contents)});
BlendMode::kSourceIn,
{FilterInput::Make(blurred_mask), FilterInput::Make(color_contents)});
}

std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur(
Expand Down
Loading