Loading include/ui/ColorSpace.h +25 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include <array> #include <cmath> #include <functional> #include <memory> #include <string> #include <ui/mat3.h> Loading Loading @@ -165,6 +166,30 @@ public: static const ColorSpace ACES(); static const ColorSpace ACEScg(); class Connector { public: Connector(const ColorSpace& src, const ColorSpace& dst) noexcept; constexpr const ColorSpace& getSource() const noexcept { return mSource; } constexpr const ColorSpace& getDestination() const noexcept { return mDestination; } constexpr const mat3& getTransform() const noexcept { return mTransform; } constexpr float3 transform(const float3& v) const noexcept { float3 linear = mSource.toLinear(apply(v, mSource.getClamper())); return apply(mDestination.fromLinear(mTransform * linear), mDestination.getClamper()); } private: const ColorSpace& mSource; const ColorSpace& mDestination; mat3 mTransform; }; static const Connector connect(const ColorSpace& src, const ColorSpace& dst) { return Connector(src, dst); } private: static constexpr mat3 computeXYZMatrix( const std::array<float2, 3>& primaries, const float2& whitePoint); Loading libs/ui/ColorSpace.cpp +50 −5 Original line number Diff line number Diff line Loading @@ -96,6 +96,47 @@ constexpr mat3 ColorSpace::computeXYZMatrix( }; } static const float2 ILLUMINANT_D50_XY = {0.34567f, 0.35850f}; static const float3 ILLUMINANT_D50_XYZ = {0.964212f, 1.0f, 0.825188f}; static const mat3 VON_KRIES = mat3{ float3{ 0.8951f, -0.7502f, 0.0389f}, float3{ 0.2664f, 1.7135f, -0.0685f}, float3{-0.1614f, 0.0367f, 1.0296f} }; static mat3 adaptation(const mat3& matrix, const float3& srcWhitePoint, const float3& dstWhitePoint) { float3 srcLMS = matrix * srcWhitePoint; float3 dstLMS = matrix * dstWhitePoint; return inverse(matrix) * mat3{dstLMS / srcLMS} * matrix; } ColorSpace::Connector::Connector( const ColorSpace& src, const ColorSpace& dst) noexcept : mSource(src) , mDestination(dst) { if (all(lessThan(abs(src.getWhitePoint() - dst.getWhitePoint()), float2{1e-3f}))) { mTransform = dst.getXYZtoRGB() * src.getRGBtoXYZ(); } else { mat3 rgbToXYZ(src.getRGBtoXYZ()); mat3 xyzToRGB(dst.getXYZtoRGB()); float3 srcXYZ = XYZ(float3{src.getWhitePoint(), 1}); float3 dstXYZ = XYZ(float3{dst.getWhitePoint(), 1}); if (any(greaterThan(abs(src.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) { rgbToXYZ = adaptation(VON_KRIES, srcXYZ, ILLUMINANT_D50_XYZ) * src.getRGBtoXYZ(); } if (any(greaterThan(abs(dst.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) { xyzToRGB = inverse(adaptation(VON_KRIES, dstXYZ, ILLUMINANT_D50_XYZ) * dst.getRGBtoXYZ()); } mTransform = xyzToRGB * rgbToXYZ; } } static constexpr float rcpResponse(float x, float g,float a, float b, float c, float d) { return x >= d * c ? (std::pow(x, 1.0f / g) - b) / a : x / c; } Loading @@ -112,6 +153,10 @@ static float absResponse(float x, float g, float a, float b, float c, float d) { return std::copysign(response(std::abs(x), g, a, b, c, d), x); } static float safePow(float x, float e) { return powf(x < 0.0f ? 0.0f : x, e); } const ColorSpace ColorSpace::sRGB() { return { "sRGB IEC61966-2.1", Loading Loading @@ -187,8 +232,8 @@ const ColorSpace ColorSpace::AdobeRGB() { "Adobe RGB (1998)", {{float2{0.64f, 0.33f}, {0.21f, 0.71f}, {0.15f, 0.06f}}}, {0.3127f, 0.3290f}, std::bind(powf, _1, 1.0f / 2.2f), std::bind(powf, _1, 2.2f) std::bind(safePow, _1, 1.0f / 2.2f), std::bind(safePow, _1, 2.2f) }; } Loading @@ -196,7 +241,7 @@ const ColorSpace ColorSpace::ProPhotoRGB() { return { "ROMM RGB ISO 22028-2:2013", {{float2{0.7347f, 0.2653f}, {0.1596f, 0.8404f}, {0.0366f, 0.0001f}}}, {0.3457f, 0.3585f}, {0.34567f, 0.35850f}, std::bind(rcpResponse, _1, 1.8f, 1.0f, 0.0f, 1 / 16.0f, 0.031248f), std::bind(response, _1, 1.8f, 1.0f, 0.0f, 1 / 16.0f, 0.031248f) }; Loading @@ -217,8 +262,8 @@ const ColorSpace ColorSpace::DCIP3() { "SMPTE RP 431-2-2007 DCI (P3)", {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}}, {0.314f, 0.351f}, std::bind(powf, _1, 1.0f / 2.6f), std::bind(powf, _1, 2.6f) std::bind(safePow, _1, 1.0f / 2.6f), std::bind(safePow, _1, 2.6f) }; } Loading libs/ui/tests/colorspace_test.cpp +12 −0 Original line number Diff line number Diff line Loading @@ -150,4 +150,16 @@ TEST_F(ColorSpaceTest, Clamping) { EXPECT_TRUE(extendedSRGB.g > 1.0f); } TEST_F(ColorSpaceTest, Connect) { // No chromatic adaptation auto r = ColorSpace::connect(ColorSpace::sRGB(), ColorSpace::AdobeRGB()) .transform({1.0f, 0.5f, 0.0f}); EXPECT_TRUE(all(lessThan(abs(r - float3{0.8912f, 0.4962f, 0.1164f}), float3{1e-4f}))); // Test with chromatic adaptation r = ColorSpace::connect(ColorSpace::sRGB(), ColorSpace::ProPhotoRGB()) .transform({1.0f, 0.0f, 0.0f}); EXPECT_TRUE(all(lessThan(abs(r - float3{0.70226f, 0.2757f, 0.1036f}), float3{1e-4f}))); } }; // namespace android Loading
include/ui/ColorSpace.h +25 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include <array> #include <cmath> #include <functional> #include <memory> #include <string> #include <ui/mat3.h> Loading Loading @@ -165,6 +166,30 @@ public: static const ColorSpace ACES(); static const ColorSpace ACEScg(); class Connector { public: Connector(const ColorSpace& src, const ColorSpace& dst) noexcept; constexpr const ColorSpace& getSource() const noexcept { return mSource; } constexpr const ColorSpace& getDestination() const noexcept { return mDestination; } constexpr const mat3& getTransform() const noexcept { return mTransform; } constexpr float3 transform(const float3& v) const noexcept { float3 linear = mSource.toLinear(apply(v, mSource.getClamper())); return apply(mDestination.fromLinear(mTransform * linear), mDestination.getClamper()); } private: const ColorSpace& mSource; const ColorSpace& mDestination; mat3 mTransform; }; static const Connector connect(const ColorSpace& src, const ColorSpace& dst) { return Connector(src, dst); } private: static constexpr mat3 computeXYZMatrix( const std::array<float2, 3>& primaries, const float2& whitePoint); Loading
libs/ui/ColorSpace.cpp +50 −5 Original line number Diff line number Diff line Loading @@ -96,6 +96,47 @@ constexpr mat3 ColorSpace::computeXYZMatrix( }; } static const float2 ILLUMINANT_D50_XY = {0.34567f, 0.35850f}; static const float3 ILLUMINANT_D50_XYZ = {0.964212f, 1.0f, 0.825188f}; static const mat3 VON_KRIES = mat3{ float3{ 0.8951f, -0.7502f, 0.0389f}, float3{ 0.2664f, 1.7135f, -0.0685f}, float3{-0.1614f, 0.0367f, 1.0296f} }; static mat3 adaptation(const mat3& matrix, const float3& srcWhitePoint, const float3& dstWhitePoint) { float3 srcLMS = matrix * srcWhitePoint; float3 dstLMS = matrix * dstWhitePoint; return inverse(matrix) * mat3{dstLMS / srcLMS} * matrix; } ColorSpace::Connector::Connector( const ColorSpace& src, const ColorSpace& dst) noexcept : mSource(src) , mDestination(dst) { if (all(lessThan(abs(src.getWhitePoint() - dst.getWhitePoint()), float2{1e-3f}))) { mTransform = dst.getXYZtoRGB() * src.getRGBtoXYZ(); } else { mat3 rgbToXYZ(src.getRGBtoXYZ()); mat3 xyzToRGB(dst.getXYZtoRGB()); float3 srcXYZ = XYZ(float3{src.getWhitePoint(), 1}); float3 dstXYZ = XYZ(float3{dst.getWhitePoint(), 1}); if (any(greaterThan(abs(src.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) { rgbToXYZ = adaptation(VON_KRIES, srcXYZ, ILLUMINANT_D50_XYZ) * src.getRGBtoXYZ(); } if (any(greaterThan(abs(dst.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) { xyzToRGB = inverse(adaptation(VON_KRIES, dstXYZ, ILLUMINANT_D50_XYZ) * dst.getRGBtoXYZ()); } mTransform = xyzToRGB * rgbToXYZ; } } static constexpr float rcpResponse(float x, float g,float a, float b, float c, float d) { return x >= d * c ? (std::pow(x, 1.0f / g) - b) / a : x / c; } Loading @@ -112,6 +153,10 @@ static float absResponse(float x, float g, float a, float b, float c, float d) { return std::copysign(response(std::abs(x), g, a, b, c, d), x); } static float safePow(float x, float e) { return powf(x < 0.0f ? 0.0f : x, e); } const ColorSpace ColorSpace::sRGB() { return { "sRGB IEC61966-2.1", Loading Loading @@ -187,8 +232,8 @@ const ColorSpace ColorSpace::AdobeRGB() { "Adobe RGB (1998)", {{float2{0.64f, 0.33f}, {0.21f, 0.71f}, {0.15f, 0.06f}}}, {0.3127f, 0.3290f}, std::bind(powf, _1, 1.0f / 2.2f), std::bind(powf, _1, 2.2f) std::bind(safePow, _1, 1.0f / 2.2f), std::bind(safePow, _1, 2.2f) }; } Loading @@ -196,7 +241,7 @@ const ColorSpace ColorSpace::ProPhotoRGB() { return { "ROMM RGB ISO 22028-2:2013", {{float2{0.7347f, 0.2653f}, {0.1596f, 0.8404f}, {0.0366f, 0.0001f}}}, {0.3457f, 0.3585f}, {0.34567f, 0.35850f}, std::bind(rcpResponse, _1, 1.8f, 1.0f, 0.0f, 1 / 16.0f, 0.031248f), std::bind(response, _1, 1.8f, 1.0f, 0.0f, 1 / 16.0f, 0.031248f) }; Loading @@ -217,8 +262,8 @@ const ColorSpace ColorSpace::DCIP3() { "SMPTE RP 431-2-2007 DCI (P3)", {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}}, {0.314f, 0.351f}, std::bind(powf, _1, 1.0f / 2.6f), std::bind(powf, _1, 2.6f) std::bind(safePow, _1, 1.0f / 2.6f), std::bind(safePow, _1, 2.6f) }; } Loading
libs/ui/tests/colorspace_test.cpp +12 −0 Original line number Diff line number Diff line Loading @@ -150,4 +150,16 @@ TEST_F(ColorSpaceTest, Clamping) { EXPECT_TRUE(extendedSRGB.g > 1.0f); } TEST_F(ColorSpaceTest, Connect) { // No chromatic adaptation auto r = ColorSpace::connect(ColorSpace::sRGB(), ColorSpace::AdobeRGB()) .transform({1.0f, 0.5f, 0.0f}); EXPECT_TRUE(all(lessThan(abs(r - float3{0.8912f, 0.4962f, 0.1164f}), float3{1e-4f}))); // Test with chromatic adaptation r = ColorSpace::connect(ColorSpace::sRGB(), ColorSpace::ProPhotoRGB()) .transform({1.0f, 0.0f, 0.0f}); EXPECT_TRUE(all(lessThan(abs(r - float3{0.70226f, 0.2757f, 0.1036f}), float3{1e-4f}))); } }; // namespace android