From 083d8dd6d486bef65634e24b2cd2b3538a48bddc Mon Sep 17 00:00:00 2001 From: Brandon DeRosier Date: Mon, 26 Jun 2023 16:37:52 -0700 Subject: [PATCH] [Impeller] Fix CPU Porter-Duff blends (flutter/engine#43217) Part of https://github.com/flutter/flutter/issues/128606. * Fix all of the Porter-Duff blends (virtually all of the blends were wrong). Still working on getting all of the advanced/color blends fixed up. * Fill in missing ops for arithmetic types/remove Vector4 conversions. * Remove the original GeometryTest in favor of the much clearer goldens. Once we have all of the CPU blends matching up properly with the GPU blends, we can think about constructing a more intentional test that covers a select number of important cases with accompanying explanation. Top rectangles = GPU blends Bottom rectangles = CPU blends --- .../flutter/impeller/aiks/aiks_unittests.cc | 13 +- engine/src/flutter/impeller/geometry/color.cc | 64 ++++--- engine/src/flutter/impeller/geometry/color.h | 76 ++++++-- .../impeller/geometry/geometry_unittests.cc | 175 ------------------ 4 files changed, 103 insertions(+), 225 deletions(-) diff --git a/engine/src/flutter/impeller/aiks/aiks_unittests.cc b/engine/src/flutter/impeller/aiks/aiks_unittests.cc index 508bc23e2d0..05ebee6ccb8 100644 --- a/engine/src/flutter/impeller/aiks/aiks_unittests.cc +++ b/engine/src/flutter/impeller/aiks/aiks_unittests.cc @@ -2085,8 +2085,9 @@ static Picture BlendModeTest(BlendMode blend_mode, const std::shared_ptr& src_image, const std::shared_ptr& dst_image) { Color destination_color = Color::CornflowerBlue().WithAlpha(0.75); - auto source_colors = - std::vector({Color::White(), Color::LimeGreen(), Color::Black()}); + auto source_colors = std::vector({Color::White().WithAlpha(0.75), + Color::LimeGreen().WithAlpha(0.75), + Color::Black().WithAlpha(0.75)}); Canvas canvas; @@ -2111,7 +2112,7 @@ static Picture BlendModeTest(BlendMode blend_mode, // pass. canvas.SaveLayer({.blend_mode = blend_mode}); { // - canvas.DrawRect({50, 50, 100, 100}, {.color = color.WithAlpha(0.75)}); + canvas.DrawRect({50, 50, 100, 100}, {.color = color}); } canvas.Restore(); } @@ -2135,9 +2136,9 @@ static Picture BlendModeTest(BlendMode blend_mode, // canvas.DrawPaint({.color = destination_color}); for (auto& color : source_colors) { // Simply write the CPU blended color to the pass. - canvas.DrawRect({50, 50, 100, 100}, {.color = destination_color.Blend( - color.WithAlpha(0.75), blend_mode), - .blend_mode = BlendMode::kSource}); + canvas.DrawRect({50, 50, 100, 100}, + {.color = destination_color.Blend(color, blend_mode), + .blend_mode = BlendMode::kSourceOver}); canvas.Translate(Vector2(100, 0)); } canvas.RestoreToCount(0); diff --git a/engine/src/flutter/impeller/geometry/color.cc b/engine/src/flutter/impeller/geometry/color.cc index d4c88998b1d..79ed191c844 100644 --- a/engine/src/flutter/impeller/geometry/color.cc +++ b/engine/src/flutter/impeller/geometry/color.cc @@ -123,18 +123,6 @@ Color ColorHSB::ToRGBA() const { return Color(0, 0, 0, alpha); } -Color Color::operator+(const Color& c) const { - return Color(Vector4(*this) + Vector4(c)); -} - -Color Color::operator-(const Color& c) const { - return Color(Vector4(*this) - Vector4(c)); -} - -Color Color::operator*(Scalar value) const { - return Color(red * value, green * value, blue * value, alpha * value); -} - Color::Color(const ColorHSB& hsbColor) : Color(hsbColor.ToRGBA()) {} Color::Color(const Vector4& value) @@ -218,47 +206,57 @@ Color Color::Blend(const Color& src, BlendMode blend_mode) const { return dst; case BlendMode::kSourceOver: // r = s + (1-sa)*d - return src + dst * (1 - src.alpha); + return (src.Premultiply() + dst.Premultiply() * (1 - src.alpha)) + .Unpremultiply(); case BlendMode::kDestinationOver: // r = d + (1-da)*s - return dst + src * (1 - dst.alpha); + return (dst.Premultiply() + src.Premultiply() * (1 - dst.alpha)) + .Unpremultiply(); case BlendMode::kSourceIn: // r = s * da - return src * dst.alpha; + return (src.Premultiply() * dst.alpha).Unpremultiply(); case BlendMode::kDestinationIn: // r = d * sa - return dst * src.alpha; + return (dst.Premultiply() * src.alpha).Unpremultiply(); case BlendMode::kSourceOut: // r = s * ( 1- da) - return src * (1 - dst.alpha); + return (src.Premultiply() * (1 - dst.alpha)).Unpremultiply(); case BlendMode::kDestinationOut: // r = d * (1-sa) - return dst * (1 - src.alpha); + return (dst.Premultiply() * (1 - src.alpha)).Unpremultiply(); case BlendMode::kSourceATop: // r = s*da + d*(1-sa) - return src * dst.alpha + dst * (1 - src.alpha); + return (src.Premultiply() * dst.alpha + + dst.Premultiply() * (1 - src.alpha)) + .Unpremultiply(); case BlendMode::kDestinationATop: // r = d*sa + s*(1-da) - return dst * src.alpha + src * (1 - dst.alpha); + return (dst.Premultiply() * src.alpha + + src.Premultiply() * (1 - dst.alpha)) + .Unpremultiply(); case BlendMode::kXor: // r = s*(1-da) + d*(1-sa) - return src * (1 - dst.alpha) + dst * (1 - src.alpha); + return (src.Premultiply() * (1 - dst.alpha) + + dst.Premultiply() * (1 - src.alpha)) + .Unpremultiply(); case BlendMode::kPlus: // r = min(s + d, 1) - return Min(src + dst, 1); + return (Min(src.Premultiply() + dst.Premultiply(), 1)).Unpremultiply(); case BlendMode::kModulate: // r = s*d - return src * dst; + return (src.Premultiply() * dst.Premultiply()).Unpremultiply(); case BlendMode::kScreen: { // r = s + d - s*d - return src + dst - src * dst; + auto s = src.Premultiply(); + auto d = dst.Premultiply(); + return (s + d - s * d).Unpremultiply(); } case BlendMode::kOverlay: return apply_rgb_srcover_alpha([&](auto s, auto d) { - if (d * 2 < dst.alpha) { + if (d * 2 <= dst.alpha) { return 2 * s * d; } - return src.alpha * dst.alpha - 2 * (dst.alpha - s) * (src.alpha - d); + return src.alpha * dst.alpha - 2 * (dst.alpha - d) * (src.alpha - s); }); case BlendMode::kDarken: { return apply_rgb_srcover_alpha([&](auto s, auto d) { @@ -313,13 +311,13 @@ Color Color::Blend(const Color& src, BlendMode blend_mode) const { std::sqrt(dst_rgb.z)), // dst_rgb, // 0.25); - Color blended = - FromRGB(ComponentChoose( - dst_rgb - (1.0 - 2.0 * src) * dst * (1.0 - dst_rgb), // - dst_rgb + (2.0 * src_rgb - 1.0) * (d - dst_rgb), // - src_rgb, // - 0.5), - dst.alpha); + Color blended = FromRGB( + ComponentChoose( + dst_rgb - (1.0 - 2.0 * src_rgb) * dst_rgb * (1.0 - dst_rgb), // + dst_rgb + (2.0 * src_rgb - 1.0) * (d - dst_rgb), // + src_rgb, // + 0.5), + dst.alpha); return blended + dst * (1 - blended.alpha); } case BlendMode::kDifference: diff --git a/engine/src/flutter/impeller/geometry/color.h b/engine/src/flutter/impeller/geometry/color.h index 7991c6bc38a..77bb9790d76 100644 --- a/engine/src/flutter/impeller/geometry/color.h +++ b/engine/src/flutter/impeller/geometry/color.h @@ -10,7 +10,9 @@ #include #include #include + #include "impeller/geometry/scalar.h" +#include "impeller/geometry/type_traits.h" #define IMPELLER_FOR_EACH_BLEND_MODE(V) \ V(Clear) \ @@ -162,11 +164,51 @@ struct Color { 0xFFFFFFFF; } - constexpr bool operator==(const Color& c) const { + constexpr inline bool operator==(const Color& c) const { return ScalarNearlyEqual(red, c.red) && ScalarNearlyEqual(green, c.green) && ScalarNearlyEqual(blue, c.blue) && ScalarNearlyEqual(alpha, c.alpha); } + constexpr inline Color operator+(const Color& c) const { + return {red + c.red, green + c.green, blue + c.blue, alpha + c.alpha}; + } + + template >> + constexpr inline Color operator+(T value) const { + auto v = static_cast(value); + return {red + v, green + v, blue + v, alpha + v}; + } + + constexpr inline Color operator-(const Color& c) const { + return {red - c.red, green - c.green, blue - c.blue, alpha - c.alpha}; + } + + template >> + constexpr inline Color operator-(T value) const { + auto v = static_cast(value); + return {red - v, green - v, blue - v, alpha - v}; + } + + constexpr inline Color operator*(const Color& c) const { + return {red * c.red, green * c.green, blue * c.blue, alpha * c.alpha}; + } + + template >> + constexpr inline Color operator*(T value) const { + auto v = static_cast(value); + return {red * v, green * v, blue * v, alpha * v}; + } + + constexpr inline Color operator/(const Color& c) const { + return {red * c.red, green * c.green, blue * c.blue, alpha * c.alpha}; + } + + template >> + constexpr inline Color operator/(T value) const { + auto v = static_cast(value); + return {red / v, green / v, blue / v, alpha / v}; + } + constexpr Color Premultiply() const { return {red * alpha, green * alpha, blue * alpha, alpha}; } @@ -835,21 +877,33 @@ struct Color { /// premultipled, the conversion output will be incorrect. Color SRGBToLinear() const; - Color operator*(const Color& c) const { - return Color(red * c.red, green * c.green, blue * c.blue, alpha * c.alpha); - } - - Color operator+(const Color& c) const; - - Color operator-(const Color& c) const; - - Color operator*(Scalar value) const; - constexpr bool IsTransparent() const { return alpha == 0.0f; } constexpr bool IsOpaque() const { return alpha == 1.0f; } }; +template >> +constexpr inline Color operator+(T value, const Color& c) { + return c + static_cast(value); +} + +template >> +constexpr inline Color operator-(T value, const Color& c) { + auto v = static_cast(value); + return {v - c.red, v - c.green, v - c.blue, v - c.alpha}; +} + +template >> +constexpr inline Color operator*(T value, const Color& c) { + return c * static_cast(value); +} + +template >> +constexpr inline Color operator/(T value, const Color& c) { + auto v = static_cast(value); + return {v / c.red, v / c.green, v / c.blue, v / c.alpha}; +} + std::string ColorToString(const Color& color); /** diff --git a/engine/src/flutter/impeller/geometry/geometry_unittests.cc b/engine/src/flutter/impeller/geometry/geometry_unittests.cc index 3a123c657ce..2b9875bf67b 100644 --- a/engine/src/flutter/impeller/geometry/geometry_unittests.cc +++ b/engine/src/flutter/impeller/geometry/geometry_unittests.cc @@ -1479,181 +1479,6 @@ TEST(GeometryTest, ColorMakeRGBA8) { } } -TEST(GeometryTest, ColorBlend) { - { - Color src = {1, 0, 0, 0.5}; - Color dst = {1, 0, 1, 1}; - ASSERT_EQ(dst.Blend(src, BlendMode::kClear), Color(0, 0, 0, 0)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSource), Color(1, 0, 0, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestination), Color(1, 0, 1, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOver), Color(1.5, 0, 0.5, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOver), Color(1, 0, 1, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceIn), Color(1, 0, 0, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationIn), - Color(0.5, 0, 0.5, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOut), Color(0, 0, 0, 0)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOut), - Color(0.5, 0, 0.5, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceATop), Color(1.5, 0, 0.5, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationATop), - Color(0.5, 0, 0.5, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kXor), Color(0.5, 0, 0.5, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kPlus), Color(1, 0, 1, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kModulate), Color(1, 0, 0, 0.5)); - } - - { - Color src = {1, 1, 0, 1}; - Color dst = {1, 0, 1, 1}; - - ASSERT_EQ(dst.Blend(src, BlendMode::kClear), Color(0, 0, 0, 0)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSource), Color(1, 1, 0, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestination), Color(1, 0, 1, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOver), Color(1, 1, 0, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOver), Color(1, 0, 1, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceIn), Color(1, 1, 0, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationIn), Color(1, 0, 1, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOut), Color(0, 0, 0, 0)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOut), Color(0, 0, 0, 0)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceATop), Color(1, 1, 0, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationATop), Color(1, 0, 1, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kXor), Color(0, 0, 0, 0)); - ASSERT_EQ(dst.Blend(src, BlendMode::kPlus), Color(1, 1, 1, 1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kModulate), Color(1, 0, 0, 1)); - } - - { - Color src = {1, 1, 0, 0.2}; - Color dst = {1, 1, 1, 0.5}; - - ASSERT_EQ(dst.Blend(src, BlendMode::kClear), Color(0, 0, 0, 0)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSource), Color(1, 1, 0, 0.2)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestination), Color(1, 1, 1, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOver), - Color(1.8, 1.8, 0.8, 0.6)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOver), - Color(1.5, 1.5, 1, 0.6)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceIn), Color(0.5, 0.5, 0, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationIn), - Color(0.2, 0.2, 0.2, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOut), Color(0.5, 0.5, 0, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOut), - Color(0.8, 0.8, 0.8, 0.4)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceATop), - Color(1.3, 1.3, 0.8, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationATop), - Color(0.7, 0.7, 0.2, 0.2)); - ASSERT_EQ(dst.Blend(src, BlendMode::kXor), Color(1.3, 1.3, 0.8, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kPlus), Color(1, 1, 1, 0.7)); - ASSERT_EQ(dst.Blend(src, BlendMode::kModulate), Color(1, 1, 0, 0.1)); - } - - { - Color src = {1, 0.5, 0, 0.2}; - Color dst = {1, 1, 0.5, 0.5}; - ASSERT_EQ(dst.Blend(src, BlendMode::kClear), Color(0, 0, 0, 0)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSource), Color(1, 0.5, 0, 0.2)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestination), Color(1, 1, 0.5, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOver), - Color(1.8, 1.3, 0.4, 0.6)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOver), - Color(1.5, 1.25, 0.5, 0.6)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceIn), Color(0.5, 0.25, 0, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationIn), - Color(0.2, 0.2, 0.1, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOut), Color(0.5, 0.25, 0, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOut), - Color(0.8, 0.8, 0.4, 0.4)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceATop), - Color(1.3, 1.05, 0.4, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationATop), - Color(0.7, 0.45, 0.1, 0.2)); - ASSERT_EQ(dst.Blend(src, BlendMode::kXor), Color(1.3, 1.05, 0.4, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kPlus), Color(1, 1, 0.5, 0.7)); - ASSERT_EQ(dst.Blend(src, BlendMode::kModulate), Color(1, 0.5, 0, 0.1)); - } - - { - Color src = {0.5, 0.5, 0, 0.2}; - Color dst = {0, 1, 0.5, 0.5}; - ASSERT_EQ(dst.Blend(src, BlendMode::kClear), Color(0, 0, 0, 0)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSource), Color(0.5, 0.5, 0, 0.2)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestination), Color(0, 1, 0.5, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOver), - Color(0.5, 1.3, 0.4, 0.6)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOver), - Color(0.25, 1.25, 0.5, 0.6)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceIn), Color(0.25, 0.25, 0, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationIn), - Color(0, 0.2, 0.1, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOut), Color(0.25, 0.25, 0, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOut), - Color(0, 0.8, 0.4, 0.4)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceATop), - Color(0.25, 1.05, 0.4, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationATop), - Color(0.25, 0.45, 0.1, 0.2)); - ASSERT_EQ(dst.Blend(src, BlendMode::kXor), Color(0.25, 1.05, 0.4, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kPlus), Color(0.5, 1, 0.5, 0.7)); - ASSERT_EQ(dst.Blend(src, BlendMode::kModulate), Color(0, 0.5, 0, 0.1)); - } - - { - Color src = {0.5, 0.5, 0.2, 0.2}; - Color dst = {0.2, 1, 0.5, 0.5}; - ASSERT_EQ(dst.Blend(src, BlendMode::kClear), Color(0, 0, 0, 0)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSource), Color(0.5, 0.5, 0.2, 0.2)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestination), Color(0.2, 1, 0.5, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOver), - Color(0.66, 1.3, 0.6, 0.6)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOver), - Color(0.45, 1.25, 0.6, 0.6)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceIn), - Color(0.25, 0.25, 0.1, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationIn), - Color(0.04, 0.2, 0.1, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOut), - Color(0.25, 0.25, 0.1, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOut), - Color(0.16, 0.8, 0.4, 0.4)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceATop), - Color(0.41, 1.05, 0.5, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationATop), - Color(0.29, 0.45, 0.2, 0.2)); - ASSERT_EQ(dst.Blend(src, BlendMode::kXor), Color(0.41, 1.05, 0.5, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kPlus), Color(0.7, 1, 0.7, 0.7)); - ASSERT_EQ(dst.Blend(src, BlendMode::kModulate), Color(0.1, 0.5, 0.1, 0.1)); - } - - { - Color src = {0.5, 0.5, 0.2, 0.2}; - Color dst = {0.2, 0.2, 0.5, 0.5}; - ASSERT_EQ(dst.Blend(src, BlendMode::kClear), Color(0, 0, 0, 0)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSource), Color(0.5, 0.5, 0.2, 0.2)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestination), - Color(0.2, 0.2, 0.5, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOver), - Color(0.66, 0.66, 0.6, 0.6)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOver), - Color(0.45, 0.45, 0.6, 0.6)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceIn), - Color(0.25, 0.25, 0.1, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationIn), - Color(0.04, 0.04, 0.1, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceOut), - Color(0.25, 0.25, 0.1, 0.1)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationOut), - Color(0.16, 0.16, 0.4, 0.4)); - ASSERT_EQ(dst.Blend(src, BlendMode::kSourceATop), - Color(0.41, 0.41, 0.5, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kDestinationATop), - Color(0.29, 0.29, 0.2, 0.2)); - ASSERT_EQ(dst.Blend(src, BlendMode::kXor), Color(0.41, 0.41, 0.5, 0.5)); - ASSERT_EQ(dst.Blend(src, BlendMode::kPlus), Color(0.7, 0.7, 0.7, 0.7)); - ASSERT_EQ(dst.Blend(src, BlendMode::kModulate), Color(0.1, 0.1, 0.1, 0.1)); - } -} - TEST(GeometryTest, ColorApplyColorMatrix) { { ColorMatrix color_matrix = {