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