1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <ui/ColorSpace.h>
18
19 using namespace std::placeholders;
20
21 namespace android {
22
linearResponse(float v)23 static constexpr float linearResponse(float v) {
24 return v;
25 }
26
rcpResponse(float x,const ColorSpace::TransferParameters & p)27 static constexpr float rcpResponse(float x, const ColorSpace::TransferParameters& p) {
28 return x >= p.d * p.c ? (std::pow(x, 1.0f / p.g) - p.b) / p.a : x / p.c;
29 }
30
response(float x,const ColorSpace::TransferParameters & p)31 static constexpr float response(float x, const ColorSpace::TransferParameters& p) {
32 return x >= p.d ? std::pow(p.a * x + p.b, p.g) : p.c * x;
33 }
34
rcpFullResponse(float x,const ColorSpace::TransferParameters & p)35 static constexpr float rcpFullResponse(float x, const ColorSpace::TransferParameters& p) {
36 return x >= p.d * p.c ? (std::pow(x - p.e, 1.0f / p.g) - p.b) / p.a : (x - p.f) / p.c;
37 }
38
fullResponse(float x,const ColorSpace::TransferParameters & p)39 static constexpr float fullResponse(float x, const ColorSpace::TransferParameters& p) {
40 return x >= p.d ? std::pow(p.a * x + p.b, p.g) + p.e : p.c * x + p.f;
41 }
42
absRcpResponse(float x,float g,float a,float b,float c,float d)43 static float absRcpResponse(float x, float g,float a, float b, float c, float d) {
44 float xx = std::abs(x);
45 return std::copysign(xx >= d * c ? (std::pow(xx, 1.0f / g) - b) / a : xx / c, x);
46 }
47
absResponse(float x,float g,float a,float b,float c,float d)48 static float absResponse(float x, float g, float a, float b, float c, float d) {
49 float xx = std::abs(x);
50 return std::copysign(xx >= d ? std::pow(a * xx + b, g) : c * xx, x);
51 }
52
safePow(float x,float e)53 static float safePow(float x, float e) {
54 return powf(x < 0.0f ? 0.0f : x, e);
55 }
56
toOETF(const ColorSpace::TransferParameters & parameters)57 static ColorSpace::transfer_function toOETF(const ColorSpace::TransferParameters& parameters) {
58 if (parameters.e == 0.0f && parameters.f == 0.0f) {
59 return std::bind(rcpResponse, _1, parameters);
60 }
61 return std::bind(rcpFullResponse, _1, parameters);
62 }
63
toEOTF(const ColorSpace::TransferParameters & parameters)64 static ColorSpace::transfer_function toEOTF( const ColorSpace::TransferParameters& parameters) {
65 if (parameters.e == 0.0f && parameters.f == 0.0f) {
66 return std::bind(response, _1, parameters);
67 }
68 return std::bind(fullResponse, _1, parameters);
69 }
70
toOETF(float gamma)71 static ColorSpace::transfer_function toOETF(float gamma) {
72 if (gamma == 1.0f) {
73 return linearResponse;
74 }
75 return std::bind(safePow, _1, 1.0f / gamma);
76 }
77
toEOTF(float gamma)78 static ColorSpace::transfer_function toEOTF(float gamma) {
79 if (gamma == 1.0f) {
80 return linearResponse;
81 }
82 return std::bind(safePow, _1, gamma);
83 }
84
computePrimaries(const mat3 & rgbToXYZ)85 static constexpr std::array<float2, 3> computePrimaries(const mat3& rgbToXYZ) {
86 float3 r(rgbToXYZ * float3{1, 0, 0});
87 float3 g(rgbToXYZ * float3{0, 1, 0});
88 float3 b(rgbToXYZ * float3{0, 0, 1});
89
90 return {{r.xy / dot(r, float3{1}),
91 g.xy / dot(g, float3{1}),
92 b.xy / dot(b, float3{1})}};
93 }
94
computeWhitePoint(const mat3 & rgbToXYZ)95 static constexpr float2 computeWhitePoint(const mat3& rgbToXYZ) {
96 float3 w(rgbToXYZ * float3{1});
97 return w.xy / dot(w, float3{1});
98 }
99
ColorSpace(const std::string & name,const mat3 & rgbToXYZ,transfer_function OETF,transfer_function EOTF,clamping_function clamper)100 ColorSpace::ColorSpace(
101 const std::string& name,
102 const mat3& rgbToXYZ,
103 transfer_function OETF,
104 transfer_function EOTF,
105 clamping_function clamper) noexcept
106 : mName(name)
107 , mRGBtoXYZ(rgbToXYZ)
108 , mXYZtoRGB(inverse(rgbToXYZ))
109 , mOETF(std::move(OETF))
110 , mEOTF(std::move(EOTF))
111 , mClamper(std::move(clamper))
112 , mPrimaries(computePrimaries(rgbToXYZ))
113 , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
114 }
115
ColorSpace(const std::string & name,const mat3 & rgbToXYZ,const TransferParameters parameters,clamping_function clamper)116 ColorSpace::ColorSpace(
117 const std::string& name,
118 const mat3& rgbToXYZ,
119 const TransferParameters parameters,
120 clamping_function clamper) noexcept
121 : mName(name)
122 , mRGBtoXYZ(rgbToXYZ)
123 , mXYZtoRGB(inverse(rgbToXYZ))
124 , mParameters(parameters)
125 , mOETF(toOETF(mParameters))
126 , mEOTF(toEOTF(mParameters))
127 , mClamper(std::move(clamper))
128 , mPrimaries(computePrimaries(rgbToXYZ))
129 , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
130 }
131
ColorSpace(const std::string & name,const mat3 & rgbToXYZ,float gamma,clamping_function clamper)132 ColorSpace::ColorSpace(
133 const std::string& name,
134 const mat3& rgbToXYZ,
135 float gamma,
136 clamping_function clamper) noexcept
137 : mName(name)
138 , mRGBtoXYZ(rgbToXYZ)
139 , mXYZtoRGB(inverse(rgbToXYZ))
140 , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f})
141 , mOETF(toOETF(gamma))
142 , mEOTF(toEOTF(gamma))
143 , mClamper(std::move(clamper))
144 , mPrimaries(computePrimaries(rgbToXYZ))
145 , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
146 }
147
ColorSpace(const std::string & name,const std::array<float2,3> & primaries,const float2 & whitePoint,transfer_function OETF,transfer_function EOTF,clamping_function clamper)148 ColorSpace::ColorSpace(
149 const std::string& name,
150 const std::array<float2, 3>& primaries,
151 const float2& whitePoint,
152 transfer_function OETF,
153 transfer_function EOTF,
154 clamping_function clamper) noexcept
155 : mName(name)
156 , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
157 , mXYZtoRGB(inverse(mRGBtoXYZ))
158 , mOETF(std::move(OETF))
159 , mEOTF(std::move(EOTF))
160 , mClamper(std::move(clamper))
161 , mPrimaries(primaries)
162 , mWhitePoint(whitePoint) {
163 }
164
ColorSpace(const std::string & name,const std::array<float2,3> & primaries,const float2 & whitePoint,const TransferParameters parameters,clamping_function clamper)165 ColorSpace::ColorSpace(
166 const std::string& name,
167 const std::array<float2, 3>& primaries,
168 const float2& whitePoint,
169 const TransferParameters parameters,
170 clamping_function clamper) noexcept
171 : mName(name)
172 , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
173 , mXYZtoRGB(inverse(mRGBtoXYZ))
174 , mParameters(parameters)
175 , mOETF(toOETF(mParameters))
176 , mEOTF(toEOTF(mParameters))
177 , mClamper(std::move(clamper))
178 , mPrimaries(primaries)
179 , mWhitePoint(whitePoint) {
180 }
181
ColorSpace(const std::string & name,const std::array<float2,3> & primaries,const float2 & whitePoint,float gamma,clamping_function clamper)182 ColorSpace::ColorSpace(
183 const std::string& name,
184 const std::array<float2, 3>& primaries,
185 const float2& whitePoint,
186 float gamma,
187 clamping_function clamper) noexcept
188 : mName(name)
189 , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
190 , mXYZtoRGB(inverse(mRGBtoXYZ))
191 , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f})
192 , mOETF(toOETF(gamma))
193 , mEOTF(toEOTF(gamma))
194 , mClamper(std::move(clamper))
195 , mPrimaries(primaries)
196 , mWhitePoint(whitePoint) {
197 }
198
computeXYZMatrix(const std::array<float2,3> & primaries,const float2 & whitePoint)199 constexpr mat3 ColorSpace::computeXYZMatrix(
200 const std::array<float2, 3>& primaries, const float2& whitePoint) {
201 const float2& R = primaries[0];
202 const float2& G = primaries[1];
203 const float2& B = primaries[2];
204 const float2& W = whitePoint;
205
206 float oneRxRy = (1 - R.x) / R.y;
207 float oneGxGy = (1 - G.x) / G.y;
208 float oneBxBy = (1 - B.x) / B.y;
209 float oneWxWy = (1 - W.x) / W.y;
210
211 float RxRy = R.x / R.y;
212 float GxGy = G.x / G.y;
213 float BxBy = B.x / B.y;
214 float WxWy = W.x / W.y;
215
216 float BY =
217 ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) /
218 ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy));
219 float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy);
220 float RY = 1 - GY - BY;
221
222 float RYRy = RY / R.y;
223 float GYGy = GY / G.y;
224 float BYBy = BY / B.y;
225
226 return {
227 float3{RYRy * R.x, RY, RYRy * (1 - R.x - R.y)},
228 float3{GYGy * G.x, GY, GYGy * (1 - G.x - G.y)},
229 float3{BYBy * B.x, BY, BYBy * (1 - B.x - B.y)}
230 };
231 }
232
sRGB()233 const ColorSpace ColorSpace::sRGB() {
234 return {
235 "sRGB IEC61966-2.1",
236 {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
237 {0.3127f, 0.3290f},
238 {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f, 0.0f, 0.0f}
239 };
240 }
241
linearSRGB()242 const ColorSpace ColorSpace::linearSRGB() {
243 return {
244 "sRGB IEC61966-2.1 (Linear)",
245 {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
246 {0.3127f, 0.3290f}
247 };
248 }
249
extendedSRGB()250 const ColorSpace ColorSpace::extendedSRGB() {
251 return {
252 "scRGB-nl IEC 61966-2-2:2003",
253 {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
254 {0.3127f, 0.3290f},
255 std::bind(absRcpResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f),
256 std::bind(absResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f),
257 std::bind(clamp<float>, _1, -0.799f, 2.399f)
258 };
259 }
260
linearExtendedSRGB()261 const ColorSpace ColorSpace::linearExtendedSRGB() {
262 return {
263 "scRGB IEC 61966-2-2:2003",
264 {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
265 {0.3127f, 0.3290f},
266 1.0f,
267 std::bind(clamp<float>, _1, -0.5f, 7.499f)
268 };
269 }
270
NTSC()271 const ColorSpace ColorSpace::NTSC() {
272 return {
273 "NTSC (1953)",
274 {{float2{0.67f, 0.33f}, {0.21f, 0.71f}, {0.14f, 0.08f}}},
275 {0.310f, 0.316f},
276 {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
277 };
278 }
279
BT709()280 const ColorSpace ColorSpace::BT709() {
281 return {
282 "Rec. ITU-R BT.709-5",
283 {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
284 {0.3127f, 0.3290f},
285 {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
286 };
287 }
288
BT2020()289 const ColorSpace ColorSpace::BT2020() {
290 return {
291 "Rec. ITU-R BT.2020-1",
292 {{float2{0.708f, 0.292f}, {0.170f, 0.797f}, {0.131f, 0.046f}}},
293 {0.3127f, 0.3290f},
294 {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
295 };
296 }
297
AdobeRGB()298 const ColorSpace ColorSpace::AdobeRGB() {
299 return {
300 "Adobe RGB (1998)",
301 {{float2{0.64f, 0.33f}, {0.21f, 0.71f}, {0.15f, 0.06f}}},
302 {0.3127f, 0.3290f},
303 2.2f
304 };
305 }
306
ProPhotoRGB()307 const ColorSpace ColorSpace::ProPhotoRGB() {
308 return {
309 "ROMM RGB ISO 22028-2:2013",
310 {{float2{0.7347f, 0.2653f}, {0.1596f, 0.8404f}, {0.0366f, 0.0001f}}},
311 {0.34567f, 0.35850f},
312 {1.8f, 1.0f, 0.0f, 1 / 16.0f, 0.031248f, 0.0f, 0.0f}
313 };
314 }
315
DisplayP3()316 const ColorSpace ColorSpace::DisplayP3() {
317 return {
318 "Display P3",
319 {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}},
320 {0.3127f, 0.3290f},
321 {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.039f, 0.0f, 0.0f}
322 };
323 }
324
DCIP3()325 const ColorSpace ColorSpace::DCIP3() {
326 return {
327 "SMPTE RP 431-2-2007 DCI (P3)",
328 {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}},
329 {0.314f, 0.351f},
330 2.6f
331 };
332 }
333
ACES()334 const ColorSpace ColorSpace::ACES() {
335 return {
336 "SMPTE ST 2065-1:2012 ACES",
337 {{float2{0.73470f, 0.26530f}, {0.0f, 1.0f}, {0.00010f, -0.0770f}}},
338 {0.32168f, 0.33767f},
339 1.0f,
340 std::bind(clamp<float>, _1, -65504.0f, 65504.0f)
341 };
342 }
343
ACEScg()344 const ColorSpace ColorSpace::ACEScg() {
345 return {
346 "Academy S-2014-004 ACEScg",
347 {{float2{0.713f, 0.293f}, {0.165f, 0.830f}, {0.128f, 0.044f}}},
348 {0.32168f, 0.33767f},
349 1.0f,
350 std::bind(clamp<float>, _1, -65504.0f, 65504.0f)
351 };
352 }
353
createLUT(uint32_t size,const ColorSpace & src,const ColorSpace & dst)354 std::unique_ptr<float3[]> ColorSpace::createLUT(uint32_t size, const ColorSpace& src,
355 const ColorSpace& dst) {
356 size = clamp(size, 2u, 256u);
357 float m = 1.0f / float(size - 1);
358
359 std::unique_ptr<float3[]> lut(new float3[size * size * size]);
360 float3* data = lut.get();
361
362 ColorSpaceConnector connector(src, dst);
363
364 for (uint32_t z = 0; z < size; z++) {
365 for (int32_t y = int32_t(size - 1); y >= 0; y--) {
366 for (uint32_t x = 0; x < size; x++) {
367 *data++ = connector.transform({x * m, y * m, z * m});
368 }
369 }
370 }
371
372 return lut;
373 }
374
375 static const float2 ILLUMINANT_D50_XY = {0.34567f, 0.35850f};
376 static const float3 ILLUMINANT_D50_XYZ = {0.964212f, 1.0f, 0.825188f};
377 static const mat3 BRADFORD = mat3{
378 float3{ 0.8951f, -0.7502f, 0.0389f},
379 float3{ 0.2664f, 1.7135f, -0.0685f},
380 float3{-0.1614f, 0.0367f, 1.0296f}
381 };
382
adaptation(const mat3 & matrix,const float3 & srcWhitePoint,const float3 & dstWhitePoint)383 static mat3 adaptation(const mat3& matrix, const float3& srcWhitePoint, const float3& dstWhitePoint) {
384 float3 srcLMS = matrix * srcWhitePoint;
385 float3 dstLMS = matrix * dstWhitePoint;
386 return inverse(matrix) * mat3{dstLMS / srcLMS} * matrix;
387 }
388
ColorSpaceConnector(const ColorSpace & src,const ColorSpace & dst)389 ColorSpaceConnector::ColorSpaceConnector(
390 const ColorSpace& src,
391 const ColorSpace& dst) noexcept
392 : mSource(src)
393 , mDestination(dst) {
394
395 if (all(lessThan(abs(src.getWhitePoint() - dst.getWhitePoint()), float2{1e-3f}))) {
396 mTransform = dst.getXYZtoRGB() * src.getRGBtoXYZ();
397 } else {
398 mat3 rgbToXYZ(src.getRGBtoXYZ());
399 mat3 xyzToRGB(dst.getXYZtoRGB());
400
401 float3 srcXYZ = ColorSpace::XYZ(float3{src.getWhitePoint(), 1});
402 float3 dstXYZ = ColorSpace::XYZ(float3{dst.getWhitePoint(), 1});
403
404 if (any(greaterThan(abs(src.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) {
405 rgbToXYZ = adaptation(BRADFORD, srcXYZ, ILLUMINANT_D50_XYZ) * src.getRGBtoXYZ();
406 }
407
408 if (any(greaterThan(abs(dst.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) {
409 xyzToRGB = inverse(adaptation(BRADFORD, dstXYZ, ILLUMINANT_D50_XYZ) * dst.getRGBtoXYZ());
410 }
411
412 mTransform = xyzToRGB * rgbToXYZ;
413 }
414 }
415
416 }; // namespace android
417