Add ColorSpace::createLUT
Generates a 3D LUT of a specified size. The generated LUT is meant to
be used as a 3D OpenGL texture for fast color space conversions in
shaders. This will be used to convert from P3 bitmaps to scRGB.
Bug: 32984164
Test: colorspace_test
Change-Id: I01f7276f885c71bde480e79260013d62786b377c
diff --git a/include/ui/ColorSpace.h b/include/ui/ColorSpace.h
index 15e6628..b1863df 100644
--- a/include/ui/ColorSpace.h
+++ b/include/ui/ColorSpace.h
@@ -180,6 +180,11 @@
return apply(mDestination.fromLinear(mTransform * linear), mDestination.getClamper());
}
+ constexpr float3 transformLinear(const float3& v) const noexcept {
+ float3 linear = apply(v, mSource.getClamper());
+ return apply(mTransform * linear, mDestination.getClamper());
+ }
+
private:
const ColorSpace& mSource;
const ColorSpace& mDestination;
@@ -190,6 +195,15 @@
return Connector(src, dst);
}
+ // Creates a NxNxN 3D LUT, where N is the specified size (min=2, max=256)
+ // The 3D lookup coordinates map to the RGB components: u=R, v=G, w=B
+ // The generated 3D LUT is meant to be used as a 3D texture and its Y
+ // axis is thus already flipped
+ // The source color space must define its values in the domain [0..1]
+ // The generated LUT transforms from gamma space to gamma space
+ static std::unique_ptr<float3> createLUT(uint32_t size,
+ const ColorSpace& src, const ColorSpace& dst);
+
private:
static constexpr mat3 computeXYZMatrix(
const std::array<float2, 3>& primaries, const float2& whitePoint);
diff --git a/include/ui/TVecHelpers.h b/include/ui/TVecHelpers.h
index 4934338..1884608 100644
--- a/include/ui/TVecHelpers.h
+++ b/include/ui/TVecHelpers.h
@@ -324,7 +324,7 @@
template<typename RT>
friend inline
- constexpr VECTOR<bool> PURE equal(const VECTOR<T>& lv, const VECTOR<RT>& rv) {
+ CONSTEXPR VECTOR<bool> PURE equal(const VECTOR<T>& lv, const VECTOR<RT>& rv) {
VECTOR<bool> r;
for (size_t i = 0; i < lv.size(); i++) {
r[i] = lv[i] == rv[i];
@@ -334,7 +334,7 @@
template<typename RT>
friend inline
- constexpr VECTOR<bool> PURE notEqual(const VECTOR<T>& lv, const VECTOR<RT>& rv) {
+ CONSTEXPR VECTOR<bool> PURE notEqual(const VECTOR<T>& lv, const VECTOR<RT>& rv) {
VECTOR<bool> r;
for (size_t i = 0; i < lv.size(); i++) {
r[i] = lv[i] != rv[i];
@@ -344,7 +344,7 @@
template<typename RT>
friend inline
- constexpr VECTOR<bool> PURE lessThan(const VECTOR<T>& lv, const VECTOR<RT>& rv) {
+ CONSTEXPR VECTOR<bool> PURE lessThan(const VECTOR<T>& lv, const VECTOR<RT>& rv) {
VECTOR<bool> r;
for (size_t i = 0; i < lv.size(); i++) {
r[i] = lv[i] < rv[i];
@@ -354,7 +354,7 @@
template<typename RT>
friend inline
- constexpr VECTOR<bool> PURE lessThanEqual(const VECTOR<T>& lv, const VECTOR<RT>& rv) {
+ CONSTEXPR VECTOR<bool> PURE lessThanEqual(const VECTOR<T>& lv, const VECTOR<RT>& rv) {
VECTOR<bool> r;
for (size_t i = 0; i < lv.size(); i++) {
r[i] = lv[i] <= rv[i];
@@ -364,7 +364,7 @@
template<typename RT>
friend inline
- constexpr VECTOR<bool> PURE greaterThan(const VECTOR<T>& lv, const VECTOR<RT>& rv) {
+ CONSTEXPR VECTOR<bool> PURE greaterThan(const VECTOR<T>& lv, const VECTOR<RT>& rv) {
VECTOR<bool> r;
for (size_t i = 0; i < lv.size(); i++) {
r[i] = lv[i] > rv[i];
@@ -374,7 +374,7 @@
template<typename RT>
friend inline
- constexpr VECTOR<bool> PURE greaterThanEqual(const VECTOR<T>& lv, const VECTOR<RT>& rv) {
+ CONSTEXPR VECTOR<bool> PURE greaterThanEqual(const VECTOR<T>& lv, const VECTOR<RT>& rv) {
VECTOR<bool> r;
for (size_t i = 0; i < lv.size(); i++) {
r[i] = lv[i] >= rv[i];
diff --git a/libs/ui/ColorSpace.cpp b/libs/ui/ColorSpace.cpp
index 6f005f4..6a86bb5 100644
--- a/libs/ui/ColorSpace.cpp
+++ b/libs/ui/ColorSpace.cpp
@@ -96,47 +96,6 @@
};
}
-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;
}
@@ -289,4 +248,68 @@
};
}
+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 BRADFORD = 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(BRADFORD, srcXYZ, ILLUMINANT_D50_XYZ) * src.getRGBtoXYZ();
+ }
+
+ if (any(greaterThan(abs(dst.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) {
+ xyzToRGB = inverse(adaptation(BRADFORD, dstXYZ, ILLUMINANT_D50_XYZ) * dst.getRGBtoXYZ());
+ }
+
+ mTransform = xyzToRGB * rgbToXYZ;
+ }
+}
+
+std::unique_ptr<float3> ColorSpace::createLUT(uint32_t size,
+ const ColorSpace& src, const ColorSpace& dst) {
+
+ size = clamp(size, 2u, 256u);
+ float m = 1.0f / float(size - 1);
+
+ std::unique_ptr<float3> lut(new float3[size * size * size]);
+ float3* data = lut.get();
+
+ Connector connector(src, dst);
+
+ for (uint32_t z = 0; z < size; z++) {
+ for (int32_t y = int32_t(size - 1); y >= 0; y--) {
+ for (uint32_t x = 0; x < size; x++) {
+ *data++ = connector.transform({x * m, y * m, z * m});
+ }
+ }
+ }
+
+ return lut;
+}
+
+
}; // namespace android
diff --git a/libs/ui/tests/colorspace_test.cpp b/libs/ui/tests/colorspace_test.cpp
index 0bfa487..1e359d3 100644
--- a/libs/ui/tests/colorspace_test.cpp
+++ b/libs/ui/tests/colorspace_test.cpp
@@ -162,4 +162,22 @@
EXPECT_TRUE(all(lessThan(abs(r - float3{0.70226f, 0.2757f, 0.1036f}), float3{1e-4f})));
}
+TEST_F(ColorSpaceTest, LUT) {
+ auto lut = ColorSpace::createLUT(17, ColorSpace::sRGB(), ColorSpace::AdobeRGB());
+ EXPECT_TRUE(lut != nullptr);
+
+ // {1.0f, 0.5f, 0.0f}
+ auto r = lut.get()[0 * 17 * 17 + 8 * 17 + 16];
+ EXPECT_TRUE(all(lessThan(abs(r - float3{0.8912f, 0.4962f, 0.1164f}), float3{1e-4f})));
+
+ // {1.0f, 1.0f, 0.5f}
+ r = lut.get()[8 * 17 * 17 + 0 * 17 + 16]; // y (G) is flipped
+ EXPECT_TRUE(all(lessThan(abs(r - float3{1.0f, 1.0f, 0.5290f}), float3{1e-4f})));
+
+ // {1.0f, 1.0f, 1.0f}
+ r = lut.get()[16 * 17 * 17 + 0 * 17 + 16]; // y (G) is flipped
+ EXPECT_TRUE(all(lessThan(abs(r - float3{1.0f, 1.0f, 1.0f}), float3{1e-4f})));
+
+}
+
}; // namespace android