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