[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
This commit is contained in:
Brandon DeRosier 2023-06-26 16:37:52 -07:00 committed by GitHub
parent b62a3603c1
commit 083d8dd6d4
4 changed files with 103 additions and 225 deletions

View File

@ -2085,8 +2085,9 @@ static Picture BlendModeTest(BlendMode blend_mode,
const std::shared_ptr<Image>& src_image,
const std::shared_ptr<Image>& dst_image) {
Color destination_color = Color::CornflowerBlue().WithAlpha(0.75);
auto source_colors =
std::vector<Color>({Color::White(), Color::LimeGreen(), Color::Black()});
auto source_colors = std::vector<Color>({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);

View File

@ -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:

View File

@ -10,7 +10,9 @@
#include <cstdlib>
#include <ostream>
#include <type_traits>
#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 <class T, class = std::enable_if_t<std::is_arithmetic_v<T>>>
constexpr inline Color operator+(T value) const {
auto v = static_cast<Scalar>(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 <class T, class = std::enable_if_t<std::is_arithmetic_v<T>>>
constexpr inline Color operator-(T value) const {
auto v = static_cast<Scalar>(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 <class T, class = std::enable_if_t<std::is_arithmetic_v<T>>>
constexpr inline Color operator*(T value) const {
auto v = static_cast<Scalar>(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 <class T, class = std::enable_if_t<std::is_arithmetic_v<T>>>
constexpr inline Color operator/(T value) const {
auto v = static_cast<Scalar>(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 <class T, class = std::enable_if_t<std::is_arithmetic_v<T>>>
constexpr inline Color operator+(T value, const Color& c) {
return c + static_cast<Scalar>(value);
}
template <class T, class = std::enable_if_t<std::is_arithmetic_v<T>>>
constexpr inline Color operator-(T value, const Color& c) {
auto v = static_cast<Scalar>(value);
return {v - c.red, v - c.green, v - c.blue, v - c.alpha};
}
template <class T, class = std::enable_if_t<std::is_arithmetic_v<T>>>
constexpr inline Color operator*(T value, const Color& c) {
return c * static_cast<Scalar>(value);
}
template <class T, class = std::enable_if_t<std::is_arithmetic_v<T>>>
constexpr inline Color operator/(T value, const Color& c) {
auto v = static_cast<Scalar>(value);
return {v / c.red, v / c.green, v / c.blue, v / c.alpha};
}
std::string ColorToString(const Color& color);
/**

View File

@ -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 = {