Skip to content

Commit 2342998

Browse files
Ink Open Sourcecopybara-github
authored andcommitted
Add ColorFunction class
PiperOrigin-RevId: 796435459
1 parent 308fe61 commit 2342998

19 files changed

+611
-18
lines changed

ink/brush/BUILD.bazel

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ cc_library(
3131
":brush_family",
3232
":brush_paint",
3333
":brush_tip",
34+
":color_function",
3435
":easing_function",
3536
"//:gtest_for_library_testonly",
3637
"//ink/geometry:type_matchers",
@@ -175,6 +176,32 @@ cc_test(
175176
],
176177
)
177178

179+
cc_library(
180+
name = "color_function",
181+
srcs = ["color_function.cc"],
182+
hdrs = ["color_function.h"],
183+
deps = [
184+
"//ink/color",
185+
"@com_google_absl//absl/status",
186+
"@com_google_absl//absl/strings",
187+
],
188+
)
189+
190+
cc_test(
191+
name = "color_function_test",
192+
srcs = ["color_function_test.cc"],
193+
deps = [
194+
":color_function",
195+
":fuzz_domains",
196+
"//ink/color",
197+
"@com_google_absl//absl/status",
198+
"@com_google_absl//absl/status:status_matchers",
199+
"@com_google_absl//absl/strings",
200+
"@com_google_fuzztest//fuzztest",
201+
"@com_google_fuzztest//fuzztest:fuzztest_gtest_main",
202+
],
203+
)
204+
178205
cc_library(
179206
name = "easing_function",
180207
srcs = ["easing_function.cc"],
@@ -233,6 +260,7 @@ cc_library(
233260
srcs = ["brush_paint.cc"],
234261
hdrs = ["brush_paint.h"],
235262
deps = [
263+
":color_function",
236264
"//ink/geometry:angle",
237265
"//ink/geometry:vec",
238266
"@com_google_absl//absl/status",
@@ -247,7 +275,9 @@ cc_test(
247275
srcs = ["brush_paint_test.cc"],
248276
deps = [
249277
":brush_paint",
278+
":color_function",
250279
":fuzz_domains",
280+
"//ink/color",
251281
"//ink/geometry:angle",
252282
"//ink/geometry:vec",
253283
"@com_google_absl//absl/hash:hash_testing",
@@ -272,6 +302,7 @@ cc_library(
272302
":brush_family",
273303
":brush_paint",
274304
":brush_tip",
305+
":color_function",
275306
":easing_function",
276307
"//ink/color",
277308
"//ink/color:fuzz_domains",

ink/brush/brush_coat_test.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ constexpr absl::string_view kTestTextureId = "test-paint";
4141
TEST(BrushCoatTest, Stringify) {
4242
EXPECT_EQ(absl::StrCat(BrushCoat{.tip = BrushTip{}}),
4343
"BrushCoat{tip=BrushTip{scale=<1, 1>, corner_rounding=1}, "
44-
"paint=BrushPaint{texture_layers={}}}");
44+
"paint=BrushPaint{}}");
4545
}
4646

4747
TEST(BrushCoatTest, CoatWithDefaultTipAndPaintIsValid) {

ink/brush/brush_paint.cc

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "absl/strings/str_format.h"
2323
#include "absl/strings/str_join.h"
2424
#include "absl/time/time.h"
25+
#include "ink/brush/color_function.h"
2526

2627
namespace ink {
2728
namespace brush_internal {
@@ -309,6 +310,12 @@ absl::Status ValidateBrushPaint(const BrushPaint& paint) {
309310
return status;
310311
}
311312
}
313+
for (const ColorFunction& color_function : paint.color_functions) {
314+
if (absl::Status status = ValidateColorFunction(color_function);
315+
!status.ok()) {
316+
return status;
317+
}
318+
}
312319
if (absl::Status status = ValidateBrushPaintTopLevel(paint); !status.ok()) {
313320
return status;
314321
}
@@ -436,10 +443,19 @@ std::string ToFormattedString(const BrushPaint::TextureLayer& texture_layer) {
436443
}
437444

438445
std::string ToFormattedString(const BrushPaint& paint) {
439-
std::string formatted =
440-
absl::StrCat("BrushPaint{texture_layers={",
441-
absl::StrJoin(paint.texture_layers, ", "), "}}");
442-
446+
std::string formatted = "BrushPaint{";
447+
if (!paint.texture_layers.empty()) {
448+
absl::StrAppend(&formatted, "texture_layers={",
449+
absl::StrJoin(paint.texture_layers, ", "), "}");
450+
}
451+
if (!paint.color_functions.empty()) {
452+
if (!paint.texture_layers.empty()) {
453+
absl::StrAppend(&formatted, ", ");
454+
}
455+
absl::StrAppend(&formatted, "color_functions={",
456+
absl::StrJoin(paint.color_functions, ", "), "}");
457+
}
458+
absl::StrAppend(&formatted, "}");
443459
return formatted;
444460
}
445461

ink/brush/brush_paint.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include "absl/status/status.h"
2323
#include "absl/time/time.h"
24+
#include "ink/brush/color_function.h"
2425
#include "ink/geometry/angle.h"
2526
#include "ink/geometry/vec.h"
2627

@@ -299,6 +300,10 @@ struct BrushPaint {
299300
};
300301

301302
std::vector<TextureLayer> texture_layers;
303+
// Transformations to apply to the base brush color (in order) before drawing
304+
// this coat of paint. When this list is empty, the base brush color will be
305+
// used unchanged.
306+
std::vector<ColorFunction> color_functions;
302307
};
303308

304309
bool operator==(const BrushPaint::TextureKeyframe& lhs,
@@ -395,7 +400,7 @@ H AbslHashValue(H h, const BrushPaint::TextureLayer& layer) {
395400

396401
template <typename H>
397402
H AbslHashValue(H h, const BrushPaint& paint) {
398-
return H::combine(std::move(h), paint.texture_layers);
403+
return H::combine(std::move(h), paint.texture_layers, paint.color_functions);
399404
}
400405

401406
inline bool operator!=(const BrushPaint::TextureKeyframe& lhs,
@@ -409,7 +414,8 @@ inline bool operator!=(const BrushPaint::TextureLayer& lhs,
409414
}
410415

411416
inline bool operator==(const BrushPaint& lhs, const BrushPaint& rhs) {
412-
return lhs.texture_layers == rhs.texture_layers;
417+
return lhs.texture_layers == rhs.texture_layers &&
418+
lhs.color_functions == rhs.color_functions;
413419
}
414420

415421
inline bool operator!=(const BrushPaint& lhs, const BrushPaint& rhs) {

ink/brush/brush_paint_test.cc

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
#include "absl/strings/str_cat.h"
2828
#include "absl/strings/string_view.h"
2929
#include "absl/time/time.h"
30+
#include "ink/brush/color_function.h"
3031
#include "ink/brush/fuzz_domains.h"
32+
#include "ink/color/color.h"
3133
#include "ink/geometry/angle.h"
3234
#include "ink/geometry/vec.h"
3335

@@ -94,8 +96,16 @@ TEST(BrushPaintTest, BrushPaintSupportsAbslHash) {
9496
std::string id2 = "bar";
9597
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({
9698
BrushPaint{},
97-
BrushPaint{{{.client_texture_id = id1}}},
98-
BrushPaint{{{.client_texture_id = id1}, {.client_texture_id = id2}}},
99+
BrushPaint{.texture_layers = {{.client_texture_id = id1}}},
100+
BrushPaint{.texture_layers = {{.client_texture_id = id2}}},
101+
BrushPaint{.texture_layers = {{.client_texture_id = id1},
102+
{.client_texture_id = id2}}},
103+
BrushPaint{.color_functions = {{ColorFunction::OpacityMultiplier{0.5}}}},
104+
BrushPaint{
105+
.color_functions = {{ColorFunction::ReplaceColor{Color::Red()}}}},
106+
BrushPaint{
107+
.texture_layers = {{.client_texture_id = id1}},
108+
.color_functions = {{ColorFunction::ReplaceColor{Color::Red()}}}},
99109
}));
100110
}
101111

@@ -417,7 +427,7 @@ TEST(BrushPaintTest, StringifyTextureLayer) {
417427
}
418428

419429
TEST(BrushPaintTest, StringifyBrushPaint) {
420-
EXPECT_EQ(absl::StrCat(BrushPaint{}), "BrushPaint{texture_layers={}}");
430+
EXPECT_EQ(absl::StrCat(BrushPaint{}), "BrushPaint{}");
421431
EXPECT_EQ(
422432
absl::StrCat(BrushPaint{.texture_layers = {{}}}),
423433
"BrushPaint{texture_layers={TextureLayer{client_texture_id=, "
@@ -628,6 +638,21 @@ TEST(BrushPaintTest, StringifyBrushPaint) {
628638
"keyframes={TextureKeyframe{progress=0.2, size=<2, 5>, rotation=0.125π}, "
629639
"TextureKeyframe{progress=0.4, offset=<2, 0.2>, opacity=0.4}}, "
630640
"blend_mode=kDstIn}}}");
641+
EXPECT_EQ(absl::StrCat(BrushPaint{
642+
.color_functions = {{ColorFunction::OpacityMultiplier{0.5}}}}),
643+
"BrushPaint{color_functions={OpacityMultiplier{0.5}}}");
644+
EXPECT_EQ(absl::StrCat(BrushPaint{
645+
.texture_layers = {{.client_texture_id =
646+
std::string(kTestTextureId)}},
647+
.color_functions = {{ColorFunction::OpacityMultiplier{0.5}}}}),
648+
"BrushPaint{texture_layers={TextureLayer{client_texture_id=test-"
649+
"texture, mapping=kTiling, origin=kStrokeSpaceOrigin, "
650+
"size_unit=kStrokeCoordinates, wrap_x=kRepeat, wrap_y=kRepeat, "
651+
"size=<1, 1>, offset=<0, 0>, rotation=0π, size_jitter=<0, 0>, "
652+
"offset_jitter=<0, 0>, rotation_jitter=0π, opacity=1, "
653+
"animation_frames=1, animation_rows=1, animation_columns=1, "
654+
"animation_duration=1s, keyframes={}, blend_mode=kModulate}}, "
655+
"color_functions={OpacityMultiplier{0.5}}}");
631656
}
632657

633658
TEST(BrushPaintTest, InvalidTextureLayerRotation) {

ink/brush/color_function.cc

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "ink/brush/color_function.h"
16+
17+
#include <cmath>
18+
#include <string>
19+
#include <variant>
20+
21+
#include "absl/status/status.h"
22+
#include "absl/strings/str_cat.h"
23+
24+
namespace ink {
25+
26+
bool ColorFunction::operator==(const ColorFunction& other) const {
27+
return parameters == other.parameters;
28+
}
29+
30+
bool ColorFunction::operator!=(const ColorFunction& other) const {
31+
return !(*this == other);
32+
}
33+
34+
bool ColorFunction::OpacityMultiplier::operator==(
35+
const OpacityMultiplier& other) const {
36+
return multiplier == other.multiplier;
37+
}
38+
39+
bool ColorFunction::OpacityMultiplier::operator!=(
40+
const ColorFunction::OpacityMultiplier& other) const {
41+
return !(*this == other);
42+
}
43+
44+
bool ColorFunction::ReplaceColor::operator==(const ReplaceColor& other) const {
45+
return color == other.color;
46+
}
47+
48+
bool ColorFunction::ReplaceColor::operator!=(
49+
const ColorFunction::ReplaceColor& other) const {
50+
return !(*this == other);
51+
}
52+
53+
namespace brush_internal {
54+
namespace {
55+
56+
absl::Status ValidateColorFunctionParameters(
57+
const ColorFunction::OpacityMultiplier& opacity) {
58+
if (!std::isfinite(opacity.multiplier) || opacity.multiplier < 0) {
59+
return absl::InvalidArgumentError(
60+
absl::StrCat("`ColorFunction::OpacityMultiplier::multiplier` must be "
61+
"finite and non-negative, got: ",
62+
opacity.multiplier));
63+
}
64+
return absl::OkStatus();
65+
}
66+
67+
absl::Status ValidateColorFunctionParameters(
68+
const ColorFunction::ReplaceColor& replace) {
69+
return absl::OkStatus();
70+
}
71+
72+
} // namespace
73+
74+
absl::Status ValidateColorFunction(const ColorFunction& color_function) {
75+
return std::visit(
76+
[](const auto& params) {
77+
return ValidateColorFunctionParameters(params);
78+
},
79+
color_function.parameters);
80+
}
81+
82+
std::string ToFormattedString(const ColorFunction& color_function) {
83+
return ToFormattedString(color_function.parameters);
84+
}
85+
86+
std::string ToFormattedString(const ColorFunction::Parameters& parameters) {
87+
return std::visit(
88+
[](const auto& params) { return ToFormattedString(params); }, parameters);
89+
}
90+
91+
std::string ToFormattedString(const ColorFunction::OpacityMultiplier& opacity) {
92+
return absl::StrCat("OpacityMultiplier{", opacity.multiplier, "}");
93+
}
94+
95+
std::string ToFormattedString(const ColorFunction::ReplaceColor& replace) {
96+
return absl::StrCat("ReplaceColor{", replace.color, "}");
97+
}
98+
99+
} // namespace brush_internal
100+
} // namespace ink

0 commit comments

Comments
 (0)