| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "impeller/geometry/color.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <functional> |
| #include <sstream> |
| #include <type_traits> |
| |
| #include "impeller/base/strings.h" |
| #include "impeller/geometry/constants.h" |
| #include "impeller/geometry/scalar.h" |
| #include "impeller/geometry/vector.h" |
| |
| namespace impeller { |
| |
| #define _IMPELLER_ASSERT_BLEND_MODE(blend_mode) \ |
| auto enum_##blend_mode = static_cast<std::underlying_type_t<BlendMode>>( \ |
| BlendMode::k##blend_mode); \ |
| if (i != enum_##blend_mode) { \ |
| return false; \ |
| } \ |
| ++i; |
| |
| static constexpr inline bool ValidateBlendModes() { |
| std::underlying_type_t<BlendMode> i = 0; |
| // Ensure the order of the blend modes match. |
| IMPELLER_FOR_EACH_BLEND_MODE(_IMPELLER_ASSERT_BLEND_MODE) |
| // Ensure the total number of blend modes match. |
| if (i - 1 != |
| static_cast<std::underlying_type_t<BlendMode>>(BlendMode::kLast)) { |
| return false; |
| } |
| return true; |
| } |
| static_assert(ValidateBlendModes(), |
| "IMPELLER_FOR_EACH_BLEND_MODE must match impeller::BlendMode."); |
| |
| #define _IMPELLER_BLEND_MODE_NAME_LIST(blend_mode) #blend_mode, |
| |
| static constexpr const char* kBlendModeNames[] = { |
| IMPELLER_FOR_EACH_BLEND_MODE(_IMPELLER_BLEND_MODE_NAME_LIST)}; |
| |
| const char* BlendModeToString(BlendMode blend_mode) { |
| return kBlendModeNames[static_cast<std::underlying_type_t<BlendMode>>( |
| blend_mode)]; |
| } |
| |
| ColorHSB ColorHSB::FromRGB(Color rgb) { |
| Scalar R = rgb.red; |
| Scalar G = rgb.green; |
| Scalar B = rgb.blue; |
| |
| Scalar v = 0.0; |
| Scalar x = 0.0; |
| Scalar f = 0.0; |
| |
| int64_t i = 0; |
| |
| x = fmin(R, G); |
| x = fmin(x, B); |
| |
| v = fmax(R, G); |
| v = fmax(v, B); |
| |
| if (v == x) { |
| return ColorHSB(0.0, 0.0, v, rgb.alpha); |
| } |
| |
| f = (R == x) ? G - B : ((G == x) ? B - R : R - G); |
| i = (R == x) ? 3 : ((G == x) ? 5 : 1); |
| |
| return ColorHSB(((i - f / (v - x)) / 6.0), (v - x) / v, v, rgb.alpha); |
| } |
| |
| Color ColorHSB::ToRGBA() const { |
| Scalar h = hue * 6.0; |
| Scalar s = saturation; |
| Scalar v = brightness; |
| |
| Scalar m = 0.0; |
| Scalar n = 0.0; |
| Scalar f = 0.0; |
| |
| int64_t i = 0; |
| |
| if (h == 0) { |
| h = 0.01; |
| } |
| |
| if (h == 0.0) { |
| return Color(v, v, v, alpha); |
| } |
| |
| i = static_cast<int64_t>(floor(h)); |
| |
| f = h - i; |
| |
| if (!(i & 1)) { |
| f = 1 - f; |
| } |
| |
| m = v * (1 - s); |
| n = v * (1 - s * f); |
| |
| switch (i) { |
| case 6: |
| case 0: |
| return Color(v, n, m, alpha); |
| case 1: |
| return Color(n, v, m, alpha); |
| case 2: |
| return Color(m, v, n, alpha); |
| case 3: |
| return Color(m, n, v, alpha); |
| case 4: |
| return Color(n, m, v, alpha); |
| case 5: |
| return Color(v, m, n, alpha); |
| } |
| return Color(0, 0, 0, alpha); |
| } |
| |
| Color::Color(const ColorHSB& hsbColor) : Color(hsbColor.ToRGBA()) {} |
| |
| Color::Color(const Vector4& value) |
| : red(value.x), green(value.y), blue(value.z), alpha(value.w) {} |
| |
| static constexpr inline Color Min(Color c, float threshold) { |
| return Color(std::min(c.red, threshold), std::min(c.green, threshold), |
| std::min(c.blue, threshold), std::min(c.alpha, threshold)); |
| } |
| |
| // The following HSV utilities correspond to the W3C blend definitions |
| // implemented in: impeller/compiler/shader_lib/impeller/blending.glsl |
| |
| static constexpr inline Scalar Luminosity(Vector3 color) { |
| return color.x * 0.3f + color.y * 0.59f + color.z * 0.11f; |
| } |
| |
| static constexpr inline Vector3 ClipColor(Vector3 color) { |
| Scalar lum = Luminosity(color); |
| Scalar mn = std::min(std::min(color.x, color.y), color.z); |
| Scalar mx = std::max(std::max(color.x, color.y), color.z); |
| // `lum - mn` and `mx - lum` will always be >= 0 in the following conditions, |
| // so adding a tiny value is enough to make these divisions safe. |
| if (mn < 0.0f) { |
| color = lum + (((color - lum) * lum) / (lum - mn + kEhCloseEnough)); |
| } |
| if (mx > 1.0) { |
| color = |
| lum + (((color - lum) * (1.0f - lum)) / (mx - lum + kEhCloseEnough)); |
| } |
| return color; |
| } |
| |
| static constexpr inline Vector3 SetLuminosity(Vector3 color, |
| Scalar luminosity) { |
| Scalar relative_lum = luminosity - Luminosity(color); |
| return ClipColor(color + relative_lum); |
| } |
| |
| static constexpr inline Scalar Saturation(Vector3 color) { |
| return std::max(std::max(color.x, color.y), color.z) - |
| std::min(std::min(color.x, color.y), color.z); |
| } |
| |
| static constexpr inline Vector3 SetSaturation(Vector3 color, |
| Scalar saturation) { |
| Scalar mn = std::min(std::min(color.x, color.y), color.z); |
| Scalar mx = std::max(std::max(color.x, color.y), color.z); |
| return (mn < mx) ? ((color - mn) * saturation) / (mx - mn) : Vector3(); |
| } |
| |
| static constexpr inline Vector3 ComponentChoose(Vector3 a, |
| Vector3 b, |
| Vector3 value, |
| Scalar cutoff) { |
| return Vector3(value.x > cutoff ? b.x : a.x, // |
| value.y > cutoff ? b.y : a.y, // |
| value.z > cutoff ? b.z : a.z // |
| ); |
| } |
| |
| static constexpr inline Vector3 ToRGB(Color color) { |
| return {color.red, color.green, color.blue}; |
| } |
| |
| static constexpr inline Color FromRGB(Vector3 color, Scalar alpha) { |
| return {color.x, color.y, color.z, alpha}; |
| } |
| |
| /// Composite a blended color onto the destination. |
| /// All three parameters are unpremultiplied. Returns a premultiplied result. |
| /// |
| /// This routine is the same as `IPApplyBlendedColor` in the Impeller shader |
| /// library. |
| static constexpr inline Color ApplyBlendedColor(Color dst, |
| Color src, |
| Vector3 blend_result) { |
| dst = dst.Premultiply(); |
| src = |
| // Use the blended color for areas where the source and destination |
| // colors overlap. |
| FromRGB(blend_result, src.alpha * dst.alpha).Premultiply() + |
| // Use the original source color for any remaining non-overlapping areas. |
| src.Premultiply() * (1.0f - dst.alpha); |
| |
| // Source-over composite the blended source color atop the destination. |
| return src + dst * (1.0f - src.alpha); |
| } |
| |
| static constexpr inline Color DoColorBlend( |
| Color dst, |
| Color src, |
| const std::function<Vector3(Vector3, Vector3)>& blend_rgb_func) { |
| const Vector3 blend_result = blend_rgb_func(ToRGB(dst), ToRGB(src)); |
| return ApplyBlendedColor(dst, src, blend_result).Unpremultiply(); |
| } |
| |
| static constexpr inline Color DoColorBlendComponents( |
| Color dst, |
| Color src, |
| const std::function<Scalar(Scalar, Scalar)>& blend_func) { |
| Vector3 blend_result = Vector3(blend_func(dst.red, src.red), // |
| blend_func(dst.green, src.green), // |
| blend_func(dst.blue, src.blue)); // |
| return ApplyBlendedColor(dst, src, blend_result).Unpremultiply(); |
| } |
| |
| Color Color::Blend(Color src, BlendMode blend_mode) const { |
| Color dst = *this; |
| |
| switch (blend_mode) { |
| case BlendMode::kClear: |
| return Color::BlackTransparent(); |
| case BlendMode::kSource: |
| return src; |
| case BlendMode::kDestination: |
| return dst; |
| case BlendMode::kSourceOver: |
| // r = s + (1-sa)*d |
| return (src.Premultiply() + dst.Premultiply() * (1 - src.alpha)) |
| .Unpremultiply(); |
| case BlendMode::kDestinationOver: |
| // r = d + (1-da)*s |
| return (dst.Premultiply() + src.Premultiply() * (1 - dst.alpha)) |
| .Unpremultiply(); |
| case BlendMode::kSourceIn: |
| // r = s * da |
| return (src.Premultiply() * dst.alpha).Unpremultiply(); |
| case BlendMode::kDestinationIn: |
| // r = d * sa |
| return (dst.Premultiply() * src.alpha).Unpremultiply(); |
| case BlendMode::kSourceOut: |
| // r = s * ( 1- da) |
| return (src.Premultiply() * (1 - dst.alpha)).Unpremultiply(); |
| case BlendMode::kDestinationOut: |
| // r = d * (1-sa) |
| return (dst.Premultiply() * (1 - src.alpha)).Unpremultiply(); |
| case BlendMode::kSourceATop: |
| // r = s*da + d*(1-sa) |
| return (src.Premultiply() * dst.alpha + |
| dst.Premultiply() * (1 - src.alpha)) |
| .Unpremultiply(); |
| case BlendMode::kDestinationATop: |
| // r = d*sa + s*(1-da) |
| return (dst.Premultiply() * src.alpha + |
| src.Premultiply() * (1 - dst.alpha)) |
| .Unpremultiply(); |
| case BlendMode::kXor: |
| // r = s*(1-da) + d*(1-sa) |
| return (src.Premultiply() * (1 - dst.alpha) + |
| dst.Premultiply() * (1 - src.alpha)) |
| .Unpremultiply(); |
| case BlendMode::kPlus: |
| // r = min(s + d, 1) |
| return (Min(src.Premultiply() + dst.Premultiply(), 1)).Unpremultiply(); |
| case BlendMode::kModulate: |
| // r = s*d |
| return (src.Premultiply() * dst.Premultiply()).Unpremultiply(); |
| case BlendMode::kScreen: { |
| return DoColorBlend(dst, src, [](Vector3 d, Vector3 s) -> Vector3 { |
| return s + d - s * d; |
| }); |
| } |
| case BlendMode::kOverlay: |
| return DoColorBlend(dst, src, [](Vector3 d, Vector3 s) -> Vector3 { |
| // The same as HardLight, but with the source and destination reversed. |
| Vector3 screen_src = 2.0 * d - 1.0; |
| Vector3 screen = screen_src + s - screen_src * s; |
| return ComponentChoose(s * (2.0 * d), // |
| screen, // |
| d, // |
| 0.5); |
| }); |
| case BlendMode::kDarken: |
| return DoColorBlend( |
| dst, src, [](Vector3 d, Vector3 s) -> Vector3 { return d.Min(s); }); |
| case BlendMode::kLighten: |
| return DoColorBlend( |
| dst, src, [](Vector3 d, Vector3 s) -> Vector3 { return d.Max(s); }); |
| case BlendMode::kColorDodge: |
| return DoColorBlendComponents(dst, src, [](Scalar d, Scalar s) -> Scalar { |
| if (d < kEhCloseEnough) { |
| return 0.0f; |
| } |
| if (1.0 - s < kEhCloseEnough) { |
| return 1.0f; |
| } |
| return std::min(1.0f, d / (1.0f - s)); |
| }); |
| case BlendMode::kColorBurn: |
| return DoColorBlendComponents(dst, src, [](Scalar d, Scalar s) -> Scalar { |
| if (1.0 - d < kEhCloseEnough) { |
| return 1.0f; |
| } |
| if (s < kEhCloseEnough) { |
| return 0.0f; |
| } |
| return 1.0f - std::min(1.0f, (1.0f - d) / s); |
| }); |
| case BlendMode::kHardLight: |
| return DoColorBlend(dst, src, [](Vector3 d, Vector3 s) -> Vector3 { |
| Vector3 screen_src = 2.0 * s - 1.0; |
| Vector3 screen = screen_src + d - screen_src * d; |
| return ComponentChoose(d * (2.0 * s), // |
| screen, // |
| s, // |
| 0.5); |
| }); |
| case BlendMode::kSoftLight: |
| return DoColorBlend(dst, src, [](Vector3 d, Vector3 s) -> Vector3 { |
| Vector3 D = ComponentChoose(((16.0 * d - 12.0) * d + 4.0) * d, // |
| Vector3(std::sqrt(d.x), std::sqrt(d.y), |
| std::sqrt(d.z)), // |
| d, // |
| 0.25); |
| return ComponentChoose(d - (1.0 - 2.0 * s) * d * (1.0 - d), // |
| d + (2.0 * s - 1.0) * (D - d), // |
| s, // |
| 0.5); |
| }); |
| case BlendMode::kDifference: |
| return DoColorBlend(dst, src, [](Vector3 d, Vector3 s) -> Vector3 { |
| return (d - s).Abs(); |
| }); |
| case BlendMode::kExclusion: |
| return DoColorBlend(dst, src, [](Vector3 d, Vector3 s) -> Vector3 { |
| return d + s - 2.0f * d * s; |
| }); |
| case BlendMode::kMultiply: |
| return DoColorBlend( |
| dst, src, [](Vector3 d, Vector3 s) -> Vector3 { return d * s; }); |
| case BlendMode::kHue: { |
| return DoColorBlend(dst, src, [](Vector3 d, Vector3 s) -> Vector3 { |
| return SetLuminosity(SetSaturation(s, Saturation(d)), Luminosity(d)); |
| }); |
| } |
| case BlendMode::kSaturation: |
| return DoColorBlend(dst, src, [](Vector3 d, Vector3 s) -> Vector3 { |
| return SetLuminosity(SetSaturation(d, Saturation(s)), Luminosity(d)); |
| }); |
| case BlendMode::kColor: |
| return DoColorBlend(dst, src, [](Vector3 d, Vector3 s) -> Vector3 { |
| return SetLuminosity(s, Luminosity(d)); |
| }); |
| case BlendMode::kLuminosity: |
| return DoColorBlend(dst, src, [](Vector3 d, Vector3 s) -> Vector3 { |
| return SetLuminosity(d, Luminosity(s)); |
| }); |
| } |
| } |
| |
| Color Color::ApplyColorMatrix(const ColorMatrix& color_matrix) const { |
| auto* c = color_matrix.array; |
| return Color( |
| c[0] * red + c[1] * green + c[2] * blue + c[3] * alpha + c[4], |
| c[5] * red + c[6] * green + c[7] * blue + c[8] * alpha + c[9], |
| c[10] * red + c[11] * green + c[12] * blue + c[13] * alpha + c[14], |
| c[15] * red + c[16] * green + c[17] * blue + c[18] * alpha + c[19]) |
| .Clamp01(); |
| } |
| |
| Color Color::LinearToSRGB() const { |
| static auto conversion = [](Scalar component) { |
| if (component <= 0.0031308) { |
| return component * 12.92; |
| } |
| return 1.055 * pow(component, (1.0 / 2.4)) - 0.055; |
| }; |
| |
| return Color(conversion(red), conversion(green), conversion(blue), alpha); |
| } |
| |
| Color Color::SRGBToLinear() const { |
| static auto conversion = [](Scalar component) { |
| if (component <= 0.04045) { |
| return component / 12.92; |
| } |
| return pow((component + 0.055) / 1.055, 2.4); |
| }; |
| |
| return Color(conversion(red), conversion(green), conversion(blue), alpha); |
| } |
| |
| std::string ColorToString(const Color& color) { |
| return SPrintF("R=%.1f,G=%.1f,B=%.1f,A=%.1f", // |
| color.red, // |
| color.green, // |
| color.blue, // |
| color.alpha // |
| ); |
| } |
| |
| } // namespace impeller |