Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit ec19ef0

Browse files
committed
[Impeller] Apply color filters on the CPU for solid colors & gradients
1 parent 1e1870f commit ec19ef0

18 files changed

+155
-15
lines changed

impeller/aiks/aiks_unittests.cc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
#include "impeller/aiks/paint_pass_delegate.h"
1919
#include "impeller/aiks/testing/context_spy.h"
2020
#include "impeller/entity/contents/color_source_contents.h"
21+
#include "impeller/entity/contents/conical_gradient_contents.h"
2122
#include "impeller/entity/contents/filters/inputs/filter_input.h"
23+
#include "impeller/entity/contents/linear_gradient_contents.h"
2224
#include "impeller/entity/contents/scene_contents.h"
2325
#include "impeller/entity/contents/solid_color_contents.h"
2426
#include "impeller/entity/contents/tiled_texture_contents.h"
@@ -2867,5 +2869,35 @@ TEST_P(AiksTest, MatrixBackdropFilter) {
28672869
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
28682870
}
28692871

2872+
TEST_P(AiksTest, SolidColorApplyColorFilter) {
2873+
auto contents = SolidColorContents();
2874+
contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75));
2875+
auto result = contents.ApplyColorFilter([](const Color& color) {
2876+
return color.Blend(Color::LimeGreen().WithAlpha(0.75), BlendMode::kScreen);
2877+
});
2878+
ASSERT_TRUE(result);
2879+
ASSERT_COLOR_NEAR(contents.GetColor(),
2880+
Color(0.433247, 0.879523, 0.825324, 0.75));
2881+
}
2882+
2883+
#define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \
2884+
TEST_P(AiksTest, name##GradientApplyColorFilter) { \
2885+
auto contents = name##GradientContents(); \
2886+
contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \
2887+
auto result = contents.ApplyColorFilter([](const Color& color) { \
2888+
return color.Blend(Color::LimeGreen().WithAlpha(0.75), \
2889+
BlendMode::kScreen); \
2890+
}); \
2891+
ASSERT_TRUE(result); \
2892+
\
2893+
std::vector<Color> expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \
2894+
ASSERT_COLORS_NEAR(contents.GetColors(), expected); \
2895+
}
2896+
2897+
APPLY_COLOR_FILTER_GRADIENT_TEST(Linear);
2898+
APPLY_COLOR_FILTER_GRADIENT_TEST(Radial);
2899+
APPLY_COLOR_FILTER_GRADIENT_TEST(Conical);
2900+
APPLY_COLOR_FILTER_GRADIENT_TEST(Sweep);
2901+
28702902
} // namespace testing
28712903
} // namespace impeller

impeller/aiks/color_filter.cc

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ BlendColorFilter::BlendColorFilter(BlendMode blend_mode, Color color)
4343

4444
BlendColorFilter::~BlendColorFilter() = default;
4545

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

6868
MatrixColorFilter::~MatrixColorFilter() = default;
6969

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

9191
SrgbToLinearColorFilter::~SrgbToLinearColorFilter() = default;
9292

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

112113
LinearToSrgbColorFilter::~LinearToSrgbColorFilter() = default;
113114

