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
19using namespace std::placeholders;
20
21namespace android {
22
23static constexpr float linearResponse(float v) {
24    return v;
25}
26
27static 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
31static 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
35static 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
39static 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
43static 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
48static 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
53static float safePow(float x, float e) {
54    return powf(x < 0.0f ? 0.0f : x, e);
55}
56
57static 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
64static 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
71static 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
78static ColorSpace::transfer_function toEOTF(float gamma) {
79    if (gamma == 1.0f) {
80        return linearResponse;
81    }
82    return std::bind(safePow, _1, gamma);
83}
84
85static 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
95static constexpr float2 computeWhitePoint(const mat3& rgbToXYZ) {
96    float3 w(rgbToXYZ * float3{1});
97    return w.xy / dot(w, float3{1});
98}
99
100ColorSpace::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
116ColorSpace::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
132ColorSpace::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
148ColorSpace::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
165ColorSpace::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
182ColorSpace::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
199constexpr 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
233const 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
242const 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
250const 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
261const 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
271const 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
280const 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
289const 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
298const 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
307const 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
316const 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
325const 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
334const 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
344const 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
354std::unique_ptr<float3> ColorSpace::createLUT(uint32_t size,
355        const ColorSpace& src, const ColorSpace& dst) {
356
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
376static const float2 ILLUMINANT_D50_XY = {0.34567f, 0.35850f};
377static const float3 ILLUMINANT_D50_XYZ = {0.964212f, 1.0f, 0.825188f};
378static 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
384static 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
390ColorSpaceConnector::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