1 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_LAB_COLOR_SPACE_H_
2 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_LAB_COLOR_SPACE_H_
3 
4 #include <algorithm>
5 #include <cmath>
6 #include <initializer_list>
7 #include <iterator>
8 
9 #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h"
10 
11 // Class to handle color transformation between RGB and CIE L*a*b* color spaces.
12 namespace LabColorSpace {
13 
14 using blink::FloatPoint3D;
15 using blink::TransformationMatrix;
16 
17 static constexpr FloatPoint3D kIlluminantD50 =
18     FloatPoint3D(0.964212f, 1.0f, 0.825188f);
19 static constexpr FloatPoint3D kIlluminantD65 =
20     FloatPoint3D(0.95042855f, 1.0f, 1.0889004f);
21 
22 // All matrices here are 3x3 matrices.
23 // They are stored in blink::TransformationMatrix which is 4x4 matrix in the
24 // following form.
25 // |a b c 0|
26 // |d e f 0|
27 // |g h i 0|
28 // |0 0 0 1|
29 
mul3x3Diag(const FloatPoint3D & lhs,const TransformationMatrix & rhs)30 inline TransformationMatrix mul3x3Diag(const FloatPoint3D& lhs,
31                                        const TransformationMatrix& rhs) {
32   return TransformationMatrix(
33       lhs.X() * rhs.M11(), lhs.Y() * rhs.M12(), lhs.Z() * rhs.M13(), 0.0f,
34       lhs.X() * rhs.M21(), lhs.Y() * rhs.M22(), lhs.Z() * rhs.M23(), 0.0f,
35       lhs.X() * rhs.M31(), lhs.Y() * rhs.M32(), lhs.Z() * rhs.M33(), 0.0f,
36       0.0f, 0.0f, 0.0f, 1.0f);
37 }
38 
39 template <typename T>
clamp(T x,T min,T max)40 inline constexpr T clamp(T x, T min, T max) {
41   return x < min ? min : x > max ? max : x;
42 }
43 
44 // See https://en.wikipedia.org/wiki/Chromatic_adaptation#Von_Kries_transform.
chromaticAdaptation(const TransformationMatrix & matrix,const FloatPoint3D & srcWhitePoint,const FloatPoint3D & dstWhitePoint)45 inline TransformationMatrix chromaticAdaptation(
46     const TransformationMatrix& matrix,
47     const FloatPoint3D& srcWhitePoint,
48     const FloatPoint3D& dstWhitePoint) {
49   FloatPoint3D srcLMS = matrix.MapPoint(srcWhitePoint);
50   FloatPoint3D dstLMS = matrix.MapPoint(dstWhitePoint);
51   // LMS is a diagonal matrix stored as a float[3]
52   FloatPoint3D LMS = {dstLMS.X() / srcLMS.X(), dstLMS.Y() / srcLMS.Y(),
53                       dstLMS.Z() / srcLMS.Z()};
54   return matrix.Inverse() * mul3x3Diag(LMS, matrix);
55 }
56 
57 class sRGBColorSpace {
58  public:
toLinear(const FloatPoint3D & v)59   FloatPoint3D toLinear(const FloatPoint3D& v) const {
60     auto EOTF = [](float u) {
61       return u < 0.04045f
62                  ? clamp(u / 12.92f, .0f, 1.0f)
63                  : clamp(std::pow((u + 0.055f) / 1.055f, 2.4f), .0f, 1.0f);
64     };
65     return FloatPoint3D(EOTF(v.X()), EOTF(v.Y()), EOTF(v.Z()));
66   }
67 
fromLinear(const FloatPoint3D & v)68   FloatPoint3D fromLinear(const FloatPoint3D& v) const {
69     auto OETF = [](float u) {
70       return (u < 0.0031308f
71                   ? clamp(12.92 * u, .0, 1.0)
72                   : clamp(1.055 * std::pow(u, 1.0 / 2.4) - 0.055, .0, 1.0));
73     };
74     return FloatPoint3D(OETF(v.X()), OETF(v.Y()), OETF(v.Z()));
75   }
76 
77   // See https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation.
toXYZ(const FloatPoint3D & rgb)78   FloatPoint3D toXYZ(const FloatPoint3D& rgb) const {
79     return transform_.MapPoint(toLinear(rgb));
80   }
81 
82   // See
83   // https://en.wikipedia.org/wiki/SRGB#The_forward_transformation_(CIE_XYZ_to_sRGB).
fromXYZ(const FloatPoint3D & xyz)84   FloatPoint3D fromXYZ(const FloatPoint3D& xyz) const {
85     return fromLinear(inverseTransform_.MapPoint(xyz));
86   }
87 
88  private:
89   TransformationMatrix kBradford = TransformationMatrix(
90        0.8951f, -0.7502f,  0.0389f, 0.0f,
91        0.2664f,  1.7135f, -0.0685f, 0.0f,
92       -0.1614f,  0.0367f,  1.0296f, 0.0f,
93        0.0f,     0.0f,     0.0f,    1.0f);
94 
95   TransformationMatrix xyzTransform = TransformationMatrix(
96       0.41238642f, 0.21263677f, 0.019330615f, 0.0f,
97       0.3575915f,  0.715183f,   0.11919712f,  0.0f,
98       0.18045056f, 0.07218022f, 0.95037293f,  0.0f,
99       0.0f,        0.0f,        0.0f,         1.0f);
100 
101   TransformationMatrix transform_ =
102       chromaticAdaptation(kBradford, kIlluminantD65, kIlluminantD50) *
103       xyzTransform;
104   TransformationMatrix inverseTransform_ = transform_.Inverse();
105 };
106 
107 class LABColorSpace {
108  public:
109   // See
110   // https://en.wikipedia.org/wiki/CIELAB_color_space#Reverse_transformation.
111   FloatPoint3D fromXYZ(const FloatPoint3D& v) const {
112     auto f = [](float x) {
113       return x > kSigma3 ? pow(x, 1.0f / 3.0f)
114                          : x / (3 * kSigma2) + 4.0f / 29.0f;
115     };
116 
117     float fx = f(v.X() / kIlluminantD50.X());
118     float fy = f(v.Y() / kIlluminantD50.Y());
119     float fz = f(v.Z() / kIlluminantD50.Z());
120 
121     float L = 116.0f * fy - 16.0f;
122     float a = 500.0f * (fx - fy);
123     float b = 200.0f * (fy - fz);
124 
125     return {clamp(L, 0.0f, 100.0f), clamp(a, -128.0f, 128.0f),
126             clamp(b, -128.0f, 128.0f)};
127   }
128 
129   // See
130   // https://en.wikipedia.org/wiki/CIELAB_color_space#Forward_transformation.
131   FloatPoint3D toXYZ(const FloatPoint3D& lab) const {
132     auto invf = [](float x) {
133       return x > kSigma ? pow(x, 3) : 3 * kSigma2 * (x - 4.0f / 29.0f);
134     };
135 
136     FloatPoint3D v = {clamp(lab.X(), 0.0f, 100.0f),
137                       clamp(lab.Y(), -128.0f, 128.0f),
138                       clamp(lab.Z(), -128.0f, 128.0f)};
139 
140     return {
141         invf((v.X() + 16.0f) / 116.0f + (v.Y() * 0.002f)) * kIlluminantD50.X(),
142         invf((v.X() + 16.0f) / 116.0f) * kIlluminantD50.Y(),
143         invf((v.X() + 16.0f) / 116.0f - (v.Z() * 0.005f)) * kIlluminantD50.Z()};
144   }
145 
146  private:
147   static constexpr float kSigma = 6.0f / 29.0f;
148   static constexpr float kSigma2 = 36.0f / 841.0f;
149   static constexpr float kSigma3 = 216.0f / 24389.0f;
150 };
151 
152 class RGBLABTransformer {
153  public:
154   FloatPoint3D sRGBToLab(const FloatPoint3D& rgb) const {
155     FloatPoint3D xyz = rcs.toXYZ(rgb);
156     return lcs.fromXYZ(xyz);
157   }
158 
159   FloatPoint3D LabToSRGB(const FloatPoint3D& lab) const {
160     FloatPoint3D xyz = lcs.toXYZ(lab);
161     return rcs.fromXYZ(xyz);
162   }
163 
164  private:
165   sRGBColorSpace rcs = sRGBColorSpace();
166   LABColorSpace lcs = LABColorSpace();
167 };
168 
169 }  // namespace LabColorSpace
170 
171 #endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_LAB_COLOR_SPACE_H_
172