114-
std::shared_ptr<ColorFilterContents> LinearToSrgbColorFilter::GetColorFilter(
115+
std::shared_ptr<ColorFilterContents>
116+
LinearToSrgbColorFilter::WrapWithGPUColorFilter(
115117
std::shared_ptr<FilterInput> input,
116118
bool absorb_opacity) const {
117119
auto filter = ColorFilterContents::MakeSrgbToLinearFilter({std::move(input)});

impeller/aiks/color_filter.h

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ struct Paint;
1717

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

2224
ColorFilter();
@@ -32,10 +34,19 @@ class ColorFilter {
3234

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

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

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

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

5263
// |ColorFilter|
53-
std::shared_ptr<ColorFilterContents> GetColorFilter(
64+
std::shared_ptr<ColorFilterContents> WrapWithGPUColorFilter(
5465
std::shared_ptr<FilterInput> input,
5566
bool absorb_opacity) const override;
5667

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

7586
// |ColorFilter|
76-
std::shared_ptr<ColorFilterContents> GetColorFilter(
87+
std::shared_ptr<ColorFilterContents> WrapWithGPUColorFilter(
7788
std::shared_ptr<FilterInput> input,
7889
bool absorb_opacity) const override;
7990

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

97108
// |ColorFilter|
98-
std::shared_ptr<ColorFilterContents> GetColorFilter(
109+
std::shared_ptr<ColorFilterContents> WrapWithGPUColorFilter(
99110
std::shared_ptr<FilterInput> input,
100111
bool absorb_opacity) const override;
101112

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

116127
// |ColorFilter|
117-
std::shared_ptr<ColorFilterContents> GetColorFilter(
128+
std::shared_ptr<ColorFilterContents> WrapWithGPUColorFilter(
118129
std::shared_ptr<FilterInput> input,
119130
bool absorb_opacity) const override;
120131

impeller/aiks/color_source.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ ColorSource ColorSource::MakeImage(std::shared_ptr<Texture> texture,
175175
if (paint.color_filter) {
176176
TiledTextureContents::ColorFilterProc filter_proc =
177177
[color_filter = paint.color_filter](FilterInput::Ref input) {
178-
return color_filter->GetColorFilter(std::move(input), false);
178+
return color_filter->WrapWithGPUColorFilter(std::move(input),
179+
false);
179180
};
180181
contents->SetColorFilter(filter_proc);
181182
}

impeller/aiks/paint.cc

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,20 @@ std::shared_ptr<Contents> Paint::WithColorFilter(
8989
if (color_source.GetType() == ColorSource::Type::kImage) {
9090
return input;
9191
}
92-
if (color_filter) {
93-
input =
94-
color_filter->GetColorFilter(FilterInput::Make(input), absorb_opacity);
92+
93+
if (!color_filter) {
94+
return input;
9595
}
96-
return input;
96+
97+
// Attempt to apply the color filter on the CPU first.
98+
// Note: This is not just an optimization; some color sources rely on
99+
// CPU-applied color filters to behave properly.
100+
if (input->ApplyColorFilter(color_filter->GetCPUColorFilterProc())) {
101+
return input;
102+
}
103+
104+
return color_filter->WrapWithGPUColorFilter(FilterInput::Make(input),
105+
absorb_opacity);
97106
}
98107

99108
/// A color matrix which inverts colors.

impeller/display_list/dl_dispatcher.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ static Paint::ImageFilterProc ToImageFilterProc(
665665
// "absorb opacity" flag to false. For image filters, the snapshot
666666
// opacity needs to be deferred until the result of the filter chain is
667667
// being blended with the layer.
668-
return filter->GetColorFilter(std::move(input), false);
668+
return filter->WrapWithGPUColorFilter(std::move(input), false);
669669
};
670670
break;
671671
}

impeller/entity/contents/conical_gradient_contents.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "impeller/entity/contents/clip_contents.h"
99
#include "impeller/entity/contents/content_context.h"
1010
#include "impeller/entity/contents/gradient_generator.h"
11+
#include "impeller/entity/contents/sweep_gradient_contents.h"
1112
#include "impeller/entity/entity.h"
1213
#include "impeller/entity/geometry/geometry.h"
1314
#include "impeller/geometry/gradient.h"
@@ -193,4 +194,12 @@ bool ConicalGradientContents::RenderTexture(const ContentContext& renderer,
193194
return true;
194195
}
195196

197+
bool ConicalGradientContents::ApplyColorFilter(
198+
const ColorFilterProc& color_filter_proc) {
199+
for (Color& color : colors_) {
200+
color = color_filter_proc(color);
201+
}
202+
return true;
203+
}
204+
196205
} // namespace impeller

impeller/entity/contents/conical_gradient_contents.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ class ConicalGradientContents final : public ColorSourceContents {
2929
const Entity& entity,
3030
RenderPass& pass) const override;
3131

32+
// |Contents|
33+
[[nodiscard]] bool ApplyColorFilter(
34+
const ColorFilterProc& color_filter_proc) override;
35+
3236
void SetCenterAndRadius(Point center, Scalar radius);
3337

3438
void SetColors(std::vector<Color> colors);

impeller/entity/contents/contents.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ std::optional<Color> Contents::AsBackgroundColor(const Entity& entity,
123123
return {};
124124
}
125125

126+
bool Contents::ApplyColorFilter(
127+
const Contents::ColorFilterProc& color_filter_proc) {
128+
return false;
129+
}
130+
126131
bool Contents::ShouldRender(const Entity& entity,
127132
const std::optional<Rect>& stencil_coverage) const {
128133
if (!stencil_coverage.has_value()) {

impeller/entity/contents/contents.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ ContentContextOptions OptionsFromPassAndEntity(const RenderPass& pass,
3030

3131
class Contents {
3232
public:
33+
/// A procedure that filters a given unpremultiplied color to produce a new
34+
/// unpremultiplied color.
35+
using ColorFilterProc = std::function<Color(Color)>;
36+
3337
struct StencilCoverage {
3438
enum class Type { kNoChange, kAppend, kRestore };
3539

@@ -126,6 +130,23 @@ class Contents {
126130
virtual std::optional<Color> AsBackgroundColor(const Entity& entity,
127131
ISize target_size) const;
128132

133+
/// @brief If possible, applies a color filter to this contents inputs on
134+
/// the CPU.
135+
///
136+
/// This method will either fully apply the color filter or
137+
/// perform no action. Partial/incorrect application of the color
138+
/// filter will never occur.
139+
///
140+
/// @param[in] color_filter_proc A function that filters a given
141+
/// unpremultiplied color to produce a new
142+
/// unpremultiplied color.
143+
///
144+
/// @return True if the color filter was able to be fully applied to all
145+
/// all relevant inputs. Otherwise, this operation is a no-op and
146+
/// false is returned.
147+
[[nodiscard]] virtual bool ApplyColorFilter(
148+
const ColorFilterProc& color_filter_proc);
149+
129150
private:
130151
std::optional<Rect> coverage_hint_;
131152
std::optional<Size> color_source_size_;

0 commit comments

Comments
 (0)