xref: /aosp_15_r20/external/skia/modules/skcms/skcms.cc (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2018 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "src/skcms_public.h"  // NO_G3_REWRITE
9*c8dee2aaSAndroid Build Coastguard Worker #include "src/skcms_internals.h"  // NO_G3_REWRITE
10*c8dee2aaSAndroid Build Coastguard Worker #include "src/skcms_Transform.h"  // NO_G3_REWRITE
11*c8dee2aaSAndroid Build Coastguard Worker #include <assert.h>
12*c8dee2aaSAndroid Build Coastguard Worker #include <float.h>
13*c8dee2aaSAndroid Build Coastguard Worker #include <limits.h>
14*c8dee2aaSAndroid Build Coastguard Worker #include <stdlib.h>
15*c8dee2aaSAndroid Build Coastguard Worker #include <string.h>
16*c8dee2aaSAndroid Build Coastguard Worker 
17*c8dee2aaSAndroid Build Coastguard Worker #if defined(__ARM_NEON)
18*c8dee2aaSAndroid Build Coastguard Worker     #include <arm_neon.h>
19*c8dee2aaSAndroid Build Coastguard Worker #elif defined(__SSE__)
20*c8dee2aaSAndroid Build Coastguard Worker     #include <immintrin.h>
21*c8dee2aaSAndroid Build Coastguard Worker 
22*c8dee2aaSAndroid Build Coastguard Worker     #if defined(__clang__)
23*c8dee2aaSAndroid Build Coastguard Worker         // That #include <immintrin.h> is usually enough, but Clang's headers
24*c8dee2aaSAndroid Build Coastguard Worker         // "helpfully" skip including the whole kitchen sink when _MSC_VER is
25*c8dee2aaSAndroid Build Coastguard Worker         // defined, because lots of programs on Windows would include that and
26*c8dee2aaSAndroid Build Coastguard Worker         // it'd be a lot slower.  But we want all those headers included so we
27*c8dee2aaSAndroid Build Coastguard Worker         // can use their features after runtime checks later.
28*c8dee2aaSAndroid Build Coastguard Worker         #include <smmintrin.h>
29*c8dee2aaSAndroid Build Coastguard Worker         #include <avxintrin.h>
30*c8dee2aaSAndroid Build Coastguard Worker         #include <avx2intrin.h>
31*c8dee2aaSAndroid Build Coastguard Worker         #include <avx512fintrin.h>
32*c8dee2aaSAndroid Build Coastguard Worker         #include <avx512dqintrin.h>
33*c8dee2aaSAndroid Build Coastguard Worker     #endif
34*c8dee2aaSAndroid Build Coastguard Worker #endif
35*c8dee2aaSAndroid Build Coastguard Worker 
36*c8dee2aaSAndroid Build Coastguard Worker using namespace skcms_private;
37*c8dee2aaSAndroid Build Coastguard Worker 
38*c8dee2aaSAndroid Build Coastguard Worker static bool sAllowRuntimeCPUDetection = true;
39*c8dee2aaSAndroid Build Coastguard Worker 
skcms_DisableRuntimeCPUDetection()40*c8dee2aaSAndroid Build Coastguard Worker void skcms_DisableRuntimeCPUDetection() {
41*c8dee2aaSAndroid Build Coastguard Worker     sAllowRuntimeCPUDetection = false;
42*c8dee2aaSAndroid Build Coastguard Worker }
43*c8dee2aaSAndroid Build Coastguard Worker 
log2f_(float x)44*c8dee2aaSAndroid Build Coastguard Worker static float log2f_(float x) {
45*c8dee2aaSAndroid Build Coastguard Worker     // The first approximation of log2(x) is its exponent 'e', minus 127.
46*c8dee2aaSAndroid Build Coastguard Worker     int32_t bits;
47*c8dee2aaSAndroid Build Coastguard Worker     memcpy(&bits, &x, sizeof(bits));
48*c8dee2aaSAndroid Build Coastguard Worker 
49*c8dee2aaSAndroid Build Coastguard Worker     float e = (float)bits * (1.0f / (1<<23));
50*c8dee2aaSAndroid Build Coastguard Worker 
51*c8dee2aaSAndroid Build Coastguard Worker     // If we use the mantissa too we can refine the error signficantly.
52*c8dee2aaSAndroid Build Coastguard Worker     int32_t m_bits = (bits & 0x007fffff) | 0x3f000000;
53*c8dee2aaSAndroid Build Coastguard Worker     float m;
54*c8dee2aaSAndroid Build Coastguard Worker     memcpy(&m, &m_bits, sizeof(m));
55*c8dee2aaSAndroid Build Coastguard Worker 
56*c8dee2aaSAndroid Build Coastguard Worker     return (e - 124.225514990f
57*c8dee2aaSAndroid Build Coastguard Worker               -   1.498030302f*m
58*c8dee2aaSAndroid Build Coastguard Worker               -   1.725879990f/(0.3520887068f + m));
59*c8dee2aaSAndroid Build Coastguard Worker }
logf_(float x)60*c8dee2aaSAndroid Build Coastguard Worker static float logf_(float x) {
61*c8dee2aaSAndroid Build Coastguard Worker     const float ln2 = 0.69314718f;
62*c8dee2aaSAndroid Build Coastguard Worker     return ln2*log2f_(x);
63*c8dee2aaSAndroid Build Coastguard Worker }
64*c8dee2aaSAndroid Build Coastguard Worker 
exp2f_(float x)65*c8dee2aaSAndroid Build Coastguard Worker static float exp2f_(float x) {
66*c8dee2aaSAndroid Build Coastguard Worker     if (x > 128.0f) {
67*c8dee2aaSAndroid Build Coastguard Worker         return INFINITY_;
68*c8dee2aaSAndroid Build Coastguard Worker     } else if (x < -127.0f) {
69*c8dee2aaSAndroid Build Coastguard Worker         return 0.0f;
70*c8dee2aaSAndroid Build Coastguard Worker     }
71*c8dee2aaSAndroid Build Coastguard Worker     float fract = x - floorf_(x);
72*c8dee2aaSAndroid Build Coastguard Worker 
73*c8dee2aaSAndroid Build Coastguard Worker     float fbits = (1.0f * (1<<23)) * (x + 121.274057500f
74*c8dee2aaSAndroid Build Coastguard Worker                                         -   1.490129070f*fract
75*c8dee2aaSAndroid Build Coastguard Worker                                         +  27.728023300f/(4.84252568f - fract));
76*c8dee2aaSAndroid Build Coastguard Worker 
77*c8dee2aaSAndroid Build Coastguard Worker     // Before we cast fbits to int32_t, check for out of range values to pacify UBSAN.
78*c8dee2aaSAndroid Build Coastguard Worker     // INT_MAX is not exactly representable as a float, so exclude it as effectively infinite.
79*c8dee2aaSAndroid Build Coastguard Worker     // Negative values are effectively underflow - we'll end up returning a (different) negative
80*c8dee2aaSAndroid Build Coastguard Worker     // value, which makes no sense. So clamp to zero.
81*c8dee2aaSAndroid Build Coastguard Worker     if (fbits >= (float)INT_MAX) {
82*c8dee2aaSAndroid Build Coastguard Worker         return INFINITY_;
83*c8dee2aaSAndroid Build Coastguard Worker     } else if (fbits < 0) {
84*c8dee2aaSAndroid Build Coastguard Worker         return 0;
85*c8dee2aaSAndroid Build Coastguard Worker     }
86*c8dee2aaSAndroid Build Coastguard Worker 
87*c8dee2aaSAndroid Build Coastguard Worker     int32_t bits = (int32_t)fbits;
88*c8dee2aaSAndroid Build Coastguard Worker     memcpy(&x, &bits, sizeof(x));
89*c8dee2aaSAndroid Build Coastguard Worker     return x;
90*c8dee2aaSAndroid Build Coastguard Worker }
91*c8dee2aaSAndroid Build Coastguard Worker 
92*c8dee2aaSAndroid Build Coastguard Worker // Not static, as it's used by some test tools.
powf_(float x,float y)93*c8dee2aaSAndroid Build Coastguard Worker float powf_(float x, float y) {
94*c8dee2aaSAndroid Build Coastguard Worker     if (x <= 0.f) {
95*c8dee2aaSAndroid Build Coastguard Worker         return 0.f;
96*c8dee2aaSAndroid Build Coastguard Worker     }
97*c8dee2aaSAndroid Build Coastguard Worker     if (x == 1.f) {
98*c8dee2aaSAndroid Build Coastguard Worker         return 1.f;
99*c8dee2aaSAndroid Build Coastguard Worker     }
100*c8dee2aaSAndroid Build Coastguard Worker     return exp2f_(log2f_(x) * y);
101*c8dee2aaSAndroid Build Coastguard Worker }
102*c8dee2aaSAndroid Build Coastguard Worker 
expf_(float x)103*c8dee2aaSAndroid Build Coastguard Worker static float expf_(float x) {
104*c8dee2aaSAndroid Build Coastguard Worker     const float log2_e = 1.4426950408889634074f;
105*c8dee2aaSAndroid Build Coastguard Worker     return exp2f_(log2_e * x);
106*c8dee2aaSAndroid Build Coastguard Worker }
107*c8dee2aaSAndroid Build Coastguard Worker 
fmaxf_(float x,float y)108*c8dee2aaSAndroid Build Coastguard Worker static float fmaxf_(float x, float y) { return x > y ? x : y; }
fminf_(float x,float y)109*c8dee2aaSAndroid Build Coastguard Worker static float fminf_(float x, float y) { return x < y ? x : y; }
110*c8dee2aaSAndroid Build Coastguard Worker 
isfinitef_(float x)111*c8dee2aaSAndroid Build Coastguard Worker static bool isfinitef_(float x) { return 0 == x*0; }
112*c8dee2aaSAndroid Build Coastguard Worker 
minus_1_ulp(float x)113*c8dee2aaSAndroid Build Coastguard Worker static float minus_1_ulp(float x) {
114*c8dee2aaSAndroid Build Coastguard Worker     int32_t bits;
115*c8dee2aaSAndroid Build Coastguard Worker     memcpy(&bits, &x, sizeof(bits));
116*c8dee2aaSAndroid Build Coastguard Worker     bits = bits - 1;
117*c8dee2aaSAndroid Build Coastguard Worker     memcpy(&x, &bits, sizeof(bits));
118*c8dee2aaSAndroid Build Coastguard Worker     return x;
119*c8dee2aaSAndroid Build Coastguard Worker }
120*c8dee2aaSAndroid Build Coastguard Worker 
121*c8dee2aaSAndroid Build Coastguard Worker // Most transfer functions we work with are sRGBish.
122*c8dee2aaSAndroid Build Coastguard Worker // For exotic HDR transfer functions, we encode them using a tf.g that makes no sense,
123*c8dee2aaSAndroid Build Coastguard Worker // and repurpose the other fields to hold the parameters of the HDR functions.
124*c8dee2aaSAndroid Build Coastguard Worker struct TF_PQish  { float A,B,C,D,E,F; };
125*c8dee2aaSAndroid Build Coastguard Worker struct TF_HLGish { float R,G,a,b,c,K_minus_1; };
126*c8dee2aaSAndroid Build Coastguard Worker // We didn't originally support a scale factor K for HLG, and instead just stored 0 in
127*c8dee2aaSAndroid Build Coastguard Worker // the unused `f` field of skcms_TransferFunction for HLGish and HLGInvish transfer functions.
128*c8dee2aaSAndroid Build Coastguard Worker // By storing f=K-1, those old unusued f=0 values now mean K=1, a noop scale factor.
129*c8dee2aaSAndroid Build Coastguard Worker 
TFKind_marker(skcms_TFType kind)130*c8dee2aaSAndroid Build Coastguard Worker static float TFKind_marker(skcms_TFType kind) {
131*c8dee2aaSAndroid Build Coastguard Worker     // We'd use different NaNs, but those aren't guaranteed to be preserved by WASM.
132*c8dee2aaSAndroid Build Coastguard Worker     return -(float)kind;
133*c8dee2aaSAndroid Build Coastguard Worker }
134*c8dee2aaSAndroid Build Coastguard Worker 
classify(const skcms_TransferFunction & tf,TF_PQish * pq=nullptr,TF_HLGish * hlg=nullptr)135*c8dee2aaSAndroid Build Coastguard Worker static skcms_TFType classify(const skcms_TransferFunction& tf, TF_PQish*   pq = nullptr
136*c8dee2aaSAndroid Build Coastguard Worker                                                              , TF_HLGish* hlg = nullptr) {
137*c8dee2aaSAndroid Build Coastguard Worker     if (tf.g < 0) {
138*c8dee2aaSAndroid Build Coastguard Worker         // Negative "g" is mapped to enum values; large negative are for sure invalid.
139*c8dee2aaSAndroid Build Coastguard Worker         if (tf.g < -128) {
140*c8dee2aaSAndroid Build Coastguard Worker             return skcms_TFType_Invalid;
141*c8dee2aaSAndroid Build Coastguard Worker         }
142*c8dee2aaSAndroid Build Coastguard Worker         int enum_g = -static_cast<int>(tf.g);
143*c8dee2aaSAndroid Build Coastguard Worker         // Non-whole "g" values are invalid as well.
144*c8dee2aaSAndroid Build Coastguard Worker         if (static_cast<float>(-enum_g) != tf.g) {
145*c8dee2aaSAndroid Build Coastguard Worker             return skcms_TFType_Invalid;
146*c8dee2aaSAndroid Build Coastguard Worker         }
147*c8dee2aaSAndroid Build Coastguard Worker         // TODO: soundness checks for PQ/HLG like we do for sRGBish?
148*c8dee2aaSAndroid Build Coastguard Worker         switch (enum_g) {
149*c8dee2aaSAndroid Build Coastguard Worker             case skcms_TFType_PQish:
150*c8dee2aaSAndroid Build Coastguard Worker                 if (pq) {
151*c8dee2aaSAndroid Build Coastguard Worker                     memcpy(pq , &tf.a, sizeof(*pq ));
152*c8dee2aaSAndroid Build Coastguard Worker                 }
153*c8dee2aaSAndroid Build Coastguard Worker                 return skcms_TFType_PQish;
154*c8dee2aaSAndroid Build Coastguard Worker             case skcms_TFType_HLGish:
155*c8dee2aaSAndroid Build Coastguard Worker                 if (hlg) {
156*c8dee2aaSAndroid Build Coastguard Worker                     memcpy(hlg, &tf.a, sizeof(*hlg));
157*c8dee2aaSAndroid Build Coastguard Worker                 }
158*c8dee2aaSAndroid Build Coastguard Worker                 return skcms_TFType_HLGish;
159*c8dee2aaSAndroid Build Coastguard Worker             case skcms_TFType_HLGinvish:
160*c8dee2aaSAndroid Build Coastguard Worker                 if (hlg) {
161*c8dee2aaSAndroid Build Coastguard Worker                     memcpy(hlg, &tf.a, sizeof(*hlg));
162*c8dee2aaSAndroid Build Coastguard Worker                 }
163*c8dee2aaSAndroid Build Coastguard Worker                 return skcms_TFType_HLGinvish;
164*c8dee2aaSAndroid Build Coastguard Worker         }
165*c8dee2aaSAndroid Build Coastguard Worker         return skcms_TFType_Invalid;
166*c8dee2aaSAndroid Build Coastguard Worker     }
167*c8dee2aaSAndroid Build Coastguard Worker 
168*c8dee2aaSAndroid Build Coastguard Worker     // Basic soundness checks for sRGBish transfer functions.
169*c8dee2aaSAndroid Build Coastguard Worker     if (isfinitef_(tf.a + tf.b + tf.c + tf.d + tf.e + tf.f + tf.g)
170*c8dee2aaSAndroid Build Coastguard Worker             // a,c,d,g should be non-negative to make any sense.
171*c8dee2aaSAndroid Build Coastguard Worker             && tf.a >= 0
172*c8dee2aaSAndroid Build Coastguard Worker             && tf.c >= 0
173*c8dee2aaSAndroid Build Coastguard Worker             && tf.d >= 0
174*c8dee2aaSAndroid Build Coastguard Worker             && tf.g >= 0
175*c8dee2aaSAndroid Build Coastguard Worker             // Raising a negative value to a fractional tf->g produces complex numbers.
176*c8dee2aaSAndroid Build Coastguard Worker             && tf.a * tf.d + tf.b >= 0) {
177*c8dee2aaSAndroid Build Coastguard Worker         return skcms_TFType_sRGBish;
178*c8dee2aaSAndroid Build Coastguard Worker     }
179*c8dee2aaSAndroid Build Coastguard Worker 
180*c8dee2aaSAndroid Build Coastguard Worker     return skcms_TFType_Invalid;
181*c8dee2aaSAndroid Build Coastguard Worker }
182*c8dee2aaSAndroid Build Coastguard Worker 
skcms_TransferFunction_getType(const skcms_TransferFunction * tf)183*c8dee2aaSAndroid Build Coastguard Worker skcms_TFType skcms_TransferFunction_getType(const skcms_TransferFunction* tf) {
184*c8dee2aaSAndroid Build Coastguard Worker     return classify(*tf);
185*c8dee2aaSAndroid Build Coastguard Worker }
skcms_TransferFunction_isSRGBish(const skcms_TransferFunction * tf)186*c8dee2aaSAndroid Build Coastguard Worker bool skcms_TransferFunction_isSRGBish(const skcms_TransferFunction* tf) {
187*c8dee2aaSAndroid Build Coastguard Worker     return classify(*tf) == skcms_TFType_sRGBish;
188*c8dee2aaSAndroid Build Coastguard Worker }
skcms_TransferFunction_isPQish(const skcms_TransferFunction * tf)189*c8dee2aaSAndroid Build Coastguard Worker bool skcms_TransferFunction_isPQish(const skcms_TransferFunction* tf) {
190*c8dee2aaSAndroid Build Coastguard Worker     return classify(*tf) == skcms_TFType_PQish;
191*c8dee2aaSAndroid Build Coastguard Worker }
skcms_TransferFunction_isHLGish(const skcms_TransferFunction * tf)192*c8dee2aaSAndroid Build Coastguard Worker bool skcms_TransferFunction_isHLGish(const skcms_TransferFunction* tf) {
193*c8dee2aaSAndroid Build Coastguard Worker     return classify(*tf) == skcms_TFType_HLGish;
194*c8dee2aaSAndroid Build Coastguard Worker }
195*c8dee2aaSAndroid Build Coastguard Worker 
skcms_TransferFunction_makePQish(skcms_TransferFunction * tf,float A,float B,float C,float D,float E,float F)196*c8dee2aaSAndroid Build Coastguard Worker bool skcms_TransferFunction_makePQish(skcms_TransferFunction* tf,
197*c8dee2aaSAndroid Build Coastguard Worker                                       float A, float B, float C,
198*c8dee2aaSAndroid Build Coastguard Worker                                       float D, float E, float F) {
199*c8dee2aaSAndroid Build Coastguard Worker     *tf = { TFKind_marker(skcms_TFType_PQish), A,B,C,D,E,F };
200*c8dee2aaSAndroid Build Coastguard Worker     assert(skcms_TransferFunction_isPQish(tf));
201*c8dee2aaSAndroid Build Coastguard Worker     return true;
202*c8dee2aaSAndroid Build Coastguard Worker }
203*c8dee2aaSAndroid Build Coastguard Worker 
skcms_TransferFunction_makeScaledHLGish(skcms_TransferFunction * tf,float K,float R,float G,float a,float b,float c)204*c8dee2aaSAndroid Build Coastguard Worker bool skcms_TransferFunction_makeScaledHLGish(skcms_TransferFunction* tf,
205*c8dee2aaSAndroid Build Coastguard Worker                                              float K, float R, float G,
206*c8dee2aaSAndroid Build Coastguard Worker                                              float a, float b, float c) {
207*c8dee2aaSAndroid Build Coastguard Worker     *tf = { TFKind_marker(skcms_TFType_HLGish), R,G, a,b,c, K-1.0f };
208*c8dee2aaSAndroid Build Coastguard Worker     assert(skcms_TransferFunction_isHLGish(tf));
209*c8dee2aaSAndroid Build Coastguard Worker     return true;
210*c8dee2aaSAndroid Build Coastguard Worker }
211*c8dee2aaSAndroid Build Coastguard Worker 
skcms_TransferFunction_eval(const skcms_TransferFunction * tf,float x)212*c8dee2aaSAndroid Build Coastguard Worker float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
213*c8dee2aaSAndroid Build Coastguard Worker     float sign = x < 0 ? -1.0f : 1.0f;
214*c8dee2aaSAndroid Build Coastguard Worker     x *= sign;
215*c8dee2aaSAndroid Build Coastguard Worker 
216*c8dee2aaSAndroid Build Coastguard Worker     TF_PQish  pq;
217*c8dee2aaSAndroid Build Coastguard Worker     TF_HLGish hlg;
218*c8dee2aaSAndroid Build Coastguard Worker     switch (classify(*tf, &pq, &hlg)) {
219*c8dee2aaSAndroid Build Coastguard Worker         case skcms_TFType_Invalid: break;
220*c8dee2aaSAndroid Build Coastguard Worker 
221*c8dee2aaSAndroid Build Coastguard Worker         case skcms_TFType_HLGish: {
222*c8dee2aaSAndroid Build Coastguard Worker             const float K = hlg.K_minus_1 + 1.0f;
223*c8dee2aaSAndroid Build Coastguard Worker             return K * sign * (x*hlg.R <= 1 ? powf_(x*hlg.R, hlg.G)
224*c8dee2aaSAndroid Build Coastguard Worker                                             : expf_((x-hlg.c)*hlg.a) + hlg.b);
225*c8dee2aaSAndroid Build Coastguard Worker         }
226*c8dee2aaSAndroid Build Coastguard Worker 
227*c8dee2aaSAndroid Build Coastguard Worker         // skcms_TransferFunction_invert() inverts R, G, and a for HLGinvish so this math is fast.
228*c8dee2aaSAndroid Build Coastguard Worker         case skcms_TFType_HLGinvish: {
229*c8dee2aaSAndroid Build Coastguard Worker             const float K = hlg.K_minus_1 + 1.0f;
230*c8dee2aaSAndroid Build Coastguard Worker             x /= K;
231*c8dee2aaSAndroid Build Coastguard Worker             return sign * (x <= 1 ? hlg.R * powf_(x, hlg.G)
232*c8dee2aaSAndroid Build Coastguard Worker                                   : hlg.a * logf_(x - hlg.b) + hlg.c);
233*c8dee2aaSAndroid Build Coastguard Worker         }
234*c8dee2aaSAndroid Build Coastguard Worker 
235*c8dee2aaSAndroid Build Coastguard Worker         case skcms_TFType_sRGBish:
236*c8dee2aaSAndroid Build Coastguard Worker             return sign * (x < tf->d ?       tf->c * x + tf->f
237*c8dee2aaSAndroid Build Coastguard Worker                                      : powf_(tf->a * x + tf->b, tf->g) + tf->e);
238*c8dee2aaSAndroid Build Coastguard Worker 
239*c8dee2aaSAndroid Build Coastguard Worker         case skcms_TFType_PQish:
240*c8dee2aaSAndroid Build Coastguard Worker             return sign *
241*c8dee2aaSAndroid Build Coastguard Worker                    powf_((pq.A + pq.B * powf_(x, pq.C)) / (pq.D + pq.E * powf_(x, pq.C)), pq.F);
242*c8dee2aaSAndroid Build Coastguard Worker     }
243*c8dee2aaSAndroid Build Coastguard Worker     return 0;
244*c8dee2aaSAndroid Build Coastguard Worker }
245*c8dee2aaSAndroid Build Coastguard Worker 
246*c8dee2aaSAndroid Build Coastguard Worker 
eval_curve(const skcms_Curve * curve,float x)247*c8dee2aaSAndroid Build Coastguard Worker static float eval_curve(const skcms_Curve* curve, float x) {
248*c8dee2aaSAndroid Build Coastguard Worker     if (curve->table_entries == 0) {
249*c8dee2aaSAndroid Build Coastguard Worker         return skcms_TransferFunction_eval(&curve->parametric, x);
250*c8dee2aaSAndroid Build Coastguard Worker     }
251*c8dee2aaSAndroid Build Coastguard Worker 
252*c8dee2aaSAndroid Build Coastguard Worker     float ix = fmaxf_(0, fminf_(x, 1)) * static_cast<float>(curve->table_entries - 1);
253*c8dee2aaSAndroid Build Coastguard Worker     int   lo = (int)                   ix        ,
254*c8dee2aaSAndroid Build Coastguard Worker           hi = (int)(float)minus_1_ulp(ix + 1.0f);
255*c8dee2aaSAndroid Build Coastguard Worker     float t = ix - (float)lo;
256*c8dee2aaSAndroid Build Coastguard Worker 
257*c8dee2aaSAndroid Build Coastguard Worker     float l, h;
258*c8dee2aaSAndroid Build Coastguard Worker     if (curve->table_8) {
259*c8dee2aaSAndroid Build Coastguard Worker         l = curve->table_8[lo] * (1/255.0f);
260*c8dee2aaSAndroid Build Coastguard Worker         h = curve->table_8[hi] * (1/255.0f);
261*c8dee2aaSAndroid Build Coastguard Worker     } else {
262*c8dee2aaSAndroid Build Coastguard Worker         uint16_t be_l, be_h;
263*c8dee2aaSAndroid Build Coastguard Worker         memcpy(&be_l, curve->table_16 + 2*lo, 2);
264*c8dee2aaSAndroid Build Coastguard Worker         memcpy(&be_h, curve->table_16 + 2*hi, 2);
265*c8dee2aaSAndroid Build Coastguard Worker         uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
266*c8dee2aaSAndroid Build Coastguard Worker         uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
267*c8dee2aaSAndroid Build Coastguard Worker         l = le_l * (1/65535.0f);
268*c8dee2aaSAndroid Build Coastguard Worker         h = le_h * (1/65535.0f);
269*c8dee2aaSAndroid Build Coastguard Worker     }
270*c8dee2aaSAndroid Build Coastguard Worker     return l + (h-l)*t;
271*c8dee2aaSAndroid Build Coastguard Worker }
272*c8dee2aaSAndroid Build Coastguard Worker 
skcms_MaxRoundtripError(const skcms_Curve * curve,const skcms_TransferFunction * inv_tf)273*c8dee2aaSAndroid Build Coastguard Worker float skcms_MaxRoundtripError(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
274*c8dee2aaSAndroid Build Coastguard Worker     uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
275*c8dee2aaSAndroid Build Coastguard Worker     const float dx = 1.0f / static_cast<float>(N - 1);
276*c8dee2aaSAndroid Build Coastguard Worker     float err = 0;
277*c8dee2aaSAndroid Build Coastguard Worker     for (uint32_t i = 0; i < N; i++) {
278*c8dee2aaSAndroid Build Coastguard Worker         float x = static_cast<float>(i) * dx,
279*c8dee2aaSAndroid Build Coastguard Worker               y = eval_curve(curve, x);
280*c8dee2aaSAndroid Build Coastguard Worker         err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
281*c8dee2aaSAndroid Build Coastguard Worker     }
282*c8dee2aaSAndroid Build Coastguard Worker     return err;
283*c8dee2aaSAndroid Build Coastguard Worker }
284*c8dee2aaSAndroid Build Coastguard Worker 
skcms_AreApproximateInverses(const skcms_Curve * curve,const skcms_TransferFunction * inv_tf)285*c8dee2aaSAndroid Build Coastguard Worker bool skcms_AreApproximateInverses(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
286*c8dee2aaSAndroid Build Coastguard Worker     return skcms_MaxRoundtripError(curve, inv_tf) < (1/512.0f);
287*c8dee2aaSAndroid Build Coastguard Worker }
288*c8dee2aaSAndroid Build Coastguard Worker 
289*c8dee2aaSAndroid Build Coastguard Worker // Additional ICC signature values that are only used internally
290*c8dee2aaSAndroid Build Coastguard Worker enum {
291*c8dee2aaSAndroid Build Coastguard Worker     // File signature
292*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_acsp = 0x61637370,
293*c8dee2aaSAndroid Build Coastguard Worker 
294*c8dee2aaSAndroid Build Coastguard Worker     // Tag signatures
295*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_rTRC = 0x72545243,
296*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_gTRC = 0x67545243,
297*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_bTRC = 0x62545243,
298*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_kTRC = 0x6B545243,
299*c8dee2aaSAndroid Build Coastguard Worker 
300*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_rXYZ = 0x7258595A,
301*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_gXYZ = 0x6758595A,
302*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_bXYZ = 0x6258595A,
303*c8dee2aaSAndroid Build Coastguard Worker 
304*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_A2B0 = 0x41324230,
305*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_B2A0 = 0x42324130,
306*c8dee2aaSAndroid Build Coastguard Worker 
307*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_CHAD = 0x63686164,
308*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_WTPT = 0x77747074,
309*c8dee2aaSAndroid Build Coastguard Worker 
310*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_CICP = 0x63696370,
311*c8dee2aaSAndroid Build Coastguard Worker 
312*c8dee2aaSAndroid Build Coastguard Worker     // Type signatures
313*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_curv = 0x63757276,
314*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_mft1 = 0x6D667431,
315*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_mft2 = 0x6D667432,
316*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_mAB  = 0x6D414220,
317*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_mBA  = 0x6D424120,
318*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_para = 0x70617261,
319*c8dee2aaSAndroid Build Coastguard Worker     skcms_Signature_sf32 = 0x73663332,
320*c8dee2aaSAndroid Build Coastguard Worker     // XYZ is also a PCS signature, so it's defined in skcms.h
321*c8dee2aaSAndroid Build Coastguard Worker     // skcms_Signature_XYZ = 0x58595A20,
322*c8dee2aaSAndroid Build Coastguard Worker };
323*c8dee2aaSAndroid Build Coastguard Worker 
read_big_u16(const uint8_t * ptr)324*c8dee2aaSAndroid Build Coastguard Worker static uint16_t read_big_u16(const uint8_t* ptr) {
325*c8dee2aaSAndroid Build Coastguard Worker     uint16_t be;
326*c8dee2aaSAndroid Build Coastguard Worker     memcpy(&be, ptr, sizeof(be));
327*c8dee2aaSAndroid Build Coastguard Worker #if defined(_MSC_VER)
328*c8dee2aaSAndroid Build Coastguard Worker     return _byteswap_ushort(be);
329*c8dee2aaSAndroid Build Coastguard Worker #else
330*c8dee2aaSAndroid Build Coastguard Worker     return __builtin_bswap16(be);
331*c8dee2aaSAndroid Build Coastguard Worker #endif
332*c8dee2aaSAndroid Build Coastguard Worker }
333*c8dee2aaSAndroid Build Coastguard Worker 
read_big_u32(const uint8_t * ptr)334*c8dee2aaSAndroid Build Coastguard Worker static uint32_t read_big_u32(const uint8_t* ptr) {
335*c8dee2aaSAndroid Build Coastguard Worker     uint32_t be;
336*c8dee2aaSAndroid Build Coastguard Worker     memcpy(&be, ptr, sizeof(be));
337*c8dee2aaSAndroid Build Coastguard Worker #if defined(_MSC_VER)
338*c8dee2aaSAndroid Build Coastguard Worker     return _byteswap_ulong(be);
339*c8dee2aaSAndroid Build Coastguard Worker #else
340*c8dee2aaSAndroid Build Coastguard Worker     return __builtin_bswap32(be);
341*c8dee2aaSAndroid Build Coastguard Worker #endif
342*c8dee2aaSAndroid Build Coastguard Worker }
343*c8dee2aaSAndroid Build Coastguard Worker 
read_big_i32(const uint8_t * ptr)344*c8dee2aaSAndroid Build Coastguard Worker static int32_t read_big_i32(const uint8_t* ptr) {
345*c8dee2aaSAndroid Build Coastguard Worker     return (int32_t)read_big_u32(ptr);
346*c8dee2aaSAndroid Build Coastguard Worker }
347*c8dee2aaSAndroid Build Coastguard Worker 
read_big_fixed(const uint8_t * ptr)348*c8dee2aaSAndroid Build Coastguard Worker static float read_big_fixed(const uint8_t* ptr) {
349*c8dee2aaSAndroid Build Coastguard Worker     return static_cast<float>(read_big_i32(ptr)) * (1.0f / 65536.0f);
350*c8dee2aaSAndroid Build Coastguard Worker }
351*c8dee2aaSAndroid Build Coastguard Worker 
352*c8dee2aaSAndroid Build Coastguard Worker // Maps to an in-memory profile so that fields line up to the locations specified
353*c8dee2aaSAndroid Build Coastguard Worker // in ICC.1:2010, section 7.2
354*c8dee2aaSAndroid Build Coastguard Worker typedef struct {
355*c8dee2aaSAndroid Build Coastguard Worker     uint8_t size                [ 4];
356*c8dee2aaSAndroid Build Coastguard Worker     uint8_t cmm_type            [ 4];
357*c8dee2aaSAndroid Build Coastguard Worker     uint8_t version             [ 4];
358*c8dee2aaSAndroid Build Coastguard Worker     uint8_t profile_class       [ 4];
359*c8dee2aaSAndroid Build Coastguard Worker     uint8_t data_color_space    [ 4];
360*c8dee2aaSAndroid Build Coastguard Worker     uint8_t pcs                 [ 4];
361*c8dee2aaSAndroid Build Coastguard Worker     uint8_t creation_date_time  [12];
362*c8dee2aaSAndroid Build Coastguard Worker     uint8_t signature           [ 4];
363*c8dee2aaSAndroid Build Coastguard Worker     uint8_t platform            [ 4];
364*c8dee2aaSAndroid Build Coastguard Worker     uint8_t flags               [ 4];
365*c8dee2aaSAndroid Build Coastguard Worker     uint8_t device_manufacturer [ 4];
366*c8dee2aaSAndroid Build Coastguard Worker     uint8_t device_model        [ 4];
367*c8dee2aaSAndroid Build Coastguard Worker     uint8_t device_attributes   [ 8];
368*c8dee2aaSAndroid Build Coastguard Worker     uint8_t rendering_intent    [ 4];
369*c8dee2aaSAndroid Build Coastguard Worker     uint8_t illuminant_X        [ 4];
370*c8dee2aaSAndroid Build Coastguard Worker     uint8_t illuminant_Y        [ 4];
371*c8dee2aaSAndroid Build Coastguard Worker     uint8_t illuminant_Z        [ 4];
372*c8dee2aaSAndroid Build Coastguard Worker     uint8_t creator             [ 4];
373*c8dee2aaSAndroid Build Coastguard Worker     uint8_t profile_id          [16];
374*c8dee2aaSAndroid Build Coastguard Worker     uint8_t reserved            [28];
375*c8dee2aaSAndroid Build Coastguard Worker     uint8_t tag_count           [ 4]; // Technically not part of header, but required
376*c8dee2aaSAndroid Build Coastguard Worker } header_Layout;
377*c8dee2aaSAndroid Build Coastguard Worker 
378*c8dee2aaSAndroid Build Coastguard Worker typedef struct {
379*c8dee2aaSAndroid Build Coastguard Worker     uint8_t signature [4];
380*c8dee2aaSAndroid Build Coastguard Worker     uint8_t offset    [4];
381*c8dee2aaSAndroid Build Coastguard Worker     uint8_t size      [4];
382*c8dee2aaSAndroid Build Coastguard Worker } tag_Layout;
383*c8dee2aaSAndroid Build Coastguard Worker 
get_tag_table(const skcms_ICCProfile * profile)384*c8dee2aaSAndroid Build Coastguard Worker static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
385*c8dee2aaSAndroid Build Coastguard Worker     return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
386*c8dee2aaSAndroid Build Coastguard Worker }
387*c8dee2aaSAndroid Build Coastguard Worker 
388*c8dee2aaSAndroid Build Coastguard Worker // s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
389*c8dee2aaSAndroid Build Coastguard Worker // use of the type is for the CHAD tag that stores exactly nine values.
390*c8dee2aaSAndroid Build Coastguard Worker typedef struct {
391*c8dee2aaSAndroid Build Coastguard Worker     uint8_t type     [ 4];
392*c8dee2aaSAndroid Build Coastguard Worker     uint8_t reserved [ 4];
393*c8dee2aaSAndroid Build Coastguard Worker     uint8_t values   [36];
394*c8dee2aaSAndroid Build Coastguard Worker } sf32_Layout;
395*c8dee2aaSAndroid Build Coastguard Worker 
skcms_GetCHAD(const skcms_ICCProfile * profile,skcms_Matrix3x3 * m)396*c8dee2aaSAndroid Build Coastguard Worker bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
397*c8dee2aaSAndroid Build Coastguard Worker     skcms_ICCTag tag;
398*c8dee2aaSAndroid Build Coastguard Worker     if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
399*c8dee2aaSAndroid Build Coastguard Worker         return false;
400*c8dee2aaSAndroid Build Coastguard Worker     }
401*c8dee2aaSAndroid Build Coastguard Worker 
402*c8dee2aaSAndroid Build Coastguard Worker     if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)) {
403*c8dee2aaSAndroid Build Coastguard Worker         return false;
404*c8dee2aaSAndroid Build Coastguard Worker     }
405*c8dee2aaSAndroid Build Coastguard Worker 
406*c8dee2aaSAndroid Build Coastguard Worker     const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
407*c8dee2aaSAndroid Build Coastguard Worker     const uint8_t* values = sf32Tag->values;
408*c8dee2aaSAndroid Build Coastguard Worker     for (int r = 0; r < 3; ++r)
409*c8dee2aaSAndroid Build Coastguard Worker     for (int c = 0; c < 3; ++c, values += 4) {
410*c8dee2aaSAndroid Build Coastguard Worker         m->vals[r][c] = read_big_fixed(values);
411*c8dee2aaSAndroid Build Coastguard Worker     }
412*c8dee2aaSAndroid Build Coastguard Worker     return true;
413*c8dee2aaSAndroid Build Coastguard Worker }
414*c8dee2aaSAndroid Build Coastguard Worker 
415*c8dee2aaSAndroid Build Coastguard Worker // XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
416*c8dee2aaSAndroid Build Coastguard Worker // the type are for tags/data that store exactly one triple.
417*c8dee2aaSAndroid Build Coastguard Worker typedef struct {
418*c8dee2aaSAndroid Build Coastguard Worker     uint8_t type     [4];
419*c8dee2aaSAndroid Build Coastguard Worker     uint8_t reserved [4];
420*c8dee2aaSAndroid Build Coastguard Worker     uint8_t X        [4];
421*c8dee2aaSAndroid Build Coastguard Worker     uint8_t Y        [4];
422*c8dee2aaSAndroid Build Coastguard Worker     uint8_t Z        [4];
423*c8dee2aaSAndroid Build Coastguard Worker } XYZ_Layout;
424*c8dee2aaSAndroid Build Coastguard Worker 
read_tag_xyz(const skcms_ICCTag * tag,float * x,float * y,float * z)425*c8dee2aaSAndroid Build Coastguard Worker static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
426*c8dee2aaSAndroid Build Coastguard Worker     if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
427*c8dee2aaSAndroid Build Coastguard Worker         return false;
428*c8dee2aaSAndroid Build Coastguard Worker     }
429*c8dee2aaSAndroid Build Coastguard Worker 
430*c8dee2aaSAndroid Build Coastguard Worker     const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
431*c8dee2aaSAndroid Build Coastguard Worker 
432*c8dee2aaSAndroid Build Coastguard Worker     *x = read_big_fixed(xyzTag->X);
433*c8dee2aaSAndroid Build Coastguard Worker     *y = read_big_fixed(xyzTag->Y);
434*c8dee2aaSAndroid Build Coastguard Worker     *z = read_big_fixed(xyzTag->Z);
435*c8dee2aaSAndroid Build Coastguard Worker     return true;
436*c8dee2aaSAndroid Build Coastguard Worker }
437*c8dee2aaSAndroid Build Coastguard Worker 
skcms_GetWTPT(const skcms_ICCProfile * profile,float xyz[3])438*c8dee2aaSAndroid Build Coastguard Worker bool skcms_GetWTPT(const skcms_ICCProfile* profile, float xyz[3]) {
439*c8dee2aaSAndroid Build Coastguard Worker     skcms_ICCTag tag;
440*c8dee2aaSAndroid Build Coastguard Worker     return skcms_GetTagBySignature(profile, skcms_Signature_WTPT, &tag) &&
441*c8dee2aaSAndroid Build Coastguard Worker            read_tag_xyz(&tag, &xyz[0], &xyz[1], &xyz[2]);
442*c8dee2aaSAndroid Build Coastguard Worker }
443*c8dee2aaSAndroid Build Coastguard Worker 
data_color_space_channel_count(uint32_t data_color_space)444*c8dee2aaSAndroid Build Coastguard Worker static int data_color_space_channel_count(uint32_t data_color_space) {
445*c8dee2aaSAndroid Build Coastguard Worker     switch (data_color_space) {
446*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_CMYK:   return 4;
447*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_Gray:   return 1;
448*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_RGB:    return 3;
449*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_Lab:    return 3;
450*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_XYZ:    return 3;
451*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_CIELUV: return 3;
452*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_YCbCr:  return 3;
453*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_CIEYxy: return 3;
454*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_HSV:    return 3;
455*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_HLS:    return 3;
456*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_CMY:    return 3;
457*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_2CLR:   return 2;
458*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_3CLR:   return 3;
459*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_4CLR:   return 4;
460*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_5CLR:   return 5;
461*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_6CLR:   return 6;
462*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_7CLR:   return 7;
463*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_8CLR:   return 8;
464*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_9CLR:   return 9;
465*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_10CLR:  return 10;
466*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_11CLR:  return 11;
467*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_12CLR:  return 12;
468*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_13CLR:  return 13;
469*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_14CLR:  return 14;
470*c8dee2aaSAndroid Build Coastguard Worker         case skcms_Signature_15CLR:  return 15;
471*c8dee2aaSAndroid Build Coastguard Worker         default:                     return -1;
472*c8dee2aaSAndroid Build Coastguard Worker     }
473*c8dee2aaSAndroid Build Coastguard Worker }
474*c8dee2aaSAndroid Build Coastguard Worker 
skcms_GetInputChannelCount(const skcms_ICCProfile * profile)475*c8dee2aaSAndroid Build Coastguard Worker int skcms_GetInputChannelCount(const skcms_ICCProfile* profile) {
476*c8dee2aaSAndroid Build Coastguard Worker     int a2b_count = 0;
477*c8dee2aaSAndroid Build Coastguard Worker     if (profile->has_A2B) {
478*c8dee2aaSAndroid Build Coastguard Worker         a2b_count = profile->A2B.input_channels != 0
479*c8dee2aaSAndroid Build Coastguard Worker                         ? static_cast<int>(profile->A2B.input_channels)
480*c8dee2aaSAndroid Build Coastguard Worker                         : 3;
481*c8dee2aaSAndroid Build Coastguard Worker     }
482*c8dee2aaSAndroid Build Coastguard Worker 
483*c8dee2aaSAndroid Build Coastguard Worker     skcms_ICCTag tag;
484*c8dee2aaSAndroid Build Coastguard Worker     int trc_count = 0;
485*c8dee2aaSAndroid Build Coastguard Worker     if (skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &tag)) {
486*c8dee2aaSAndroid Build Coastguard Worker         trc_count = 1;
487*c8dee2aaSAndroid Build Coastguard Worker     } else if (profile->has_trc) {
488*c8dee2aaSAndroid Build Coastguard Worker         trc_count = 3;
489*c8dee2aaSAndroid Build Coastguard Worker     }
490*c8dee2aaSAndroid Build Coastguard Worker 
491*c8dee2aaSAndroid Build Coastguard Worker     int dcs_count = data_color_space_channel_count(profile->data_color_space);
492*c8dee2aaSAndroid Build Coastguard Worker 
493*c8dee2aaSAndroid Build Coastguard Worker     if (dcs_count < 0) {
494*c8dee2aaSAndroid Build Coastguard Worker         return -1;
495*c8dee2aaSAndroid Build Coastguard Worker     }
496*c8dee2aaSAndroid Build Coastguard Worker 
497*c8dee2aaSAndroid Build Coastguard Worker     if (a2b_count > 0 && a2b_count != dcs_count) {
498*c8dee2aaSAndroid Build Coastguard Worker         return -1;
499*c8dee2aaSAndroid Build Coastguard Worker     }
500*c8dee2aaSAndroid Build Coastguard Worker     if (trc_count > 0 && trc_count != dcs_count) {
501*c8dee2aaSAndroid Build Coastguard Worker         return -1;
502*c8dee2aaSAndroid Build Coastguard Worker     }
503*c8dee2aaSAndroid Build Coastguard Worker 
504*c8dee2aaSAndroid Build Coastguard Worker     return dcs_count;
505*c8dee2aaSAndroid Build Coastguard Worker }
506*c8dee2aaSAndroid Build Coastguard Worker 
read_to_XYZD50(const skcms_ICCTag * rXYZ,const skcms_ICCTag * gXYZ,const skcms_ICCTag * bXYZ,skcms_Matrix3x3 * toXYZ)507*c8dee2aaSAndroid Build Coastguard Worker static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
508*c8dee2aaSAndroid Build Coastguard Worker                            const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
509*c8dee2aaSAndroid Build Coastguard Worker     return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
510*c8dee2aaSAndroid Build Coastguard Worker            read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
511*c8dee2aaSAndroid Build Coastguard Worker            read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
512*c8dee2aaSAndroid Build Coastguard Worker }
513*c8dee2aaSAndroid Build Coastguard Worker 
514*c8dee2aaSAndroid Build Coastguard Worker typedef struct {
515*c8dee2aaSAndroid Build Coastguard Worker     uint8_t type          [4];
516*c8dee2aaSAndroid Build Coastguard Worker     uint8_t reserved_a    [4];
517*c8dee2aaSAndroid Build Coastguard Worker     uint8_t function_type [2];
518*c8dee2aaSAndroid Build Coastguard Worker     uint8_t reserved_b    [2];
519*c8dee2aaSAndroid Build Coastguard Worker     uint8_t variable      [1/*variable*/];  // 1, 3, 4, 5, or 7 s15.16, depending on function_type
520*c8dee2aaSAndroid Build Coastguard Worker } para_Layout;
521*c8dee2aaSAndroid Build Coastguard Worker 
read_curve_para(const uint8_t * buf,uint32_t size,skcms_Curve * curve,uint32_t * curve_size)522*c8dee2aaSAndroid Build Coastguard Worker static bool read_curve_para(const uint8_t* buf, uint32_t size,
523*c8dee2aaSAndroid Build Coastguard Worker                             skcms_Curve* curve, uint32_t* curve_size) {
524*c8dee2aaSAndroid Build Coastguard Worker     if (size < SAFE_FIXED_SIZE(para_Layout)) {
525*c8dee2aaSAndroid Build Coastguard Worker         return false;
526*c8dee2aaSAndroid Build Coastguard Worker     }
527*c8dee2aaSAndroid Build Coastguard Worker 
528*c8dee2aaSAndroid Build Coastguard Worker     const para_Layout* paraTag = (const para_Layout*)buf;
529*c8dee2aaSAndroid Build Coastguard Worker 
530*c8dee2aaSAndroid Build Coastguard Worker     enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
531*c8dee2aaSAndroid Build Coastguard Worker     uint16_t function_type = read_big_u16(paraTag->function_type);
532*c8dee2aaSAndroid Build Coastguard Worker     if (function_type > kGABCDEF) {
533*c8dee2aaSAndroid Build Coastguard Worker         return false;
534*c8dee2aaSAndroid Build Coastguard Worker     }
535*c8dee2aaSAndroid Build Coastguard Worker 
536*c8dee2aaSAndroid Build Coastguard Worker     static const uint32_t curve_bytes[] = { 4, 12, 16, 20, 28 };
537*c8dee2aaSAndroid Build Coastguard Worker     if (size < SAFE_FIXED_SIZE(para_Layout) + curve_bytes[function_type]) {
538*c8dee2aaSAndroid Build Coastguard Worker         return false;
539*c8dee2aaSAndroid Build Coastguard Worker     }
540*c8dee2aaSAndroid Build Coastguard Worker 
541*c8dee2aaSAndroid Build Coastguard Worker     if (curve_size) {
542*c8dee2aaSAndroid Build Coastguard Worker         *curve_size = SAFE_FIXED_SIZE(para_Layout) + curve_bytes[function_type];
543*c8dee2aaSAndroid Build Coastguard Worker     }
544*c8dee2aaSAndroid Build Coastguard Worker 
545*c8dee2aaSAndroid Build Coastguard Worker     curve->table_entries = 0;
546*c8dee2aaSAndroid Build Coastguard Worker     curve->parametric.a  = 1.0f;
547*c8dee2aaSAndroid Build Coastguard Worker     curve->parametric.b  = 0.0f;
548*c8dee2aaSAndroid Build Coastguard Worker     curve->parametric.c  = 0.0f;
549*c8dee2aaSAndroid Build Coastguard Worker     curve->parametric.d  = 0.0f;
550*c8dee2aaSAndroid Build Coastguard Worker     curve->parametric.e  = 0.0f;
551*c8dee2aaSAndroid Build Coastguard Worker     curve->parametric.f  = 0.0f;
552*c8dee2aaSAndroid Build Coastguard Worker     curve->parametric.g  = read_big_fixed(paraTag->variable);
553*c8dee2aaSAndroid Build Coastguard Worker 
554*c8dee2aaSAndroid Build Coastguard Worker     switch (function_type) {
555*c8dee2aaSAndroid Build Coastguard Worker         case kGAB:
556*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.a = read_big_fixed(paraTag->variable + 4);
557*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.b = read_big_fixed(paraTag->variable + 8);
558*c8dee2aaSAndroid Build Coastguard Worker             if (curve->parametric.a == 0) {
559*c8dee2aaSAndroid Build Coastguard Worker                 return false;
560*c8dee2aaSAndroid Build Coastguard Worker             }
561*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.d = -curve->parametric.b / curve->parametric.a;
562*c8dee2aaSAndroid Build Coastguard Worker             break;
563*c8dee2aaSAndroid Build Coastguard Worker         case kGABC:
564*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.a = read_big_fixed(paraTag->variable + 4);
565*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.b = read_big_fixed(paraTag->variable + 8);
566*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.e = read_big_fixed(paraTag->variable + 12);
567*c8dee2aaSAndroid Build Coastguard Worker             if (curve->parametric.a == 0) {
568*c8dee2aaSAndroid Build Coastguard Worker                 return false;
569*c8dee2aaSAndroid Build Coastguard Worker             }
570*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.d = -curve->parametric.b / curve->parametric.a;
571*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.f = curve->parametric.e;
572*c8dee2aaSAndroid Build Coastguard Worker             break;
573*c8dee2aaSAndroid Build Coastguard Worker         case kGABCD:
574*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.a = read_big_fixed(paraTag->variable + 4);
575*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.b = read_big_fixed(paraTag->variable + 8);
576*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.c = read_big_fixed(paraTag->variable + 12);
577*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.d = read_big_fixed(paraTag->variable + 16);
578*c8dee2aaSAndroid Build Coastguard Worker             break;
579*c8dee2aaSAndroid Build Coastguard Worker         case kGABCDEF:
580*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.a = read_big_fixed(paraTag->variable + 4);
581*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.b = read_big_fixed(paraTag->variable + 8);
582*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.c = read_big_fixed(paraTag->variable + 12);
583*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.d = read_big_fixed(paraTag->variable + 16);
584*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.e = read_big_fixed(paraTag->variable + 20);
585*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.f = read_big_fixed(paraTag->variable + 24);
586*c8dee2aaSAndroid Build Coastguard Worker             break;
587*c8dee2aaSAndroid Build Coastguard Worker     }
588*c8dee2aaSAndroid Build Coastguard Worker     return skcms_TransferFunction_isSRGBish(&curve->parametric);
589*c8dee2aaSAndroid Build Coastguard Worker }
590*c8dee2aaSAndroid Build Coastguard Worker 
591*c8dee2aaSAndroid Build Coastguard Worker typedef struct {
592*c8dee2aaSAndroid Build Coastguard Worker     uint8_t type          [4];
593*c8dee2aaSAndroid Build Coastguard Worker     uint8_t reserved      [4];
594*c8dee2aaSAndroid Build Coastguard Worker     uint8_t value_count   [4];
595*c8dee2aaSAndroid Build Coastguard Worker     uint8_t variable      [1/*variable*/];  // value_count, 8.8 if 1, uint16 (n*65535) if > 1
596*c8dee2aaSAndroid Build Coastguard Worker } curv_Layout;
597*c8dee2aaSAndroid Build Coastguard Worker 
read_curve_curv(const uint8_t * buf,uint32_t size,skcms_Curve * curve,uint32_t * curve_size)598*c8dee2aaSAndroid Build Coastguard Worker static bool read_curve_curv(const uint8_t* buf, uint32_t size,
599*c8dee2aaSAndroid Build Coastguard Worker                             skcms_Curve* curve, uint32_t* curve_size) {
600*c8dee2aaSAndroid Build Coastguard Worker     if (size < SAFE_FIXED_SIZE(curv_Layout)) {
601*c8dee2aaSAndroid Build Coastguard Worker         return false;
602*c8dee2aaSAndroid Build Coastguard Worker     }
603*c8dee2aaSAndroid Build Coastguard Worker 
604*c8dee2aaSAndroid Build Coastguard Worker     const curv_Layout* curvTag = (const curv_Layout*)buf;
605*c8dee2aaSAndroid Build Coastguard Worker 
606*c8dee2aaSAndroid Build Coastguard Worker     uint32_t value_count = read_big_u32(curvTag->value_count);
607*c8dee2aaSAndroid Build Coastguard Worker     if (size < SAFE_FIXED_SIZE(curv_Layout) + value_count * SAFE_SIZEOF(uint16_t)) {
608*c8dee2aaSAndroid Build Coastguard Worker         return false;
609*c8dee2aaSAndroid Build Coastguard Worker     }
610*c8dee2aaSAndroid Build Coastguard Worker 
611*c8dee2aaSAndroid Build Coastguard Worker     if (curve_size) {
612*c8dee2aaSAndroid Build Coastguard Worker         *curve_size = SAFE_FIXED_SIZE(curv_Layout) + value_count * SAFE_SIZEOF(uint16_t);
613*c8dee2aaSAndroid Build Coastguard Worker     }
614*c8dee2aaSAndroid Build Coastguard Worker 
615*c8dee2aaSAndroid Build Coastguard Worker     if (value_count < 2) {
616*c8dee2aaSAndroid Build Coastguard Worker         curve->table_entries = 0;
617*c8dee2aaSAndroid Build Coastguard Worker         curve->parametric.a  = 1.0f;
618*c8dee2aaSAndroid Build Coastguard Worker         curve->parametric.b  = 0.0f;
619*c8dee2aaSAndroid Build Coastguard Worker         curve->parametric.c  = 0.0f;
620*c8dee2aaSAndroid Build Coastguard Worker         curve->parametric.d  = 0.0f;
621*c8dee2aaSAndroid Build Coastguard Worker         curve->parametric.e  = 0.0f;
622*c8dee2aaSAndroid Build Coastguard Worker         curve->parametric.f  = 0.0f;
623*c8dee2aaSAndroid Build Coastguard Worker         if (value_count == 0) {
624*c8dee2aaSAndroid Build Coastguard Worker             // Empty tables are a shorthand for an identity curve
625*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.g = 1.0f;
626*c8dee2aaSAndroid Build Coastguard Worker         } else {
627*c8dee2aaSAndroid Build Coastguard Worker             // Single entry tables are a shorthand for simple gamma
628*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric.g = read_big_u16(curvTag->variable) * (1.0f / 256.0f);
629*c8dee2aaSAndroid Build Coastguard Worker         }
630*c8dee2aaSAndroid Build Coastguard Worker     } else {
631*c8dee2aaSAndroid Build Coastguard Worker         curve->table_8       = nullptr;
632*c8dee2aaSAndroid Build Coastguard Worker         curve->table_16      = curvTag->variable;
633*c8dee2aaSAndroid Build Coastguard Worker         curve->table_entries = value_count;
634*c8dee2aaSAndroid Build Coastguard Worker     }
635*c8dee2aaSAndroid Build Coastguard Worker 
636*c8dee2aaSAndroid Build Coastguard Worker     return true;
637*c8dee2aaSAndroid Build Coastguard Worker }
638*c8dee2aaSAndroid Build Coastguard Worker 
639*c8dee2aaSAndroid Build Coastguard Worker // Parses both curveType and parametricCurveType data. Ensures that at most 'size' bytes are read.
640*c8dee2aaSAndroid Build Coastguard Worker // If curve_size is not nullptr, writes the number of bytes used by the curve in (*curve_size).
read_curve(const uint8_t * buf,uint32_t size,skcms_Curve * curve,uint32_t * curve_size)641*c8dee2aaSAndroid Build Coastguard Worker static bool read_curve(const uint8_t* buf, uint32_t size,
642*c8dee2aaSAndroid Build Coastguard Worker                        skcms_Curve* curve, uint32_t* curve_size) {
643*c8dee2aaSAndroid Build Coastguard Worker     if (!buf || size < 4 || !curve) {
644*c8dee2aaSAndroid Build Coastguard Worker         return false;
645*c8dee2aaSAndroid Build Coastguard Worker     }
646*c8dee2aaSAndroid Build Coastguard Worker 
647*c8dee2aaSAndroid Build Coastguard Worker     uint32_t type = read_big_u32(buf);
648*c8dee2aaSAndroid Build Coastguard Worker     if (type == skcms_Signature_para) {
649*c8dee2aaSAndroid Build Coastguard Worker         return read_curve_para(buf, size, curve, curve_size);
650*c8dee2aaSAndroid Build Coastguard Worker     } else if (type == skcms_Signature_curv) {
651*c8dee2aaSAndroid Build Coastguard Worker         return read_curve_curv(buf, size, curve, curve_size);
652*c8dee2aaSAndroid Build Coastguard Worker     }
653*c8dee2aaSAndroid Build Coastguard Worker 
654*c8dee2aaSAndroid Build Coastguard Worker     return false;
655*c8dee2aaSAndroid Build Coastguard Worker }
656*c8dee2aaSAndroid Build Coastguard Worker 
657*c8dee2aaSAndroid Build Coastguard Worker // mft1 and mft2 share a large chunk of data
658*c8dee2aaSAndroid Build Coastguard Worker typedef struct {
659*c8dee2aaSAndroid Build Coastguard Worker     uint8_t type                 [ 4];
660*c8dee2aaSAndroid Build Coastguard Worker     uint8_t reserved_a           [ 4];
661*c8dee2aaSAndroid Build Coastguard Worker     uint8_t input_channels       [ 1];
662*c8dee2aaSAndroid Build Coastguard Worker     uint8_t output_channels      [ 1];
663*c8dee2aaSAndroid Build Coastguard Worker     uint8_t grid_points          [ 1];
664*c8dee2aaSAndroid Build Coastguard Worker     uint8_t reserved_b           [ 1];
665*c8dee2aaSAndroid Build Coastguard Worker     uint8_t matrix               [36];
666*c8dee2aaSAndroid Build Coastguard Worker } mft_CommonLayout;
667*c8dee2aaSAndroid Build Coastguard Worker 
668*c8dee2aaSAndroid Build Coastguard Worker typedef struct {
669*c8dee2aaSAndroid Build Coastguard Worker     mft_CommonLayout common      [1];
670*c8dee2aaSAndroid Build Coastguard Worker 
671*c8dee2aaSAndroid Build Coastguard Worker     uint8_t variable             [1/*variable*/];
672*c8dee2aaSAndroid Build Coastguard Worker } mft1_Layout;
673*c8dee2aaSAndroid Build Coastguard Worker 
674*c8dee2aaSAndroid Build Coastguard Worker typedef struct {
675*c8dee2aaSAndroid Build Coastguard Worker     mft_CommonLayout common      [1];
676*c8dee2aaSAndroid Build Coastguard Worker 
677*c8dee2aaSAndroid Build Coastguard Worker     uint8_t input_table_entries  [2];
678*c8dee2aaSAndroid Build Coastguard Worker     uint8_t output_table_entries [2];
679*c8dee2aaSAndroid Build Coastguard Worker     uint8_t variable             [1/*variable*/];
680*c8dee2aaSAndroid Build Coastguard Worker } mft2_Layout;
681*c8dee2aaSAndroid Build Coastguard Worker 
read_mft_common(const mft_CommonLayout * mftTag,skcms_A2B * a2b)682*c8dee2aaSAndroid Build Coastguard Worker static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_A2B* a2b) {
683*c8dee2aaSAndroid Build Coastguard Worker     // MFT matrices are applied before the first set of curves, but must be identity unless the
684*c8dee2aaSAndroid Build Coastguard Worker     // input is PCSXYZ. We don't support PCSXYZ profiles, so we ignore this matrix. Note that the
685*c8dee2aaSAndroid Build Coastguard Worker     // matrix in skcms_A2B is applied later in the pipe, so supporting this would require another
686*c8dee2aaSAndroid Build Coastguard Worker     // field/flag.
687*c8dee2aaSAndroid Build Coastguard Worker     a2b->matrix_channels = 0;
688*c8dee2aaSAndroid Build Coastguard Worker     a2b-> input_channels = mftTag-> input_channels[0];
689*c8dee2aaSAndroid Build Coastguard Worker     a2b->output_channels = mftTag->output_channels[0];
690*c8dee2aaSAndroid Build Coastguard Worker 
691*c8dee2aaSAndroid Build Coastguard Worker     // We require exactly three (ie XYZ/Lab/RGB) output channels
692*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
693*c8dee2aaSAndroid Build Coastguard Worker         return false;
694*c8dee2aaSAndroid Build Coastguard Worker     }
695*c8dee2aaSAndroid Build Coastguard Worker     // We require at least one, and no more than four (ie CMYK) input channels
696*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
697*c8dee2aaSAndroid Build Coastguard Worker         return false;
698*c8dee2aaSAndroid Build Coastguard Worker     }
699*c8dee2aaSAndroid Build Coastguard Worker 
700*c8dee2aaSAndroid Build Coastguard Worker     for (uint32_t i = 0; i < a2b->input_channels; ++i) {
701*c8dee2aaSAndroid Build Coastguard Worker         a2b->grid_points[i] = mftTag->grid_points[0];
702*c8dee2aaSAndroid Build Coastguard Worker     }
703*c8dee2aaSAndroid Build Coastguard Worker     // The grid only makes sense with at least two points along each axis
704*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->grid_points[0] < 2) {
705*c8dee2aaSAndroid Build Coastguard Worker         return false;
706*c8dee2aaSAndroid Build Coastguard Worker     }
707*c8dee2aaSAndroid Build Coastguard Worker     return true;
708*c8dee2aaSAndroid Build Coastguard Worker }
709*c8dee2aaSAndroid Build Coastguard Worker 
710*c8dee2aaSAndroid Build Coastguard Worker // All as the A2B version above, except where noted.
read_mft_common(const mft_CommonLayout * mftTag,skcms_B2A * b2a)711*c8dee2aaSAndroid Build Coastguard Worker static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_B2A* b2a) {
712*c8dee2aaSAndroid Build Coastguard Worker     // Same as A2B.
713*c8dee2aaSAndroid Build Coastguard Worker     b2a->matrix_channels = 0;
714*c8dee2aaSAndroid Build Coastguard Worker     b2a-> input_channels = mftTag-> input_channels[0];
715*c8dee2aaSAndroid Build Coastguard Worker     b2a->output_channels = mftTag->output_channels[0];
716*c8dee2aaSAndroid Build Coastguard Worker 
717*c8dee2aaSAndroid Build Coastguard Worker 
718*c8dee2aaSAndroid Build Coastguard Worker     // For B2A, exactly 3 input channels (XYZ) and 3 (RGB) or 4 (CMYK) output channels.
719*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->input_channels != ARRAY_COUNT(b2a->input_curves)) {
720*c8dee2aaSAndroid Build Coastguard Worker         return false;
721*c8dee2aaSAndroid Build Coastguard Worker     }
722*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->output_channels < 3 || b2a->output_channels > ARRAY_COUNT(b2a->output_curves)) {
723*c8dee2aaSAndroid Build Coastguard Worker         return false;
724*c8dee2aaSAndroid Build Coastguard Worker     }
725*c8dee2aaSAndroid Build Coastguard Worker 
726*c8dee2aaSAndroid Build Coastguard Worker     // Same as A2B.
727*c8dee2aaSAndroid Build Coastguard Worker     for (uint32_t i = 0; i < b2a->input_channels; ++i) {
728*c8dee2aaSAndroid Build Coastguard Worker         b2a->grid_points[i] = mftTag->grid_points[0];
729*c8dee2aaSAndroid Build Coastguard Worker     }
730*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->grid_points[0] < 2) {
731*c8dee2aaSAndroid Build Coastguard Worker         return false;
732*c8dee2aaSAndroid Build Coastguard Worker     }
733*c8dee2aaSAndroid Build Coastguard Worker     return true;
734*c8dee2aaSAndroid Build Coastguard Worker }
735*c8dee2aaSAndroid Build Coastguard Worker 
736*c8dee2aaSAndroid Build Coastguard Worker template <typename A2B_or_B2A>
init_tables(const uint8_t * table_base,uint64_t max_tables_len,uint32_t byte_width,uint32_t input_table_entries,uint32_t output_table_entries,A2B_or_B2A * out)737*c8dee2aaSAndroid Build Coastguard Worker static bool init_tables(const uint8_t* table_base, uint64_t max_tables_len, uint32_t byte_width,
738*c8dee2aaSAndroid Build Coastguard Worker                         uint32_t input_table_entries, uint32_t output_table_entries,
739*c8dee2aaSAndroid Build Coastguard Worker                         A2B_or_B2A* out) {
740*c8dee2aaSAndroid Build Coastguard Worker     // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
741*c8dee2aaSAndroid Build Coastguard Worker     uint32_t byte_len_per_input_table  = input_table_entries * byte_width;
742*c8dee2aaSAndroid Build Coastguard Worker     uint32_t byte_len_per_output_table = output_table_entries * byte_width;
743*c8dee2aaSAndroid Build Coastguard Worker 
744*c8dee2aaSAndroid Build Coastguard Worker     // [input|output]_channels are <= 4, so still no overflow
745*c8dee2aaSAndroid Build Coastguard Worker     uint32_t byte_len_all_input_tables  = out->input_channels * byte_len_per_input_table;
746*c8dee2aaSAndroid Build Coastguard Worker     uint32_t byte_len_all_output_tables = out->output_channels * byte_len_per_output_table;
747*c8dee2aaSAndroid Build Coastguard Worker 
748*c8dee2aaSAndroid Build Coastguard Worker     uint64_t grid_size = out->output_channels * byte_width;
749*c8dee2aaSAndroid Build Coastguard Worker     for (uint32_t axis = 0; axis < out->input_channels; ++axis) {
750*c8dee2aaSAndroid Build Coastguard Worker         grid_size *= out->grid_points[axis];
751*c8dee2aaSAndroid Build Coastguard Worker     }
752*c8dee2aaSAndroid Build Coastguard Worker 
753*c8dee2aaSAndroid Build Coastguard Worker     if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
754*c8dee2aaSAndroid Build Coastguard Worker         return false;
755*c8dee2aaSAndroid Build Coastguard Worker     }
756*c8dee2aaSAndroid Build Coastguard Worker 
757*c8dee2aaSAndroid Build Coastguard Worker     for (uint32_t i = 0; i < out->input_channels; ++i) {
758*c8dee2aaSAndroid Build Coastguard Worker         out->input_curves[i].table_entries = input_table_entries;
759*c8dee2aaSAndroid Build Coastguard Worker         if (byte_width == 1) {
760*c8dee2aaSAndroid Build Coastguard Worker             out->input_curves[i].table_8  = table_base + i * byte_len_per_input_table;
761*c8dee2aaSAndroid Build Coastguard Worker             out->input_curves[i].table_16 = nullptr;
762*c8dee2aaSAndroid Build Coastguard Worker         } else {
763*c8dee2aaSAndroid Build Coastguard Worker             out->input_curves[i].table_8  = nullptr;
764*c8dee2aaSAndroid Build Coastguard Worker             out->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
765*c8dee2aaSAndroid Build Coastguard Worker         }
766*c8dee2aaSAndroid Build Coastguard Worker     }
767*c8dee2aaSAndroid Build Coastguard Worker 
768*c8dee2aaSAndroid Build Coastguard Worker     if (byte_width == 1) {
769*c8dee2aaSAndroid Build Coastguard Worker         out->grid_8  = table_base + byte_len_all_input_tables;
770*c8dee2aaSAndroid Build Coastguard Worker         out->grid_16 = nullptr;
771*c8dee2aaSAndroid Build Coastguard Worker     } else {
772*c8dee2aaSAndroid Build Coastguard Worker         out->grid_8  = nullptr;
773*c8dee2aaSAndroid Build Coastguard Worker         out->grid_16 = table_base + byte_len_all_input_tables;
774*c8dee2aaSAndroid Build Coastguard Worker     }
775*c8dee2aaSAndroid Build Coastguard Worker 
776*c8dee2aaSAndroid Build Coastguard Worker     const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
777*c8dee2aaSAndroid Build Coastguard Worker     for (uint32_t i = 0; i < out->output_channels; ++i) {
778*c8dee2aaSAndroid Build Coastguard Worker         out->output_curves[i].table_entries = output_table_entries;
779*c8dee2aaSAndroid Build Coastguard Worker         if (byte_width == 1) {
780*c8dee2aaSAndroid Build Coastguard Worker             out->output_curves[i].table_8  = output_table_base + i * byte_len_per_output_table;
781*c8dee2aaSAndroid Build Coastguard Worker             out->output_curves[i].table_16 = nullptr;
782*c8dee2aaSAndroid Build Coastguard Worker         } else {
783*c8dee2aaSAndroid Build Coastguard Worker             out->output_curves[i].table_8  = nullptr;
784*c8dee2aaSAndroid Build Coastguard Worker             out->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
785*c8dee2aaSAndroid Build Coastguard Worker         }
786*c8dee2aaSAndroid Build Coastguard Worker     }
787*c8dee2aaSAndroid Build Coastguard Worker 
788*c8dee2aaSAndroid Build Coastguard Worker     return true;
789*c8dee2aaSAndroid Build Coastguard Worker }
790*c8dee2aaSAndroid Build Coastguard Worker 
791*c8dee2aaSAndroid Build Coastguard Worker template <typename A2B_or_B2A>
read_tag_mft1(const skcms_ICCTag * tag,A2B_or_B2A * out)792*c8dee2aaSAndroid Build Coastguard Worker static bool read_tag_mft1(const skcms_ICCTag* tag, A2B_or_B2A* out) {
793*c8dee2aaSAndroid Build Coastguard Worker     if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
794*c8dee2aaSAndroid Build Coastguard Worker         return false;
795*c8dee2aaSAndroid Build Coastguard Worker     }
796*c8dee2aaSAndroid Build Coastguard Worker 
797*c8dee2aaSAndroid Build Coastguard Worker     const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
798*c8dee2aaSAndroid Build Coastguard Worker     if (!read_mft_common(mftTag->common, out)) {
799*c8dee2aaSAndroid Build Coastguard Worker         return false;
800*c8dee2aaSAndroid Build Coastguard Worker     }
801*c8dee2aaSAndroid Build Coastguard Worker 
802*c8dee2aaSAndroid Build Coastguard Worker     uint32_t input_table_entries  = 256;
803*c8dee2aaSAndroid Build Coastguard Worker     uint32_t output_table_entries = 256;
804*c8dee2aaSAndroid Build Coastguard Worker 
805*c8dee2aaSAndroid Build Coastguard Worker     return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft1_Layout), 1,
806*c8dee2aaSAndroid Build Coastguard Worker                        input_table_entries, output_table_entries, out);
807*c8dee2aaSAndroid Build Coastguard Worker }
808*c8dee2aaSAndroid Build Coastguard Worker 
809*c8dee2aaSAndroid Build Coastguard Worker template <typename A2B_or_B2A>
read_tag_mft2(const skcms_ICCTag * tag,A2B_or_B2A * out)810*c8dee2aaSAndroid Build Coastguard Worker static bool read_tag_mft2(const skcms_ICCTag* tag, A2B_or_B2A* out) {
811*c8dee2aaSAndroid Build Coastguard Worker     if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
812*c8dee2aaSAndroid Build Coastguard Worker         return false;
813*c8dee2aaSAndroid Build Coastguard Worker     }
814*c8dee2aaSAndroid Build Coastguard Worker 
815*c8dee2aaSAndroid Build Coastguard Worker     const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
816*c8dee2aaSAndroid Build Coastguard Worker     if (!read_mft_common(mftTag->common, out)) {
817*c8dee2aaSAndroid Build Coastguard Worker         return false;
818*c8dee2aaSAndroid Build Coastguard Worker     }
819*c8dee2aaSAndroid Build Coastguard Worker 
820*c8dee2aaSAndroid Build Coastguard Worker     uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
821*c8dee2aaSAndroid Build Coastguard Worker     uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
822*c8dee2aaSAndroid Build Coastguard Worker 
823*c8dee2aaSAndroid Build Coastguard Worker     // ICC spec mandates that 2 <= table_entries <= 4096
824*c8dee2aaSAndroid Build Coastguard Worker     if (input_table_entries < 2 || input_table_entries > 4096 ||
825*c8dee2aaSAndroid Build Coastguard Worker         output_table_entries < 2 || output_table_entries > 4096) {
826*c8dee2aaSAndroid Build Coastguard Worker         return false;
827*c8dee2aaSAndroid Build Coastguard Worker     }
828*c8dee2aaSAndroid Build Coastguard Worker 
829*c8dee2aaSAndroid Build Coastguard Worker     return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft2_Layout), 2,
830*c8dee2aaSAndroid Build Coastguard Worker                        input_table_entries, output_table_entries, out);
831*c8dee2aaSAndroid Build Coastguard Worker }
832*c8dee2aaSAndroid Build Coastguard Worker 
read_curves(const uint8_t * buf,uint32_t size,uint32_t curve_offset,uint32_t num_curves,skcms_Curve * curves)833*c8dee2aaSAndroid Build Coastguard Worker static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
834*c8dee2aaSAndroid Build Coastguard Worker                         uint32_t num_curves, skcms_Curve* curves) {
835*c8dee2aaSAndroid Build Coastguard Worker     for (uint32_t i = 0; i < num_curves; ++i) {
836*c8dee2aaSAndroid Build Coastguard Worker         if (curve_offset > size) {
837*c8dee2aaSAndroid Build Coastguard Worker             return false;
838*c8dee2aaSAndroid Build Coastguard Worker         }
839*c8dee2aaSAndroid Build Coastguard Worker 
840*c8dee2aaSAndroid Build Coastguard Worker         uint32_t curve_bytes;
841*c8dee2aaSAndroid Build Coastguard Worker         if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
842*c8dee2aaSAndroid Build Coastguard Worker             return false;
843*c8dee2aaSAndroid Build Coastguard Worker         }
844*c8dee2aaSAndroid Build Coastguard Worker 
845*c8dee2aaSAndroid Build Coastguard Worker         if (curve_bytes > UINT32_MAX - 3) {
846*c8dee2aaSAndroid Build Coastguard Worker             return false;
847*c8dee2aaSAndroid Build Coastguard Worker         }
848*c8dee2aaSAndroid Build Coastguard Worker         curve_bytes = (curve_bytes + 3) & ~3U;
849*c8dee2aaSAndroid Build Coastguard Worker 
850*c8dee2aaSAndroid Build Coastguard Worker         uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
851*c8dee2aaSAndroid Build Coastguard Worker         curve_offset = (uint32_t)new_offset_64;
852*c8dee2aaSAndroid Build Coastguard Worker         if (new_offset_64 != curve_offset) {
853*c8dee2aaSAndroid Build Coastguard Worker             return false;
854*c8dee2aaSAndroid Build Coastguard Worker         }
855*c8dee2aaSAndroid Build Coastguard Worker     }
856*c8dee2aaSAndroid Build Coastguard Worker 
857*c8dee2aaSAndroid Build Coastguard Worker     return true;
858*c8dee2aaSAndroid Build Coastguard Worker }
859*c8dee2aaSAndroid Build Coastguard Worker 
860*c8dee2aaSAndroid Build Coastguard Worker // mAB and mBA tags use the same encoding, including color lookup tables.
861*c8dee2aaSAndroid Build Coastguard Worker typedef struct {
862*c8dee2aaSAndroid Build Coastguard Worker     uint8_t type                 [ 4];
863*c8dee2aaSAndroid Build Coastguard Worker     uint8_t reserved_a           [ 4];
864*c8dee2aaSAndroid Build Coastguard Worker     uint8_t input_channels       [ 1];
865*c8dee2aaSAndroid Build Coastguard Worker     uint8_t output_channels      [ 1];
866*c8dee2aaSAndroid Build Coastguard Worker     uint8_t reserved_b           [ 2];
867*c8dee2aaSAndroid Build Coastguard Worker     uint8_t b_curve_offset       [ 4];
868*c8dee2aaSAndroid Build Coastguard Worker     uint8_t matrix_offset        [ 4];
869*c8dee2aaSAndroid Build Coastguard Worker     uint8_t m_curve_offset       [ 4];
870*c8dee2aaSAndroid Build Coastguard Worker     uint8_t clut_offset          [ 4];
871*c8dee2aaSAndroid Build Coastguard Worker     uint8_t a_curve_offset       [ 4];
872*c8dee2aaSAndroid Build Coastguard Worker } mAB_or_mBA_Layout;
873*c8dee2aaSAndroid Build Coastguard Worker 
874*c8dee2aaSAndroid Build Coastguard Worker typedef struct {
875*c8dee2aaSAndroid Build Coastguard Worker     uint8_t grid_points          [16];
876*c8dee2aaSAndroid Build Coastguard Worker     uint8_t grid_byte_width      [ 1];
877*c8dee2aaSAndroid Build Coastguard Worker     uint8_t reserved             [ 3];
878*c8dee2aaSAndroid Build Coastguard Worker     uint8_t variable             [1/*variable*/];
879*c8dee2aaSAndroid Build Coastguard Worker } CLUT_Layout;
880*c8dee2aaSAndroid Build Coastguard Worker 
read_tag_mab(const skcms_ICCTag * tag,skcms_A2B * a2b,bool pcs_is_xyz)881*c8dee2aaSAndroid Build Coastguard Worker static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
882*c8dee2aaSAndroid Build Coastguard Worker     if (tag->size < SAFE_SIZEOF(mAB_or_mBA_Layout)) {
883*c8dee2aaSAndroid Build Coastguard Worker         return false;
884*c8dee2aaSAndroid Build Coastguard Worker     }
885*c8dee2aaSAndroid Build Coastguard Worker 
886*c8dee2aaSAndroid Build Coastguard Worker     const mAB_or_mBA_Layout* mABTag = (const mAB_or_mBA_Layout*)tag->buf;
887*c8dee2aaSAndroid Build Coastguard Worker 
888*c8dee2aaSAndroid Build Coastguard Worker     a2b->input_channels  = mABTag->input_channels[0];
889*c8dee2aaSAndroid Build Coastguard Worker     a2b->output_channels = mABTag->output_channels[0];
890*c8dee2aaSAndroid Build Coastguard Worker 
891*c8dee2aaSAndroid Build Coastguard Worker     // We require exactly three (ie XYZ/Lab/RGB) output channels
892*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
893*c8dee2aaSAndroid Build Coastguard Worker         return false;
894*c8dee2aaSAndroid Build Coastguard Worker     }
895*c8dee2aaSAndroid Build Coastguard Worker     // We require no more than four (ie CMYK) input channels
896*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
897*c8dee2aaSAndroid Build Coastguard Worker         return false;
898*c8dee2aaSAndroid Build Coastguard Worker     }
899*c8dee2aaSAndroid Build Coastguard Worker 
900*c8dee2aaSAndroid Build Coastguard Worker     uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
901*c8dee2aaSAndroid Build Coastguard Worker     uint32_t matrix_offset  = read_big_u32(mABTag->matrix_offset);
902*c8dee2aaSAndroid Build Coastguard Worker     uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
903*c8dee2aaSAndroid Build Coastguard Worker     uint32_t clut_offset    = read_big_u32(mABTag->clut_offset);
904*c8dee2aaSAndroid Build Coastguard Worker     uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
905*c8dee2aaSAndroid Build Coastguard Worker 
906*c8dee2aaSAndroid Build Coastguard Worker     // "B" curves must be present
907*c8dee2aaSAndroid Build Coastguard Worker     if (0 == b_curve_offset) {
908*c8dee2aaSAndroid Build Coastguard Worker         return false;
909*c8dee2aaSAndroid Build Coastguard Worker     }
910*c8dee2aaSAndroid Build Coastguard Worker 
911*c8dee2aaSAndroid Build Coastguard Worker     if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
912*c8dee2aaSAndroid Build Coastguard Worker                      a2b->output_curves)) {
913*c8dee2aaSAndroid Build Coastguard Worker         return false;
914*c8dee2aaSAndroid Build Coastguard Worker     }
915*c8dee2aaSAndroid Build Coastguard Worker 
916*c8dee2aaSAndroid Build Coastguard Worker     // "M" curves and Matrix must be used together
917*c8dee2aaSAndroid Build Coastguard Worker     if (0 != m_curve_offset) {
918*c8dee2aaSAndroid Build Coastguard Worker         if (0 == matrix_offset) {
919*c8dee2aaSAndroid Build Coastguard Worker             return false;
920*c8dee2aaSAndroid Build Coastguard Worker         }
921*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix_channels = a2b->output_channels;
922*c8dee2aaSAndroid Build Coastguard Worker         if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
923*c8dee2aaSAndroid Build Coastguard Worker                          a2b->matrix_curves)) {
924*c8dee2aaSAndroid Build Coastguard Worker             return false;
925*c8dee2aaSAndroid Build Coastguard Worker         }
926*c8dee2aaSAndroid Build Coastguard Worker 
927*c8dee2aaSAndroid Build Coastguard Worker         // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
928*c8dee2aaSAndroid Build Coastguard Worker         if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
929*c8dee2aaSAndroid Build Coastguard Worker             return false;
930*c8dee2aaSAndroid Build Coastguard Worker         }
931*c8dee2aaSAndroid Build Coastguard Worker         float encoding_factor = pcs_is_xyz ? (65535 / 32768.0f) : 1.0f;
932*c8dee2aaSAndroid Build Coastguard Worker         const uint8_t* mtx_buf = tag->buf + matrix_offset;
933*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf +  0);
934*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf +  4);
935*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf +  8);
936*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
937*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
938*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
939*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
940*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
941*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
942*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
943*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
944*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
945*c8dee2aaSAndroid Build Coastguard Worker     } else {
946*c8dee2aaSAndroid Build Coastguard Worker         if (0 != matrix_offset) {
947*c8dee2aaSAndroid Build Coastguard Worker             return false;
948*c8dee2aaSAndroid Build Coastguard Worker         }
949*c8dee2aaSAndroid Build Coastguard Worker         a2b->matrix_channels = 0;
950*c8dee2aaSAndroid Build Coastguard Worker     }
951*c8dee2aaSAndroid Build Coastguard Worker 
952*c8dee2aaSAndroid Build Coastguard Worker     // "A" curves and CLUT must be used together
953*c8dee2aaSAndroid Build Coastguard Worker     if (0 != a_curve_offset) {
954*c8dee2aaSAndroid Build Coastguard Worker         if (0 == clut_offset) {
955*c8dee2aaSAndroid Build Coastguard Worker             return false;
956*c8dee2aaSAndroid Build Coastguard Worker         }
957*c8dee2aaSAndroid Build Coastguard Worker         if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
958*c8dee2aaSAndroid Build Coastguard Worker                          a2b->input_curves)) {
959*c8dee2aaSAndroid Build Coastguard Worker             return false;
960*c8dee2aaSAndroid Build Coastguard Worker         }
961*c8dee2aaSAndroid Build Coastguard Worker 
962*c8dee2aaSAndroid Build Coastguard Worker         if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout)) {
963*c8dee2aaSAndroid Build Coastguard Worker             return false;
964*c8dee2aaSAndroid Build Coastguard Worker         }
965*c8dee2aaSAndroid Build Coastguard Worker         const CLUT_Layout* clut = (const CLUT_Layout*)(tag->buf + clut_offset);
966*c8dee2aaSAndroid Build Coastguard Worker 
967*c8dee2aaSAndroid Build Coastguard Worker         if (clut->grid_byte_width[0] == 1) {
968*c8dee2aaSAndroid Build Coastguard Worker             a2b->grid_8  = clut->variable;
969*c8dee2aaSAndroid Build Coastguard Worker             a2b->grid_16 = nullptr;
970*c8dee2aaSAndroid Build Coastguard Worker         } else if (clut->grid_byte_width[0] == 2) {
971*c8dee2aaSAndroid Build Coastguard Worker             a2b->grid_8  = nullptr;
972*c8dee2aaSAndroid Build Coastguard Worker             a2b->grid_16 = clut->variable;
973*c8dee2aaSAndroid Build Coastguard Worker         } else {
974*c8dee2aaSAndroid Build Coastguard Worker             return false;
975*c8dee2aaSAndroid Build Coastguard Worker         }
976*c8dee2aaSAndroid Build Coastguard Worker 
977*c8dee2aaSAndroid Build Coastguard Worker         uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];  // the payload
978*c8dee2aaSAndroid Build Coastguard Worker         for (uint32_t i = 0; i < a2b->input_channels; ++i) {
979*c8dee2aaSAndroid Build Coastguard Worker             a2b->grid_points[i] = clut->grid_points[i];
980*c8dee2aaSAndroid Build Coastguard Worker             // The grid only makes sense with at least two points along each axis
981*c8dee2aaSAndroid Build Coastguard Worker             if (a2b->grid_points[i] < 2) {
982*c8dee2aaSAndroid Build Coastguard Worker                 return false;
983*c8dee2aaSAndroid Build Coastguard Worker             }
984*c8dee2aaSAndroid Build Coastguard Worker             grid_size *= a2b->grid_points[i];
985*c8dee2aaSAndroid Build Coastguard Worker         }
986*c8dee2aaSAndroid Build Coastguard Worker         if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout) + grid_size) {
987*c8dee2aaSAndroid Build Coastguard Worker             return false;
988*c8dee2aaSAndroid Build Coastguard Worker         }
989*c8dee2aaSAndroid Build Coastguard Worker     } else {
990*c8dee2aaSAndroid Build Coastguard Worker         if (0 != clut_offset) {
991*c8dee2aaSAndroid Build Coastguard Worker             return false;
992*c8dee2aaSAndroid Build Coastguard Worker         }
993*c8dee2aaSAndroid Build Coastguard Worker 
994*c8dee2aaSAndroid Build Coastguard Worker         // If there is no CLUT, the number of input and output channels must match
995*c8dee2aaSAndroid Build Coastguard Worker         if (a2b->input_channels != a2b->output_channels) {
996*c8dee2aaSAndroid Build Coastguard Worker             return false;
997*c8dee2aaSAndroid Build Coastguard Worker         }
998*c8dee2aaSAndroid Build Coastguard Worker 
999*c8dee2aaSAndroid Build Coastguard Worker         // Zero out the number of input channels to signal that we're skipping this stage
1000*c8dee2aaSAndroid Build Coastguard Worker         a2b->input_channels = 0;
1001*c8dee2aaSAndroid Build Coastguard Worker     }
1002*c8dee2aaSAndroid Build Coastguard Worker 
1003*c8dee2aaSAndroid Build Coastguard Worker     return true;
1004*c8dee2aaSAndroid Build Coastguard Worker }
1005*c8dee2aaSAndroid Build Coastguard Worker 
1006*c8dee2aaSAndroid Build Coastguard Worker // Exactly the same as read_tag_mab(), except where there are comments.
1007*c8dee2aaSAndroid Build Coastguard Worker // TODO: refactor the two to eliminate common code?
read_tag_mba(const skcms_ICCTag * tag,skcms_B2A * b2a,bool pcs_is_xyz)1008*c8dee2aaSAndroid Build Coastguard Worker static bool read_tag_mba(const skcms_ICCTag* tag, skcms_B2A* b2a, bool pcs_is_xyz) {
1009*c8dee2aaSAndroid Build Coastguard Worker     if (tag->size < SAFE_SIZEOF(mAB_or_mBA_Layout)) {
1010*c8dee2aaSAndroid Build Coastguard Worker         return false;
1011*c8dee2aaSAndroid Build Coastguard Worker     }
1012*c8dee2aaSAndroid Build Coastguard Worker 
1013*c8dee2aaSAndroid Build Coastguard Worker     const mAB_or_mBA_Layout* mBATag = (const mAB_or_mBA_Layout*)tag->buf;
1014*c8dee2aaSAndroid Build Coastguard Worker 
1015*c8dee2aaSAndroid Build Coastguard Worker     b2a->input_channels  = mBATag->input_channels[0];
1016*c8dee2aaSAndroid Build Coastguard Worker     b2a->output_channels = mBATag->output_channels[0];
1017*c8dee2aaSAndroid Build Coastguard Worker 
1018*c8dee2aaSAndroid Build Coastguard Worker     // Require exactly 3 inputs (XYZ) and 3 (RGB) or 4 (CMYK) outputs.
1019*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->input_channels != ARRAY_COUNT(b2a->input_curves)) {
1020*c8dee2aaSAndroid Build Coastguard Worker         return false;
1021*c8dee2aaSAndroid Build Coastguard Worker     }
1022*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->output_channels < 3 || b2a->output_channels > ARRAY_COUNT(b2a->output_curves)) {
1023*c8dee2aaSAndroid Build Coastguard Worker         return false;
1024*c8dee2aaSAndroid Build Coastguard Worker     }
1025*c8dee2aaSAndroid Build Coastguard Worker 
1026*c8dee2aaSAndroid Build Coastguard Worker     uint32_t b_curve_offset = read_big_u32(mBATag->b_curve_offset);
1027*c8dee2aaSAndroid Build Coastguard Worker     uint32_t matrix_offset  = read_big_u32(mBATag->matrix_offset);
1028*c8dee2aaSAndroid Build Coastguard Worker     uint32_t m_curve_offset = read_big_u32(mBATag->m_curve_offset);
1029*c8dee2aaSAndroid Build Coastguard Worker     uint32_t clut_offset    = read_big_u32(mBATag->clut_offset);
1030*c8dee2aaSAndroid Build Coastguard Worker     uint32_t a_curve_offset = read_big_u32(mBATag->a_curve_offset);
1031*c8dee2aaSAndroid Build Coastguard Worker 
1032*c8dee2aaSAndroid Build Coastguard Worker     if (0 == b_curve_offset) {
1033*c8dee2aaSAndroid Build Coastguard Worker         return false;
1034*c8dee2aaSAndroid Build Coastguard Worker     }
1035*c8dee2aaSAndroid Build Coastguard Worker 
1036*c8dee2aaSAndroid Build Coastguard Worker     // "B" curves are our inputs, not outputs.
1037*c8dee2aaSAndroid Build Coastguard Worker     if (!read_curves(tag->buf, tag->size, b_curve_offset, b2a->input_channels,
1038*c8dee2aaSAndroid Build Coastguard Worker                      b2a->input_curves)) {
1039*c8dee2aaSAndroid Build Coastguard Worker         return false;
1040*c8dee2aaSAndroid Build Coastguard Worker     }
1041*c8dee2aaSAndroid Build Coastguard Worker 
1042*c8dee2aaSAndroid Build Coastguard Worker     if (0 != m_curve_offset) {
1043*c8dee2aaSAndroid Build Coastguard Worker         if (0 == matrix_offset) {
1044*c8dee2aaSAndroid Build Coastguard Worker             return false;
1045*c8dee2aaSAndroid Build Coastguard Worker         }
1046*c8dee2aaSAndroid Build Coastguard Worker         // Matrix channels is tied to input_channels (3), not output_channels.
1047*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix_channels = b2a->input_channels;
1048*c8dee2aaSAndroid Build Coastguard Worker 
1049*c8dee2aaSAndroid Build Coastguard Worker         if (!read_curves(tag->buf, tag->size, m_curve_offset, b2a->matrix_channels,
1050*c8dee2aaSAndroid Build Coastguard Worker                          b2a->matrix_curves)) {
1051*c8dee2aaSAndroid Build Coastguard Worker             return false;
1052*c8dee2aaSAndroid Build Coastguard Worker         }
1053*c8dee2aaSAndroid Build Coastguard Worker 
1054*c8dee2aaSAndroid Build Coastguard Worker         if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
1055*c8dee2aaSAndroid Build Coastguard Worker             return false;
1056*c8dee2aaSAndroid Build Coastguard Worker         }
1057*c8dee2aaSAndroid Build Coastguard Worker         float encoding_factor = pcs_is_xyz ? (32768 / 65535.0f) : 1.0f;  // TODO: understand
1058*c8dee2aaSAndroid Build Coastguard Worker         const uint8_t* mtx_buf = tag->buf + matrix_offset;
1059*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf +  0);
1060*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf +  4);
1061*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf +  8);
1062*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
1063*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
1064*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
1065*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
1066*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
1067*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
1068*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
1069*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
1070*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
1071*c8dee2aaSAndroid Build Coastguard Worker     } else {
1072*c8dee2aaSAndroid Build Coastguard Worker         if (0 != matrix_offset) {
1073*c8dee2aaSAndroid Build Coastguard Worker             return false;
1074*c8dee2aaSAndroid Build Coastguard Worker         }
1075*c8dee2aaSAndroid Build Coastguard Worker         b2a->matrix_channels = 0;
1076*c8dee2aaSAndroid Build Coastguard Worker     }
1077*c8dee2aaSAndroid Build Coastguard Worker 
1078*c8dee2aaSAndroid Build Coastguard Worker     if (0 != a_curve_offset) {
1079*c8dee2aaSAndroid Build Coastguard Worker         if (0 == clut_offset) {
1080*c8dee2aaSAndroid Build Coastguard Worker             return false;
1081*c8dee2aaSAndroid Build Coastguard Worker         }
1082*c8dee2aaSAndroid Build Coastguard Worker 
1083*c8dee2aaSAndroid Build Coastguard Worker         // "A" curves are our output, not input.
1084*c8dee2aaSAndroid Build Coastguard Worker         if (!read_curves(tag->buf, tag->size, a_curve_offset, b2a->output_channels,
1085*c8dee2aaSAndroid Build Coastguard Worker                          b2a->output_curves)) {
1086*c8dee2aaSAndroid Build Coastguard Worker             return false;
1087*c8dee2aaSAndroid Build Coastguard Worker         }
1088*c8dee2aaSAndroid Build Coastguard Worker 
1089*c8dee2aaSAndroid Build Coastguard Worker         if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout)) {
1090*c8dee2aaSAndroid Build Coastguard Worker             return false;
1091*c8dee2aaSAndroid Build Coastguard Worker         }
1092*c8dee2aaSAndroid Build Coastguard Worker         const CLUT_Layout* clut = (const CLUT_Layout*)(tag->buf + clut_offset);
1093*c8dee2aaSAndroid Build Coastguard Worker 
1094*c8dee2aaSAndroid Build Coastguard Worker         if (clut->grid_byte_width[0] == 1) {
1095*c8dee2aaSAndroid Build Coastguard Worker             b2a->grid_8  = clut->variable;
1096*c8dee2aaSAndroid Build Coastguard Worker             b2a->grid_16 = nullptr;
1097*c8dee2aaSAndroid Build Coastguard Worker         } else if (clut->grid_byte_width[0] == 2) {
1098*c8dee2aaSAndroid Build Coastguard Worker             b2a->grid_8  = nullptr;
1099*c8dee2aaSAndroid Build Coastguard Worker             b2a->grid_16 = clut->variable;
1100*c8dee2aaSAndroid Build Coastguard Worker         } else {
1101*c8dee2aaSAndroid Build Coastguard Worker             return false;
1102*c8dee2aaSAndroid Build Coastguard Worker         }
1103*c8dee2aaSAndroid Build Coastguard Worker 
1104*c8dee2aaSAndroid Build Coastguard Worker         uint64_t grid_size = b2a->output_channels * clut->grid_byte_width[0];
1105*c8dee2aaSAndroid Build Coastguard Worker         for (uint32_t i = 0; i < b2a->input_channels; ++i) {
1106*c8dee2aaSAndroid Build Coastguard Worker             b2a->grid_points[i] = clut->grid_points[i];
1107*c8dee2aaSAndroid Build Coastguard Worker             if (b2a->grid_points[i] < 2) {
1108*c8dee2aaSAndroid Build Coastguard Worker                 return false;
1109*c8dee2aaSAndroid Build Coastguard Worker             }
1110*c8dee2aaSAndroid Build Coastguard Worker             grid_size *= b2a->grid_points[i];
1111*c8dee2aaSAndroid Build Coastguard Worker         }
1112*c8dee2aaSAndroid Build Coastguard Worker         if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout) + grid_size) {
1113*c8dee2aaSAndroid Build Coastguard Worker             return false;
1114*c8dee2aaSAndroid Build Coastguard Worker         }
1115*c8dee2aaSAndroid Build Coastguard Worker     } else {
1116*c8dee2aaSAndroid Build Coastguard Worker         if (0 != clut_offset) {
1117*c8dee2aaSAndroid Build Coastguard Worker             return false;
1118*c8dee2aaSAndroid Build Coastguard Worker         }
1119*c8dee2aaSAndroid Build Coastguard Worker 
1120*c8dee2aaSAndroid Build Coastguard Worker         if (b2a->input_channels != b2a->output_channels) {
1121*c8dee2aaSAndroid Build Coastguard Worker             return false;
1122*c8dee2aaSAndroid Build Coastguard Worker         }
1123*c8dee2aaSAndroid Build Coastguard Worker 
1124*c8dee2aaSAndroid Build Coastguard Worker         // Zero out *output* channels to skip this stage.
1125*c8dee2aaSAndroid Build Coastguard Worker         b2a->output_channels = 0;
1126*c8dee2aaSAndroid Build Coastguard Worker     }
1127*c8dee2aaSAndroid Build Coastguard Worker     return true;
1128*c8dee2aaSAndroid Build Coastguard Worker }
1129*c8dee2aaSAndroid Build Coastguard Worker 
1130*c8dee2aaSAndroid Build Coastguard Worker // If you pass f, we'll fit a possibly-non-zero value for *f.
1131*c8dee2aaSAndroid Build Coastguard Worker // If you pass nullptr, we'll assume you want *f to be treated as zero.
fit_linear(const skcms_Curve * curve,int N,float tol,float * c,float * d,float * f=nullptr)1132*c8dee2aaSAndroid Build Coastguard Worker static int fit_linear(const skcms_Curve* curve, int N, float tol,
1133*c8dee2aaSAndroid Build Coastguard Worker                       float* c, float* d, float* f = nullptr) {
1134*c8dee2aaSAndroid Build Coastguard Worker     assert(N > 1);
1135*c8dee2aaSAndroid Build Coastguard Worker     // We iteratively fit the first points to the TF's linear piece.
1136*c8dee2aaSAndroid Build Coastguard Worker     // We want the cx + f line to pass through the first and last points we fit exactly.
1137*c8dee2aaSAndroid Build Coastguard Worker     //
1138*c8dee2aaSAndroid Build Coastguard Worker     // As we walk along the points we find the minimum and maximum slope of the line before the
1139*c8dee2aaSAndroid Build Coastguard Worker     // error would exceed our tolerance.  We stop when the range [slope_min, slope_max] becomes
1140*c8dee2aaSAndroid Build Coastguard Worker     // emtpy, when we definitely can't add any more points.
1141*c8dee2aaSAndroid Build Coastguard Worker     //
1142*c8dee2aaSAndroid Build Coastguard Worker     // Some points' error intervals may intersect the running interval but not lie fully
1143*c8dee2aaSAndroid Build Coastguard Worker     // within it.  So we keep track of the last point we saw that is a valid end point candidate,
1144*c8dee2aaSAndroid Build Coastguard Worker     // and once the search is done, back up to build the line through *that* point.
1145*c8dee2aaSAndroid Build Coastguard Worker     const float dx = 1.0f / static_cast<float>(N - 1);
1146*c8dee2aaSAndroid Build Coastguard Worker 
1147*c8dee2aaSAndroid Build Coastguard Worker     int lin_points = 1;
1148*c8dee2aaSAndroid Build Coastguard Worker 
1149*c8dee2aaSAndroid Build Coastguard Worker     float f_zero = 0.0f;
1150*c8dee2aaSAndroid Build Coastguard Worker     if (f) {
1151*c8dee2aaSAndroid Build Coastguard Worker         *f = eval_curve(curve, 0);
1152*c8dee2aaSAndroid Build Coastguard Worker     } else {
1153*c8dee2aaSAndroid Build Coastguard Worker         f = &f_zero;
1154*c8dee2aaSAndroid Build Coastguard Worker     }
1155*c8dee2aaSAndroid Build Coastguard Worker 
1156*c8dee2aaSAndroid Build Coastguard Worker 
1157*c8dee2aaSAndroid Build Coastguard Worker     float slope_min = -INFINITY_;
1158*c8dee2aaSAndroid Build Coastguard Worker     float slope_max = +INFINITY_;
1159*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 1; i < N; ++i) {
1160*c8dee2aaSAndroid Build Coastguard Worker         float x = static_cast<float>(i) * dx;
1161*c8dee2aaSAndroid Build Coastguard Worker         float y = eval_curve(curve, x);
1162*c8dee2aaSAndroid Build Coastguard Worker 
1163*c8dee2aaSAndroid Build Coastguard Worker         float slope_max_i = (y + tol - *f) / x,
1164*c8dee2aaSAndroid Build Coastguard Worker               slope_min_i = (y - tol - *f) / x;
1165*c8dee2aaSAndroid Build Coastguard Worker         if (slope_max_i < slope_min || slope_max < slope_min_i) {
1166*c8dee2aaSAndroid Build Coastguard Worker             // Slope intervals would no longer overlap.
1167*c8dee2aaSAndroid Build Coastguard Worker             break;
1168*c8dee2aaSAndroid Build Coastguard Worker         }
1169*c8dee2aaSAndroid Build Coastguard Worker         slope_max = fminf_(slope_max, slope_max_i);
1170*c8dee2aaSAndroid Build Coastguard Worker         slope_min = fmaxf_(slope_min, slope_min_i);
1171*c8dee2aaSAndroid Build Coastguard Worker 
1172*c8dee2aaSAndroid Build Coastguard Worker         float cur_slope = (y - *f) / x;
1173*c8dee2aaSAndroid Build Coastguard Worker         if (slope_min <= cur_slope && cur_slope <= slope_max) {
1174*c8dee2aaSAndroid Build Coastguard Worker             lin_points = i + 1;
1175*c8dee2aaSAndroid Build Coastguard Worker             *c = cur_slope;
1176*c8dee2aaSAndroid Build Coastguard Worker         }
1177*c8dee2aaSAndroid Build Coastguard Worker     }
1178*c8dee2aaSAndroid Build Coastguard Worker 
1179*c8dee2aaSAndroid Build Coastguard Worker     // Set D to the last point that met our tolerance.
1180*c8dee2aaSAndroid Build Coastguard Worker     *d = static_cast<float>(lin_points - 1) * dx;
1181*c8dee2aaSAndroid Build Coastguard Worker     return lin_points;
1182*c8dee2aaSAndroid Build Coastguard Worker }
1183*c8dee2aaSAndroid Build Coastguard Worker 
1184*c8dee2aaSAndroid Build Coastguard Worker // If this skcms_Curve holds an identity table, rewrite it as an identity skcms_TransferFunction.
canonicalize_identity(skcms_Curve * curve)1185*c8dee2aaSAndroid Build Coastguard Worker static void canonicalize_identity(skcms_Curve* curve) {
1186*c8dee2aaSAndroid Build Coastguard Worker     if (curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
1187*c8dee2aaSAndroid Build Coastguard Worker         int N = (int)curve->table_entries;
1188*c8dee2aaSAndroid Build Coastguard Worker 
1189*c8dee2aaSAndroid Build Coastguard Worker         float c = 0.0f, d = 0.0f, f = 0.0f;
1190*c8dee2aaSAndroid Build Coastguard Worker         if (N == fit_linear(curve, N, 1.0f/static_cast<float>(2*N), &c,&d,&f)
1191*c8dee2aaSAndroid Build Coastguard Worker             && c == 1.0f
1192*c8dee2aaSAndroid Build Coastguard Worker             && f == 0.0f) {
1193*c8dee2aaSAndroid Build Coastguard Worker             curve->table_entries = 0;
1194*c8dee2aaSAndroid Build Coastguard Worker             curve->table_8       = nullptr;
1195*c8dee2aaSAndroid Build Coastguard Worker             curve->table_16      = nullptr;
1196*c8dee2aaSAndroid Build Coastguard Worker             curve->parametric    = skcms_TransferFunction{1,1,0,0,0,0,0};
1197*c8dee2aaSAndroid Build Coastguard Worker         }
1198*c8dee2aaSAndroid Build Coastguard Worker     }
1199*c8dee2aaSAndroid Build Coastguard Worker }
1200*c8dee2aaSAndroid Build Coastguard Worker 
read_a2b(const skcms_ICCTag * tag,skcms_A2B * a2b,bool pcs_is_xyz)1201*c8dee2aaSAndroid Build Coastguard Worker static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
1202*c8dee2aaSAndroid Build Coastguard Worker     bool ok = false;
1203*c8dee2aaSAndroid Build Coastguard Worker     if (tag->type == skcms_Signature_mft1) { ok = read_tag_mft1(tag, a2b); }
1204*c8dee2aaSAndroid Build Coastguard Worker     if (tag->type == skcms_Signature_mft2) { ok = read_tag_mft2(tag, a2b); }
1205*c8dee2aaSAndroid Build Coastguard Worker     if (tag->type == skcms_Signature_mAB ) { ok = read_tag_mab(tag, a2b, pcs_is_xyz); }
1206*c8dee2aaSAndroid Build Coastguard Worker     if (!ok) {
1207*c8dee2aaSAndroid Build Coastguard Worker         return false;
1208*c8dee2aaSAndroid Build Coastguard Worker     }
1209*c8dee2aaSAndroid Build Coastguard Worker 
1210*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->input_channels > 0) { canonicalize_identity(a2b->input_curves + 0); }
1211*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->input_channels > 1) { canonicalize_identity(a2b->input_curves + 1); }
1212*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->input_channels > 2) { canonicalize_identity(a2b->input_curves + 2); }
1213*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->input_channels > 3) { canonicalize_identity(a2b->input_curves + 3); }
1214*c8dee2aaSAndroid Build Coastguard Worker 
1215*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->matrix_channels > 0) { canonicalize_identity(a2b->matrix_curves + 0); }
1216*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->matrix_channels > 1) { canonicalize_identity(a2b->matrix_curves + 1); }
1217*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->matrix_channels > 2) { canonicalize_identity(a2b->matrix_curves + 2); }
1218*c8dee2aaSAndroid Build Coastguard Worker 
1219*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->output_channels > 0) { canonicalize_identity(a2b->output_curves + 0); }
1220*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->output_channels > 1) { canonicalize_identity(a2b->output_curves + 1); }
1221*c8dee2aaSAndroid Build Coastguard Worker     if (a2b->output_channels > 2) { canonicalize_identity(a2b->output_curves + 2); }
1222*c8dee2aaSAndroid Build Coastguard Worker 
1223*c8dee2aaSAndroid Build Coastguard Worker     return true;
1224*c8dee2aaSAndroid Build Coastguard Worker }
1225*c8dee2aaSAndroid Build Coastguard Worker 
read_b2a(const skcms_ICCTag * tag,skcms_B2A * b2a,bool pcs_is_xyz)1226*c8dee2aaSAndroid Build Coastguard Worker static bool read_b2a(const skcms_ICCTag* tag, skcms_B2A* b2a, bool pcs_is_xyz) {
1227*c8dee2aaSAndroid Build Coastguard Worker     bool ok = false;
1228*c8dee2aaSAndroid Build Coastguard Worker     if (tag->type == skcms_Signature_mft1) { ok = read_tag_mft1(tag, b2a); }
1229*c8dee2aaSAndroid Build Coastguard Worker     if (tag->type == skcms_Signature_mft2) { ok = read_tag_mft2(tag, b2a); }
1230*c8dee2aaSAndroid Build Coastguard Worker     if (tag->type == skcms_Signature_mBA ) { ok = read_tag_mba(tag, b2a, pcs_is_xyz); }
1231*c8dee2aaSAndroid Build Coastguard Worker     if (!ok) {
1232*c8dee2aaSAndroid Build Coastguard Worker         return false;
1233*c8dee2aaSAndroid Build Coastguard Worker     }
1234*c8dee2aaSAndroid Build Coastguard Worker 
1235*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->input_channels > 0) { canonicalize_identity(b2a->input_curves + 0); }
1236*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->input_channels > 1) { canonicalize_identity(b2a->input_curves + 1); }
1237*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->input_channels > 2) { canonicalize_identity(b2a->input_curves + 2); }
1238*c8dee2aaSAndroid Build Coastguard Worker 
1239*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->matrix_channels > 0) { canonicalize_identity(b2a->matrix_curves + 0); }
1240*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->matrix_channels > 1) { canonicalize_identity(b2a->matrix_curves + 1); }
1241*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->matrix_channels > 2) { canonicalize_identity(b2a->matrix_curves + 2); }
1242*c8dee2aaSAndroid Build Coastguard Worker 
1243*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->output_channels > 0) { canonicalize_identity(b2a->output_curves + 0); }
1244*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->output_channels > 1) { canonicalize_identity(b2a->output_curves + 1); }
1245*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->output_channels > 2) { canonicalize_identity(b2a->output_curves + 2); }
1246*c8dee2aaSAndroid Build Coastguard Worker     if (b2a->output_channels > 3) { canonicalize_identity(b2a->output_curves + 3); }
1247*c8dee2aaSAndroid Build Coastguard Worker 
1248*c8dee2aaSAndroid Build Coastguard Worker     return true;
1249*c8dee2aaSAndroid Build Coastguard Worker }
1250*c8dee2aaSAndroid Build Coastguard Worker 
1251*c8dee2aaSAndroid Build Coastguard Worker typedef struct {
1252*c8dee2aaSAndroid Build Coastguard Worker     uint8_t type                     [4];
1253*c8dee2aaSAndroid Build Coastguard Worker     uint8_t reserved                 [4];
1254*c8dee2aaSAndroid Build Coastguard Worker     uint8_t color_primaries          [1];
1255*c8dee2aaSAndroid Build Coastguard Worker     uint8_t transfer_characteristics [1];
1256*c8dee2aaSAndroid Build Coastguard Worker     uint8_t matrix_coefficients      [1];
1257*c8dee2aaSAndroid Build Coastguard Worker     uint8_t video_full_range_flag    [1];
1258*c8dee2aaSAndroid Build Coastguard Worker } CICP_Layout;
1259*c8dee2aaSAndroid Build Coastguard Worker 
read_cicp(const skcms_ICCTag * tag,skcms_CICP * cicp)1260*c8dee2aaSAndroid Build Coastguard Worker static bool read_cicp(const skcms_ICCTag* tag, skcms_CICP* cicp) {
1261*c8dee2aaSAndroid Build Coastguard Worker     if (tag->type != skcms_Signature_CICP || tag->size < SAFE_SIZEOF(CICP_Layout)) {
1262*c8dee2aaSAndroid Build Coastguard Worker         return false;
1263*c8dee2aaSAndroid Build Coastguard Worker     }
1264*c8dee2aaSAndroid Build Coastguard Worker 
1265*c8dee2aaSAndroid Build Coastguard Worker     const CICP_Layout* cicpTag = (const CICP_Layout*)tag->buf;
1266*c8dee2aaSAndroid Build Coastguard Worker 
1267*c8dee2aaSAndroid Build Coastguard Worker     cicp->color_primaries          = cicpTag->color_primaries[0];
1268*c8dee2aaSAndroid Build Coastguard Worker     cicp->transfer_characteristics = cicpTag->transfer_characteristics[0];
1269*c8dee2aaSAndroid Build Coastguard Worker     cicp->matrix_coefficients      = cicpTag->matrix_coefficients[0];
1270*c8dee2aaSAndroid Build Coastguard Worker     cicp->video_full_range_flag    = cicpTag->video_full_range_flag[0];
1271*c8dee2aaSAndroid Build Coastguard Worker     return true;
1272*c8dee2aaSAndroid Build Coastguard Worker }
1273*c8dee2aaSAndroid Build Coastguard Worker 
skcms_GetTagByIndex(const skcms_ICCProfile * profile,uint32_t idx,skcms_ICCTag * tag)1274*c8dee2aaSAndroid Build Coastguard Worker void skcms_GetTagByIndex(const skcms_ICCProfile* profile, uint32_t idx, skcms_ICCTag* tag) {
1275*c8dee2aaSAndroid Build Coastguard Worker     if (!profile || !profile->buffer || !tag) { return; }
1276*c8dee2aaSAndroid Build Coastguard Worker     if (idx > profile->tag_count) { return; }
1277*c8dee2aaSAndroid Build Coastguard Worker     const tag_Layout* tags = get_tag_table(profile);
1278*c8dee2aaSAndroid Build Coastguard Worker     tag->signature = read_big_u32(tags[idx].signature);
1279*c8dee2aaSAndroid Build Coastguard Worker     tag->size      = read_big_u32(tags[idx].size);
1280*c8dee2aaSAndroid Build Coastguard Worker     tag->buf       = read_big_u32(tags[idx].offset) + profile->buffer;
1281*c8dee2aaSAndroid Build Coastguard Worker     tag->type      = read_big_u32(tag->buf);
1282*c8dee2aaSAndroid Build Coastguard Worker }
1283*c8dee2aaSAndroid Build Coastguard Worker 
skcms_GetTagBySignature(const skcms_ICCProfile * profile,uint32_t sig,skcms_ICCTag * tag)1284*c8dee2aaSAndroid Build Coastguard Worker bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
1285*c8dee2aaSAndroid Build Coastguard Worker     if (!profile || !profile->buffer || !tag) { return false; }
1286*c8dee2aaSAndroid Build Coastguard Worker     const tag_Layout* tags = get_tag_table(profile);
1287*c8dee2aaSAndroid Build Coastguard Worker     for (uint32_t i = 0; i < profile->tag_count; ++i) {
1288*c8dee2aaSAndroid Build Coastguard Worker         if (read_big_u32(tags[i].signature) == sig) {
1289*c8dee2aaSAndroid Build Coastguard Worker             tag->signature = sig;
1290*c8dee2aaSAndroid Build Coastguard Worker             tag->size      = read_big_u32(tags[i].size);
1291*c8dee2aaSAndroid Build Coastguard Worker             tag->buf       = read_big_u32(tags[i].offset) + profile->buffer;
1292*c8dee2aaSAndroid Build Coastguard Worker             tag->type      = read_big_u32(tag->buf);
1293*c8dee2aaSAndroid Build Coastguard Worker             return true;
1294*c8dee2aaSAndroid Build Coastguard Worker         }
1295*c8dee2aaSAndroid Build Coastguard Worker     }
1296*c8dee2aaSAndroid Build Coastguard Worker     return false;
1297*c8dee2aaSAndroid Build Coastguard Worker }
1298*c8dee2aaSAndroid Build Coastguard Worker 
usable_as_src(const skcms_ICCProfile * profile)1299*c8dee2aaSAndroid Build Coastguard Worker static bool usable_as_src(const skcms_ICCProfile* profile) {
1300*c8dee2aaSAndroid Build Coastguard Worker     return profile->has_A2B
1301*c8dee2aaSAndroid Build Coastguard Worker        || (profile->has_trc && profile->has_toXYZD50);
1302*c8dee2aaSAndroid Build Coastguard Worker }
1303*c8dee2aaSAndroid Build Coastguard Worker 
skcms_ParseWithA2BPriority(const void * buf,size_t len,const int priority[],const int priorities,skcms_ICCProfile * profile)1304*c8dee2aaSAndroid Build Coastguard Worker bool skcms_ParseWithA2BPriority(const void* buf, size_t len,
1305*c8dee2aaSAndroid Build Coastguard Worker                                 const int priority[], const int priorities,
1306*c8dee2aaSAndroid Build Coastguard Worker                                 skcms_ICCProfile* profile) {
1307*c8dee2aaSAndroid Build Coastguard Worker     static_assert(SAFE_SIZEOF(header_Layout) == 132, "need to update header code");
1308*c8dee2aaSAndroid Build Coastguard Worker 
1309*c8dee2aaSAndroid Build Coastguard Worker     if (!profile) {
1310*c8dee2aaSAndroid Build Coastguard Worker         return false;
1311*c8dee2aaSAndroid Build Coastguard Worker     }
1312*c8dee2aaSAndroid Build Coastguard Worker     memset(profile, 0, SAFE_SIZEOF(*profile));
1313*c8dee2aaSAndroid Build Coastguard Worker 
1314*c8dee2aaSAndroid Build Coastguard Worker     if (len < SAFE_SIZEOF(header_Layout)) {
1315*c8dee2aaSAndroid Build Coastguard Worker         return false;
1316*c8dee2aaSAndroid Build Coastguard Worker     }
1317*c8dee2aaSAndroid Build Coastguard Worker 
1318*c8dee2aaSAndroid Build Coastguard Worker     // Byte-swap all header fields
1319*c8dee2aaSAndroid Build Coastguard Worker     const header_Layout* header  = (const header_Layout*)buf;
1320*c8dee2aaSAndroid Build Coastguard Worker     profile->buffer              = (const uint8_t*)buf;
1321*c8dee2aaSAndroid Build Coastguard Worker     profile->size                = read_big_u32(header->size);
1322*c8dee2aaSAndroid Build Coastguard Worker     uint32_t version             = read_big_u32(header->version);
1323*c8dee2aaSAndroid Build Coastguard Worker     profile->data_color_space    = read_big_u32(header->data_color_space);
1324*c8dee2aaSAndroid Build Coastguard Worker     profile->pcs                 = read_big_u32(header->pcs);
1325*c8dee2aaSAndroid Build Coastguard Worker     uint32_t signature           = read_big_u32(header->signature);
1326*c8dee2aaSAndroid Build Coastguard Worker     float illuminant_X           = read_big_fixed(header->illuminant_X);
1327*c8dee2aaSAndroid Build Coastguard Worker     float illuminant_Y           = read_big_fixed(header->illuminant_Y);
1328*c8dee2aaSAndroid Build Coastguard Worker     float illuminant_Z           = read_big_fixed(header->illuminant_Z);
1329*c8dee2aaSAndroid Build Coastguard Worker     profile->tag_count           = read_big_u32(header->tag_count);
1330*c8dee2aaSAndroid Build Coastguard Worker 
1331*c8dee2aaSAndroid Build Coastguard Worker     // Validate signature, size (smaller than buffer, large enough to hold tag table),
1332*c8dee2aaSAndroid Build Coastguard Worker     // and major version
1333*c8dee2aaSAndroid Build Coastguard Worker     uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
1334*c8dee2aaSAndroid Build Coastguard Worker     if (signature != skcms_Signature_acsp ||
1335*c8dee2aaSAndroid Build Coastguard Worker         profile->size > len ||
1336*c8dee2aaSAndroid Build Coastguard Worker         profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
1337*c8dee2aaSAndroid Build Coastguard Worker         (version >> 24) > 4) {
1338*c8dee2aaSAndroid Build Coastguard Worker         return false;
1339*c8dee2aaSAndroid Build Coastguard Worker     }
1340*c8dee2aaSAndroid Build Coastguard Worker 
1341*c8dee2aaSAndroid Build Coastguard Worker     // Validate that illuminant is D50 white
1342*c8dee2aaSAndroid Build Coastguard Worker     if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
1343*c8dee2aaSAndroid Build Coastguard Worker         fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
1344*c8dee2aaSAndroid Build Coastguard Worker         fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
1345*c8dee2aaSAndroid Build Coastguard Worker         return false;
1346*c8dee2aaSAndroid Build Coastguard Worker     }
1347*c8dee2aaSAndroid Build Coastguard Worker 
1348*c8dee2aaSAndroid Build Coastguard Worker     // Validate that all tag entries have sane offset + size
1349*c8dee2aaSAndroid Build Coastguard Worker     const tag_Layout* tags = get_tag_table(profile);
1350*c8dee2aaSAndroid Build Coastguard Worker     for (uint32_t i = 0; i < profile->tag_count; ++i) {
1351*c8dee2aaSAndroid Build Coastguard Worker         uint32_t tag_offset = read_big_u32(tags[i].offset);
1352*c8dee2aaSAndroid Build Coastguard Worker         uint32_t tag_size   = read_big_u32(tags[i].size);
1353*c8dee2aaSAndroid Build Coastguard Worker         uint64_t tag_end    = (uint64_t)tag_offset + (uint64_t)tag_size;
1354*c8dee2aaSAndroid Build Coastguard Worker         if (tag_size < 4 || tag_end > profile->size) {
1355*c8dee2aaSAndroid Build Coastguard Worker             return false;
1356*c8dee2aaSAndroid Build Coastguard Worker         }
1357*c8dee2aaSAndroid Build Coastguard Worker     }
1358*c8dee2aaSAndroid Build Coastguard Worker 
1359*c8dee2aaSAndroid Build Coastguard Worker     if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
1360*c8dee2aaSAndroid Build Coastguard Worker         return false;
1361*c8dee2aaSAndroid Build Coastguard Worker     }
1362*c8dee2aaSAndroid Build Coastguard Worker 
1363*c8dee2aaSAndroid Build Coastguard Worker     bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
1364*c8dee2aaSAndroid Build Coastguard Worker 
1365*c8dee2aaSAndroid Build Coastguard Worker     // Pre-parse commonly used tags.
1366*c8dee2aaSAndroid Build Coastguard Worker     skcms_ICCTag kTRC;
1367*c8dee2aaSAndroid Build Coastguard Worker     if (profile->data_color_space == skcms_Signature_Gray &&
1368*c8dee2aaSAndroid Build Coastguard Worker         skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
1369*c8dee2aaSAndroid Build Coastguard Worker         if (!read_curve(kTRC.buf, kTRC.size, &profile->trc[0], nullptr)) {
1370*c8dee2aaSAndroid Build Coastguard Worker             // Malformed tag
1371*c8dee2aaSAndroid Build Coastguard Worker             return false;
1372*c8dee2aaSAndroid Build Coastguard Worker         }
1373*c8dee2aaSAndroid Build Coastguard Worker         profile->trc[1] = profile->trc[0];
1374*c8dee2aaSAndroid Build Coastguard Worker         profile->trc[2] = profile->trc[0];
1375*c8dee2aaSAndroid Build Coastguard Worker         profile->has_trc = true;
1376*c8dee2aaSAndroid Build Coastguard Worker 
1377*c8dee2aaSAndroid Build Coastguard Worker         if (pcs_is_xyz) {
1378*c8dee2aaSAndroid Build Coastguard Worker             profile->toXYZD50.vals[0][0] = illuminant_X;
1379*c8dee2aaSAndroid Build Coastguard Worker             profile->toXYZD50.vals[1][1] = illuminant_Y;
1380*c8dee2aaSAndroid Build Coastguard Worker             profile->toXYZD50.vals[2][2] = illuminant_Z;
1381*c8dee2aaSAndroid Build Coastguard Worker             profile->has_toXYZD50 = true;
1382*c8dee2aaSAndroid Build Coastguard Worker         }
1383*c8dee2aaSAndroid Build Coastguard Worker     } else {
1384*c8dee2aaSAndroid Build Coastguard Worker         skcms_ICCTag rTRC, gTRC, bTRC;
1385*c8dee2aaSAndroid Build Coastguard Worker         if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
1386*c8dee2aaSAndroid Build Coastguard Worker             skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
1387*c8dee2aaSAndroid Build Coastguard Worker             skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
1388*c8dee2aaSAndroid Build Coastguard Worker             if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
1389*c8dee2aaSAndroid Build Coastguard Worker                 !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
1390*c8dee2aaSAndroid Build Coastguard Worker                 !read_curve(bTRC.buf, bTRC.size, &profile->trc[2], nullptr)) {
1391*c8dee2aaSAndroid Build Coastguard Worker                 // Malformed TRC tags
1392*c8dee2aaSAndroid Build Coastguard Worker                 return false;
1393*c8dee2aaSAndroid Build Coastguard Worker             }
1394*c8dee2aaSAndroid Build Coastguard Worker             profile->has_trc = true;
1395*c8dee2aaSAndroid Build Coastguard Worker         }
1396*c8dee2aaSAndroid Build Coastguard Worker 
1397*c8dee2aaSAndroid Build Coastguard Worker         skcms_ICCTag rXYZ, gXYZ, bXYZ;
1398*c8dee2aaSAndroid Build Coastguard Worker         if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
1399*c8dee2aaSAndroid Build Coastguard Worker             skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
1400*c8dee2aaSAndroid Build Coastguard Worker             skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
1401*c8dee2aaSAndroid Build Coastguard Worker             if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
1402*c8dee2aaSAndroid Build Coastguard Worker                 // Malformed XYZ tags
1403*c8dee2aaSAndroid Build Coastguard Worker                 return false;
1404*c8dee2aaSAndroid Build Coastguard Worker             }
1405*c8dee2aaSAndroid Build Coastguard Worker             profile->has_toXYZD50 = true;
1406*c8dee2aaSAndroid Build Coastguard Worker         }
1407*c8dee2aaSAndroid Build Coastguard Worker     }
1408*c8dee2aaSAndroid Build Coastguard Worker 
1409*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 0; i < priorities; i++) {
1410*c8dee2aaSAndroid Build Coastguard Worker         // enum { perceptual, relative_colormetric, saturation }
1411*c8dee2aaSAndroid Build Coastguard Worker         if (priority[i] < 0 || priority[i] > 2) {
1412*c8dee2aaSAndroid Build Coastguard Worker             return false;
1413*c8dee2aaSAndroid Build Coastguard Worker         }
1414*c8dee2aaSAndroid Build Coastguard Worker         uint32_t sig = skcms_Signature_A2B0 + static_cast<uint32_t>(priority[i]);
1415*c8dee2aaSAndroid Build Coastguard Worker         skcms_ICCTag tag;
1416*c8dee2aaSAndroid Build Coastguard Worker         if (skcms_GetTagBySignature(profile, sig, &tag)) {
1417*c8dee2aaSAndroid Build Coastguard Worker             if (!read_a2b(&tag, &profile->A2B, pcs_is_xyz)) {
1418*c8dee2aaSAndroid Build Coastguard Worker                 // Malformed A2B tag
1419*c8dee2aaSAndroid Build Coastguard Worker                 return false;
1420*c8dee2aaSAndroid Build Coastguard Worker             }
1421*c8dee2aaSAndroid Build Coastguard Worker             profile->has_A2B = true;
1422*c8dee2aaSAndroid Build Coastguard Worker             break;
1423*c8dee2aaSAndroid Build Coastguard Worker         }
1424*c8dee2aaSAndroid Build Coastguard Worker     }
1425*c8dee2aaSAndroid Build Coastguard Worker 
1426*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 0; i < priorities; i++) {
1427*c8dee2aaSAndroid Build Coastguard Worker         // enum { perceptual, relative_colormetric, saturation }
1428*c8dee2aaSAndroid Build Coastguard Worker         if (priority[i] < 0 || priority[i] > 2) {
1429*c8dee2aaSAndroid Build Coastguard Worker             return false;
1430*c8dee2aaSAndroid Build Coastguard Worker         }
1431*c8dee2aaSAndroid Build Coastguard Worker         uint32_t sig = skcms_Signature_B2A0 + static_cast<uint32_t>(priority[i]);
1432*c8dee2aaSAndroid Build Coastguard Worker         skcms_ICCTag tag;
1433*c8dee2aaSAndroid Build Coastguard Worker         if (skcms_GetTagBySignature(profile, sig, &tag)) {
1434*c8dee2aaSAndroid Build Coastguard Worker             if (!read_b2a(&tag, &profile->B2A, pcs_is_xyz)) {
1435*c8dee2aaSAndroid Build Coastguard Worker                 // Malformed B2A tag
1436*c8dee2aaSAndroid Build Coastguard Worker                 return false;
1437*c8dee2aaSAndroid Build Coastguard Worker             }
1438*c8dee2aaSAndroid Build Coastguard Worker             profile->has_B2A = true;
1439*c8dee2aaSAndroid Build Coastguard Worker             break;
1440*c8dee2aaSAndroid Build Coastguard Worker         }
1441*c8dee2aaSAndroid Build Coastguard Worker     }
1442*c8dee2aaSAndroid Build Coastguard Worker 
1443*c8dee2aaSAndroid Build Coastguard Worker     skcms_ICCTag cicp_tag;
1444*c8dee2aaSAndroid Build Coastguard Worker     if (skcms_GetTagBySignature(profile, skcms_Signature_CICP, &cicp_tag)) {
1445*c8dee2aaSAndroid Build Coastguard Worker         if (!read_cicp(&cicp_tag, &profile->CICP)) {
1446*c8dee2aaSAndroid Build Coastguard Worker             // Malformed CICP tag
1447*c8dee2aaSAndroid Build Coastguard Worker             return false;
1448*c8dee2aaSAndroid Build Coastguard Worker         }
1449*c8dee2aaSAndroid Build Coastguard Worker         profile->has_CICP = true;
1450*c8dee2aaSAndroid Build Coastguard Worker     }
1451*c8dee2aaSAndroid Build Coastguard Worker 
1452*c8dee2aaSAndroid Build Coastguard Worker     return usable_as_src(profile);
1453*c8dee2aaSAndroid Build Coastguard Worker }
1454*c8dee2aaSAndroid Build Coastguard Worker 
1455*c8dee2aaSAndroid Build Coastguard Worker 
skcms_sRGB_profile()1456*c8dee2aaSAndroid Build Coastguard Worker const skcms_ICCProfile* skcms_sRGB_profile() {
1457*c8dee2aaSAndroid Build Coastguard Worker     static const skcms_ICCProfile sRGB_profile = {
1458*c8dee2aaSAndroid Build Coastguard Worker         nullptr,               // buffer, moot here
1459*c8dee2aaSAndroid Build Coastguard Worker 
1460*c8dee2aaSAndroid Build Coastguard Worker         0,                     // size, moot here
1461*c8dee2aaSAndroid Build Coastguard Worker         skcms_Signature_RGB,   // data_color_space
1462*c8dee2aaSAndroid Build Coastguard Worker         skcms_Signature_XYZ,   // pcs
1463*c8dee2aaSAndroid Build Coastguard Worker         0,                     // tag count, moot here
1464*c8dee2aaSAndroid Build Coastguard Worker 
1465*c8dee2aaSAndroid Build Coastguard Worker         // We choose to represent sRGB with its canonical transfer function,
1466*c8dee2aaSAndroid Build Coastguard Worker         // and with its canonical XYZD50 gamut matrix.
1467*c8dee2aaSAndroid Build Coastguard Worker         {   // the 3 trc curves
1468*c8dee2aaSAndroid Build Coastguard Worker             {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1469*c8dee2aaSAndroid Build Coastguard Worker             {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1470*c8dee2aaSAndroid Build Coastguard Worker             {{0, {2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0, 0}}},
1471*c8dee2aaSAndroid Build Coastguard Worker         },
1472*c8dee2aaSAndroid Build Coastguard Worker 
1473*c8dee2aaSAndroid Build Coastguard Worker         {{  // 3x3 toXYZD50 matrix
1474*c8dee2aaSAndroid Build Coastguard Worker             { 0.436065674f, 0.385147095f, 0.143066406f },
1475*c8dee2aaSAndroid Build Coastguard Worker             { 0.222488403f, 0.716873169f, 0.060607910f },
1476*c8dee2aaSAndroid Build Coastguard Worker             { 0.013916016f, 0.097076416f, 0.714096069f },
1477*c8dee2aaSAndroid Build Coastguard Worker         }},
1478*c8dee2aaSAndroid Build Coastguard Worker 
1479*c8dee2aaSAndroid Build Coastguard Worker         {   // an empty A2B
1480*c8dee2aaSAndroid Build Coastguard Worker             {   // input_curves
1481*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1482*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1483*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1484*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1485*c8dee2aaSAndroid Build Coastguard Worker             },
1486*c8dee2aaSAndroid Build Coastguard Worker             nullptr,   // grid_8
1487*c8dee2aaSAndroid Build Coastguard Worker             nullptr,   // grid_16
1488*c8dee2aaSAndroid Build Coastguard Worker             0,         // input_channels
1489*c8dee2aaSAndroid Build Coastguard Worker             {0,0,0,0}, // grid_points
1490*c8dee2aaSAndroid Build Coastguard Worker 
1491*c8dee2aaSAndroid Build Coastguard Worker             {   // matrix_curves
1492*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1493*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1494*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1495*c8dee2aaSAndroid Build Coastguard Worker             },
1496*c8dee2aaSAndroid Build Coastguard Worker             {{  // matrix (3x4)
1497*c8dee2aaSAndroid Build Coastguard Worker                 { 0,0,0,0 },
1498*c8dee2aaSAndroid Build Coastguard Worker                 { 0,0,0,0 },
1499*c8dee2aaSAndroid Build Coastguard Worker                 { 0,0,0,0 },
1500*c8dee2aaSAndroid Build Coastguard Worker             }},
1501*c8dee2aaSAndroid Build Coastguard Worker             0,  // matrix_channels
1502*c8dee2aaSAndroid Build Coastguard Worker 
1503*c8dee2aaSAndroid Build Coastguard Worker             0,  // output_channels
1504*c8dee2aaSAndroid Build Coastguard Worker             {   // output_curves
1505*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1506*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1507*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1508*c8dee2aaSAndroid Build Coastguard Worker             },
1509*c8dee2aaSAndroid Build Coastguard Worker         },
1510*c8dee2aaSAndroid Build Coastguard Worker 
1511*c8dee2aaSAndroid Build Coastguard Worker         {   // an empty B2A
1512*c8dee2aaSAndroid Build Coastguard Worker             {   // input_curves
1513*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1514*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1515*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1516*c8dee2aaSAndroid Build Coastguard Worker             },
1517*c8dee2aaSAndroid Build Coastguard Worker             0,  // input_channels
1518*c8dee2aaSAndroid Build Coastguard Worker 
1519*c8dee2aaSAndroid Build Coastguard Worker             0,  // matrix_channels
1520*c8dee2aaSAndroid Build Coastguard Worker             {   // matrix_curves
1521*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1522*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1523*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1524*c8dee2aaSAndroid Build Coastguard Worker             },
1525*c8dee2aaSAndroid Build Coastguard Worker             {{  // matrix (3x4)
1526*c8dee2aaSAndroid Build Coastguard Worker                 { 0,0,0,0 },
1527*c8dee2aaSAndroid Build Coastguard Worker                 { 0,0,0,0 },
1528*c8dee2aaSAndroid Build Coastguard Worker                 { 0,0,0,0 },
1529*c8dee2aaSAndroid Build Coastguard Worker             }},
1530*c8dee2aaSAndroid Build Coastguard Worker 
1531*c8dee2aaSAndroid Build Coastguard Worker             {   // output_curves
1532*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1533*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1534*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1535*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1536*c8dee2aaSAndroid Build Coastguard Worker             },
1537*c8dee2aaSAndroid Build Coastguard Worker             nullptr,    // grid_8
1538*c8dee2aaSAndroid Build Coastguard Worker             nullptr,    // grid_16
1539*c8dee2aaSAndroid Build Coastguard Worker             {0,0,0,0},  // grid_points
1540*c8dee2aaSAndroid Build Coastguard Worker             0,          // output_channels
1541*c8dee2aaSAndroid Build Coastguard Worker         },
1542*c8dee2aaSAndroid Build Coastguard Worker 
1543*c8dee2aaSAndroid Build Coastguard Worker         { 0, 0, 0, 0 },  // an empty CICP
1544*c8dee2aaSAndroid Build Coastguard Worker 
1545*c8dee2aaSAndroid Build Coastguard Worker         true,  // has_trc
1546*c8dee2aaSAndroid Build Coastguard Worker         true,  // has_toXYZD50
1547*c8dee2aaSAndroid Build Coastguard Worker         false, // has_A2B
1548*c8dee2aaSAndroid Build Coastguard Worker         false, // has B2A
1549*c8dee2aaSAndroid Build Coastguard Worker         false, // has_CICP
1550*c8dee2aaSAndroid Build Coastguard Worker     };
1551*c8dee2aaSAndroid Build Coastguard Worker     return &sRGB_profile;
1552*c8dee2aaSAndroid Build Coastguard Worker }
1553*c8dee2aaSAndroid Build Coastguard Worker 
skcms_XYZD50_profile()1554*c8dee2aaSAndroid Build Coastguard Worker const skcms_ICCProfile* skcms_XYZD50_profile() {
1555*c8dee2aaSAndroid Build Coastguard Worker     // Just like sRGB above, but with identity transfer functions and toXYZD50 matrix.
1556*c8dee2aaSAndroid Build Coastguard Worker     static const skcms_ICCProfile XYZD50_profile = {
1557*c8dee2aaSAndroid Build Coastguard Worker         nullptr,               // buffer, moot here
1558*c8dee2aaSAndroid Build Coastguard Worker 
1559*c8dee2aaSAndroid Build Coastguard Worker         0,                     // size, moot here
1560*c8dee2aaSAndroid Build Coastguard Worker         skcms_Signature_RGB,   // data_color_space
1561*c8dee2aaSAndroid Build Coastguard Worker         skcms_Signature_XYZ,   // pcs
1562*c8dee2aaSAndroid Build Coastguard Worker         0,                     // tag count, moot here
1563*c8dee2aaSAndroid Build Coastguard Worker 
1564*c8dee2aaSAndroid Build Coastguard Worker         {   // the 3 trc curves
1565*c8dee2aaSAndroid Build Coastguard Worker             {{0, {1,1, 0,0,0,0,0}}},
1566*c8dee2aaSAndroid Build Coastguard Worker             {{0, {1,1, 0,0,0,0,0}}},
1567*c8dee2aaSAndroid Build Coastguard Worker             {{0, {1,1, 0,0,0,0,0}}},
1568*c8dee2aaSAndroid Build Coastguard Worker         },
1569*c8dee2aaSAndroid Build Coastguard Worker 
1570*c8dee2aaSAndroid Build Coastguard Worker         {{  // 3x3 toXYZD50 matrix
1571*c8dee2aaSAndroid Build Coastguard Worker             { 1,0,0 },
1572*c8dee2aaSAndroid Build Coastguard Worker             { 0,1,0 },
1573*c8dee2aaSAndroid Build Coastguard Worker             { 0,0,1 },
1574*c8dee2aaSAndroid Build Coastguard Worker         }},
1575*c8dee2aaSAndroid Build Coastguard Worker 
1576*c8dee2aaSAndroid Build Coastguard Worker         {   // an empty A2B
1577*c8dee2aaSAndroid Build Coastguard Worker             {   // input_curves
1578*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1579*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1580*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1581*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1582*c8dee2aaSAndroid Build Coastguard Worker             },
1583*c8dee2aaSAndroid Build Coastguard Worker             nullptr,   // grid_8
1584*c8dee2aaSAndroid Build Coastguard Worker             nullptr,   // grid_16
1585*c8dee2aaSAndroid Build Coastguard Worker             0,         // input_channels
1586*c8dee2aaSAndroid Build Coastguard Worker             {0,0,0,0}, // grid_points
1587*c8dee2aaSAndroid Build Coastguard Worker 
1588*c8dee2aaSAndroid Build Coastguard Worker             {   // matrix_curves
1589*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1590*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1591*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1592*c8dee2aaSAndroid Build Coastguard Worker             },
1593*c8dee2aaSAndroid Build Coastguard Worker             {{  // matrix (3x4)
1594*c8dee2aaSAndroid Build Coastguard Worker                 { 0,0,0,0 },
1595*c8dee2aaSAndroid Build Coastguard Worker                 { 0,0,0,0 },
1596*c8dee2aaSAndroid Build Coastguard Worker                 { 0,0,0,0 },
1597*c8dee2aaSAndroid Build Coastguard Worker             }},
1598*c8dee2aaSAndroid Build Coastguard Worker             0,  // matrix_channels
1599*c8dee2aaSAndroid Build Coastguard Worker 
1600*c8dee2aaSAndroid Build Coastguard Worker             0,  // output_channels
1601*c8dee2aaSAndroid Build Coastguard Worker             {   // output_curves
1602*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1603*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1604*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1605*c8dee2aaSAndroid Build Coastguard Worker             },
1606*c8dee2aaSAndroid Build Coastguard Worker         },
1607*c8dee2aaSAndroid Build Coastguard Worker 
1608*c8dee2aaSAndroid Build Coastguard Worker         {   // an empty B2A
1609*c8dee2aaSAndroid Build Coastguard Worker             {   // input_curves
1610*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1611*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1612*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1613*c8dee2aaSAndroid Build Coastguard Worker             },
1614*c8dee2aaSAndroid Build Coastguard Worker             0,  // input_channels
1615*c8dee2aaSAndroid Build Coastguard Worker 
1616*c8dee2aaSAndroid Build Coastguard Worker             0,  // matrix_channels
1617*c8dee2aaSAndroid Build Coastguard Worker             {   // matrix_curves
1618*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1619*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1620*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1621*c8dee2aaSAndroid Build Coastguard Worker             },
1622*c8dee2aaSAndroid Build Coastguard Worker             {{  // matrix (3x4)
1623*c8dee2aaSAndroid Build Coastguard Worker                 { 0,0,0,0 },
1624*c8dee2aaSAndroid Build Coastguard Worker                 { 0,0,0,0 },
1625*c8dee2aaSAndroid Build Coastguard Worker                 { 0,0,0,0 },
1626*c8dee2aaSAndroid Build Coastguard Worker             }},
1627*c8dee2aaSAndroid Build Coastguard Worker 
1628*c8dee2aaSAndroid Build Coastguard Worker             {   // output_curves
1629*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1630*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1631*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1632*c8dee2aaSAndroid Build Coastguard Worker                 {{0, {0,0, 0,0,0,0,0}}},
1633*c8dee2aaSAndroid Build Coastguard Worker             },
1634*c8dee2aaSAndroid Build Coastguard Worker             nullptr,    // grid_8
1635*c8dee2aaSAndroid Build Coastguard Worker             nullptr,    // grid_16
1636*c8dee2aaSAndroid Build Coastguard Worker             {0,0,0,0},  // grid_points
1637*c8dee2aaSAndroid Build Coastguard Worker             0,          // output_channels
1638*c8dee2aaSAndroid Build Coastguard Worker         },
1639*c8dee2aaSAndroid Build Coastguard Worker 
1640*c8dee2aaSAndroid Build Coastguard Worker         { 0, 0, 0, 0 },  // an empty CICP
1641*c8dee2aaSAndroid Build Coastguard Worker 
1642*c8dee2aaSAndroid Build Coastguard Worker         true,  // has_trc
1643*c8dee2aaSAndroid Build Coastguard Worker         true,  // has_toXYZD50
1644*c8dee2aaSAndroid Build Coastguard Worker         false, // has_A2B
1645*c8dee2aaSAndroid Build Coastguard Worker         false, // has B2A
1646*c8dee2aaSAndroid Build Coastguard Worker         false, // has_CICP
1647*c8dee2aaSAndroid Build Coastguard Worker     };
1648*c8dee2aaSAndroid Build Coastguard Worker 
1649*c8dee2aaSAndroid Build Coastguard Worker     return &XYZD50_profile;
1650*c8dee2aaSAndroid Build Coastguard Worker }
1651*c8dee2aaSAndroid Build Coastguard Worker 
skcms_sRGB_TransferFunction()1652*c8dee2aaSAndroid Build Coastguard Worker const skcms_TransferFunction* skcms_sRGB_TransferFunction() {
1653*c8dee2aaSAndroid Build Coastguard Worker     return &skcms_sRGB_profile()->trc[0].parametric;
1654*c8dee2aaSAndroid Build Coastguard Worker }
1655*c8dee2aaSAndroid Build Coastguard Worker 
skcms_sRGB_Inverse_TransferFunction()1656*c8dee2aaSAndroid Build Coastguard Worker const skcms_TransferFunction* skcms_sRGB_Inverse_TransferFunction() {
1657*c8dee2aaSAndroid Build Coastguard Worker     static const skcms_TransferFunction sRGB_inv =
1658*c8dee2aaSAndroid Build Coastguard Worker         {0.416666657f, 1.137283325f, -0.0f, 12.920000076f, 0.003130805f, -0.054969788f, -0.0f};
1659*c8dee2aaSAndroid Build Coastguard Worker     return &sRGB_inv;
1660*c8dee2aaSAndroid Build Coastguard Worker }
1661*c8dee2aaSAndroid Build Coastguard Worker 
skcms_Identity_TransferFunction()1662*c8dee2aaSAndroid Build Coastguard Worker const skcms_TransferFunction* skcms_Identity_TransferFunction() {
1663*c8dee2aaSAndroid Build Coastguard Worker     static const skcms_TransferFunction identity = {1,1,0,0,0,0,0};
1664*c8dee2aaSAndroid Build Coastguard Worker     return &identity;
1665*c8dee2aaSAndroid Build Coastguard Worker }
1666*c8dee2aaSAndroid Build Coastguard Worker 
1667*c8dee2aaSAndroid Build Coastguard Worker const uint8_t skcms_252_random_bytes[] = {
1668*c8dee2aaSAndroid Build Coastguard Worker     8, 179, 128, 204, 253, 38, 134, 184, 68, 102, 32, 138, 99, 39, 169, 215,
1669*c8dee2aaSAndroid Build Coastguard Worker     119, 26, 3, 223, 95, 239, 52, 132, 114, 74, 81, 234, 97, 116, 244, 205, 30,
1670*c8dee2aaSAndroid Build Coastguard Worker     154, 173, 12, 51, 159, 122, 153, 61, 226, 236, 178, 229, 55, 181, 220, 191,
1671*c8dee2aaSAndroid Build Coastguard Worker     194, 160, 126, 168, 82, 131, 18, 180, 245, 163, 22, 246, 69, 235, 252, 57,
1672*c8dee2aaSAndroid Build Coastguard Worker     108, 14, 6, 152, 240, 255, 171, 242, 20, 227, 177, 238, 96, 85, 16, 211,
1673*c8dee2aaSAndroid Build Coastguard Worker     70, 200, 149, 155, 146, 127, 145, 100, 151, 109, 19, 165, 208, 195, 164,
1674*c8dee2aaSAndroid Build Coastguard Worker     137, 254, 182, 248, 64, 201, 45, 209, 5, 147, 207, 210, 113, 162, 83, 225,
1675*c8dee2aaSAndroid Build Coastguard Worker     9, 31, 15, 231, 115, 37, 58, 53, 24, 49, 197, 56, 120, 172, 48, 21, 214,
1676*c8dee2aaSAndroid Build Coastguard Worker     129, 111, 11, 50, 187, 196, 34, 60, 103, 71, 144, 47, 203, 77, 80, 232,
1677*c8dee2aaSAndroid Build Coastguard Worker     140, 222, 250, 206, 166, 247, 139, 249, 221, 72, 106, 27, 199, 117, 54,
1678*c8dee2aaSAndroid Build Coastguard Worker     219, 135, 118, 40, 79, 41, 251, 46, 93, 212, 92, 233, 148, 28, 121, 63,
1679*c8dee2aaSAndroid Build Coastguard Worker     123, 158, 105, 59, 29, 42, 143, 23, 0, 107, 176, 87, 104, 183, 156, 193,
1680*c8dee2aaSAndroid Build Coastguard Worker     189, 90, 188, 65, 190, 17, 198, 7, 186, 161, 1, 124, 78, 125, 170, 133,
1681*c8dee2aaSAndroid Build Coastguard Worker     174, 218, 67, 157, 75, 101, 89, 217, 62, 33, 141, 228, 25, 35, 91, 230, 4,
1682*c8dee2aaSAndroid Build Coastguard Worker     2, 13, 73, 86, 167, 237, 84, 243, 44, 185, 66, 130, 110, 150, 142, 216, 88,
1683*c8dee2aaSAndroid Build Coastguard Worker     112, 36, 224, 136, 202, 76, 94, 98, 175, 213
1684*c8dee2aaSAndroid Build Coastguard Worker };
1685*c8dee2aaSAndroid Build Coastguard Worker 
skcms_ApproximatelyEqualProfiles(const skcms_ICCProfile * A,const skcms_ICCProfile * B)1686*c8dee2aaSAndroid Build Coastguard Worker bool skcms_ApproximatelyEqualProfiles(const skcms_ICCProfile* A, const skcms_ICCProfile* B) {
1687*c8dee2aaSAndroid Build Coastguard Worker     // Test for exactly equal profiles first.
1688*c8dee2aaSAndroid Build Coastguard Worker     if (A == B || 0 == memcmp(A,B, sizeof(skcms_ICCProfile))) {
1689*c8dee2aaSAndroid Build Coastguard Worker         return true;
1690*c8dee2aaSAndroid Build Coastguard Worker     }
1691*c8dee2aaSAndroid Build Coastguard Worker 
1692*c8dee2aaSAndroid Build Coastguard Worker     // For now this is the essentially the same strategy we use in test_only.c
1693*c8dee2aaSAndroid Build Coastguard Worker     // for our skcms_Transform() smoke tests:
1694*c8dee2aaSAndroid Build Coastguard Worker     //    1) transform A to XYZD50
1695*c8dee2aaSAndroid Build Coastguard Worker     //    2) transform B to XYZD50
1696*c8dee2aaSAndroid Build Coastguard Worker     //    3) return true if they're similar enough
1697*c8dee2aaSAndroid Build Coastguard Worker     // Our current criterion in 3) is maximum 1 bit error per XYZD50 byte.
1698*c8dee2aaSAndroid Build Coastguard Worker 
1699*c8dee2aaSAndroid Build Coastguard Worker     // skcms_252_random_bytes are 252 of a random shuffle of all possible bytes.
1700*c8dee2aaSAndroid Build Coastguard Worker     // 252 is evenly divisible by 3 and 4.  Only 192, 10, 241, and 43 are missing.
1701*c8dee2aaSAndroid Build Coastguard Worker 
1702*c8dee2aaSAndroid Build Coastguard Worker     // We want to allow otherwise equivalent profiles tagged as grayscale and RGB
1703*c8dee2aaSAndroid Build Coastguard Worker     // to be treated as equal.  But CMYK profiles are a totally different ballgame.
1704*c8dee2aaSAndroid Build Coastguard Worker     const auto CMYK = skcms_Signature_CMYK;
1705*c8dee2aaSAndroid Build Coastguard Worker     if ((A->data_color_space == CMYK) != (B->data_color_space == CMYK)) {
1706*c8dee2aaSAndroid Build Coastguard Worker         return false;
1707*c8dee2aaSAndroid Build Coastguard Worker     }
1708*c8dee2aaSAndroid Build Coastguard Worker 
1709*c8dee2aaSAndroid Build Coastguard Worker     // Interpret as RGB_888 if data color space is RGB or GRAY, RGBA_8888 if CMYK.
1710*c8dee2aaSAndroid Build Coastguard Worker     // TODO: working with RGBA_8888 either way is probably fastest.
1711*c8dee2aaSAndroid Build Coastguard Worker     skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1712*c8dee2aaSAndroid Build Coastguard Worker     size_t npixels = 84;
1713*c8dee2aaSAndroid Build Coastguard Worker     if (A->data_color_space == skcms_Signature_CMYK) {
1714*c8dee2aaSAndroid Build Coastguard Worker         fmt = skcms_PixelFormat_RGBA_8888;
1715*c8dee2aaSAndroid Build Coastguard Worker         npixels = 63;
1716*c8dee2aaSAndroid Build Coastguard Worker     }
1717*c8dee2aaSAndroid Build Coastguard Worker 
1718*c8dee2aaSAndroid Build Coastguard Worker     // TODO: if A or B is a known profile (skcms_sRGB_profile, skcms_XYZD50_profile),
1719*c8dee2aaSAndroid Build Coastguard Worker     // use pre-canned results and skip that skcms_Transform() call?
1720*c8dee2aaSAndroid Build Coastguard Worker     uint8_t dstA[252],
1721*c8dee2aaSAndroid Build Coastguard Worker             dstB[252];
1722*c8dee2aaSAndroid Build Coastguard Worker     if (!skcms_Transform(
1723*c8dee2aaSAndroid Build Coastguard Worker                 skcms_252_random_bytes,     fmt, skcms_AlphaFormat_Unpremul, A,
1724*c8dee2aaSAndroid Build Coastguard Worker                 dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1725*c8dee2aaSAndroid Build Coastguard Worker                 npixels)) {
1726*c8dee2aaSAndroid Build Coastguard Worker         return false;
1727*c8dee2aaSAndroid Build Coastguard Worker     }
1728*c8dee2aaSAndroid Build Coastguard Worker     if (!skcms_Transform(
1729*c8dee2aaSAndroid Build Coastguard Worker                 skcms_252_random_bytes,     fmt, skcms_AlphaFormat_Unpremul, B,
1730*c8dee2aaSAndroid Build Coastguard Worker                 dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1731*c8dee2aaSAndroid Build Coastguard Worker                 npixels)) {
1732*c8dee2aaSAndroid Build Coastguard Worker         return false;
1733*c8dee2aaSAndroid Build Coastguard Worker     }
1734*c8dee2aaSAndroid Build Coastguard Worker 
1735*c8dee2aaSAndroid Build Coastguard Worker     // TODO: make sure this final check has reasonable codegen.
1736*c8dee2aaSAndroid Build Coastguard Worker     for (size_t i = 0; i < 252; i++) {
1737*c8dee2aaSAndroid Build Coastguard Worker         if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1738*c8dee2aaSAndroid Build Coastguard Worker             return false;
1739*c8dee2aaSAndroid Build Coastguard Worker         }
1740*c8dee2aaSAndroid Build Coastguard Worker     }
1741*c8dee2aaSAndroid Build Coastguard Worker     return true;
1742*c8dee2aaSAndroid Build Coastguard Worker }
1743*c8dee2aaSAndroid Build Coastguard Worker 
skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile * profile,const skcms_TransferFunction * inv_tf)1744*c8dee2aaSAndroid Build Coastguard Worker bool skcms_TRCs_AreApproximateInverse(const skcms_ICCProfile* profile,
1745*c8dee2aaSAndroid Build Coastguard Worker                                       const skcms_TransferFunction* inv_tf) {
1746*c8dee2aaSAndroid Build Coastguard Worker     if (!profile || !profile->has_trc) {
1747*c8dee2aaSAndroid Build Coastguard Worker         return false;
1748*c8dee2aaSAndroid Build Coastguard Worker     }
1749*c8dee2aaSAndroid Build Coastguard Worker 
1750*c8dee2aaSAndroid Build Coastguard Worker     return skcms_AreApproximateInverses(&profile->trc[0], inv_tf) &&
1751*c8dee2aaSAndroid Build Coastguard Worker            skcms_AreApproximateInverses(&profile->trc[1], inv_tf) &&
1752*c8dee2aaSAndroid Build Coastguard Worker            skcms_AreApproximateInverses(&profile->trc[2], inv_tf);
1753*c8dee2aaSAndroid Build Coastguard Worker }
1754*c8dee2aaSAndroid Build Coastguard Worker 
is_zero_to_one(float x)1755*c8dee2aaSAndroid Build Coastguard Worker static bool is_zero_to_one(float x) {
1756*c8dee2aaSAndroid Build Coastguard Worker     return 0 <= x && x <= 1;
1757*c8dee2aaSAndroid Build Coastguard Worker }
1758*c8dee2aaSAndroid Build Coastguard Worker 
1759*c8dee2aaSAndroid Build Coastguard Worker typedef struct { float vals[3]; } skcms_Vector3;
1760*c8dee2aaSAndroid Build Coastguard Worker 
mv_mul(const skcms_Matrix3x3 * m,const skcms_Vector3 * v)1761*c8dee2aaSAndroid Build Coastguard Worker static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1762*c8dee2aaSAndroid Build Coastguard Worker     skcms_Vector3 dst = {{0,0,0}};
1763*c8dee2aaSAndroid Build Coastguard Worker     for (int row = 0; row < 3; ++row) {
1764*c8dee2aaSAndroid Build Coastguard Worker         dst.vals[row] = m->vals[row][0] * v->vals[0]
1765*c8dee2aaSAndroid Build Coastguard Worker                       + m->vals[row][1] * v->vals[1]
1766*c8dee2aaSAndroid Build Coastguard Worker                       + m->vals[row][2] * v->vals[2];
1767*c8dee2aaSAndroid Build Coastguard Worker     }
1768*c8dee2aaSAndroid Build Coastguard Worker     return dst;
1769*c8dee2aaSAndroid Build Coastguard Worker }
1770*c8dee2aaSAndroid Build Coastguard Worker 
skcms_AdaptToXYZD50(float wx,float wy,skcms_Matrix3x3 * toXYZD50)1771*c8dee2aaSAndroid Build Coastguard Worker bool skcms_AdaptToXYZD50(float wx, float wy,
1772*c8dee2aaSAndroid Build Coastguard Worker                          skcms_Matrix3x3* toXYZD50) {
1773*c8dee2aaSAndroid Build Coastguard Worker     if (!is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1774*c8dee2aaSAndroid Build Coastguard Worker         !toXYZD50) {
1775*c8dee2aaSAndroid Build Coastguard Worker         return false;
1776*c8dee2aaSAndroid Build Coastguard Worker     }
1777*c8dee2aaSAndroid Build Coastguard Worker 
1778*c8dee2aaSAndroid Build Coastguard Worker     // Assumes that Y is 1.0f.
1779*c8dee2aaSAndroid Build Coastguard Worker     skcms_Vector3 wXYZ = { { wx / wy, 1, (1 - wx - wy) / wy } };
1780*c8dee2aaSAndroid Build Coastguard Worker 
1781*c8dee2aaSAndroid Build Coastguard Worker     // Now convert toXYZ matrix to toXYZD50.
1782*c8dee2aaSAndroid Build Coastguard Worker     skcms_Vector3 wXYZD50 = { { 0.96422f, 1.0f, 0.82521f } };
1783*c8dee2aaSAndroid Build Coastguard Worker 
1784*c8dee2aaSAndroid Build Coastguard Worker     // Calculate the chromatic adaptation matrix.  We will use the Bradford method, thus
1785*c8dee2aaSAndroid Build Coastguard Worker     // the matrices below.  The Bradford method is used by Adobe and is widely considered
1786*c8dee2aaSAndroid Build Coastguard Worker     // to be the best.
1787*c8dee2aaSAndroid Build Coastguard Worker     skcms_Matrix3x3 xyz_to_lms = {{
1788*c8dee2aaSAndroid Build Coastguard Worker         {  0.8951f,  0.2664f, -0.1614f },
1789*c8dee2aaSAndroid Build Coastguard Worker         { -0.7502f,  1.7135f,  0.0367f },
1790*c8dee2aaSAndroid Build Coastguard Worker         {  0.0389f, -0.0685f,  1.0296f },
1791*c8dee2aaSAndroid Build Coastguard Worker     }};
1792*c8dee2aaSAndroid Build Coastguard Worker     skcms_Matrix3x3 lms_to_xyz = {{
1793*c8dee2aaSAndroid Build Coastguard Worker         {  0.9869929f, -0.1470543f, 0.1599627f },
1794*c8dee2aaSAndroid Build Coastguard Worker         {  0.4323053f,  0.5183603f, 0.0492912f },
1795*c8dee2aaSAndroid Build Coastguard Worker         { -0.0085287f,  0.0400428f, 0.9684867f },
1796*c8dee2aaSAndroid Build Coastguard Worker     }};
1797*c8dee2aaSAndroid Build Coastguard Worker 
1798*c8dee2aaSAndroid Build Coastguard Worker     skcms_Vector3 srcCone = mv_mul(&xyz_to_lms, &wXYZ);
1799*c8dee2aaSAndroid Build Coastguard Worker     skcms_Vector3 dstCone = mv_mul(&xyz_to_lms, &wXYZD50);
1800*c8dee2aaSAndroid Build Coastguard Worker 
1801*c8dee2aaSAndroid Build Coastguard Worker     *toXYZD50 = {{
1802*c8dee2aaSAndroid Build Coastguard Worker         { dstCone.vals[0] / srcCone.vals[0], 0, 0 },
1803*c8dee2aaSAndroid Build Coastguard Worker         { 0, dstCone.vals[1] / srcCone.vals[1], 0 },
1804*c8dee2aaSAndroid Build Coastguard Worker         { 0, 0, dstCone.vals[2] / srcCone.vals[2] },
1805*c8dee2aaSAndroid Build Coastguard Worker     }};
1806*c8dee2aaSAndroid Build Coastguard Worker     *toXYZD50 = skcms_Matrix3x3_concat(toXYZD50, &xyz_to_lms);
1807*c8dee2aaSAndroid Build Coastguard Worker     *toXYZD50 = skcms_Matrix3x3_concat(&lms_to_xyz, toXYZD50);
1808*c8dee2aaSAndroid Build Coastguard Worker 
1809*c8dee2aaSAndroid Build Coastguard Worker     return true;
1810*c8dee2aaSAndroid Build Coastguard Worker }
1811*c8dee2aaSAndroid Build Coastguard Worker 
skcms_PrimariesToXYZD50(float rx,float ry,float gx,float gy,float bx,float by,float wx,float wy,skcms_Matrix3x3 * toXYZD50)1812*c8dee2aaSAndroid Build Coastguard Worker bool skcms_PrimariesToXYZD50(float rx, float ry,
1813*c8dee2aaSAndroid Build Coastguard Worker                              float gx, float gy,
1814*c8dee2aaSAndroid Build Coastguard Worker                              float bx, float by,
1815*c8dee2aaSAndroid Build Coastguard Worker                              float wx, float wy,
1816*c8dee2aaSAndroid Build Coastguard Worker                              skcms_Matrix3x3* toXYZD50) {
1817*c8dee2aaSAndroid Build Coastguard Worker     if (!is_zero_to_one(rx) || !is_zero_to_one(ry) ||
1818*c8dee2aaSAndroid Build Coastguard Worker         !is_zero_to_one(gx) || !is_zero_to_one(gy) ||
1819*c8dee2aaSAndroid Build Coastguard Worker         !is_zero_to_one(bx) || !is_zero_to_one(by) ||
1820*c8dee2aaSAndroid Build Coastguard Worker         !is_zero_to_one(wx) || !is_zero_to_one(wy) ||
1821*c8dee2aaSAndroid Build Coastguard Worker         !toXYZD50) {
1822*c8dee2aaSAndroid Build Coastguard Worker         return false;
1823*c8dee2aaSAndroid Build Coastguard Worker     }
1824*c8dee2aaSAndroid Build Coastguard Worker 
1825*c8dee2aaSAndroid Build Coastguard Worker     // First, we need to convert xy values (primaries) to XYZ.
1826*c8dee2aaSAndroid Build Coastguard Worker     skcms_Matrix3x3 primaries = {{
1827*c8dee2aaSAndroid Build Coastguard Worker         { rx, gx, bx },
1828*c8dee2aaSAndroid Build Coastguard Worker         { ry, gy, by },
1829*c8dee2aaSAndroid Build Coastguard Worker         { 1 - rx - ry, 1 - gx - gy, 1 - bx - by },
1830*c8dee2aaSAndroid Build Coastguard Worker     }};
1831*c8dee2aaSAndroid Build Coastguard Worker     skcms_Matrix3x3 primaries_inv;
1832*c8dee2aaSAndroid Build Coastguard Worker     if (!skcms_Matrix3x3_invert(&primaries, &primaries_inv)) {
1833*c8dee2aaSAndroid Build Coastguard Worker         return false;
1834*c8dee2aaSAndroid Build Coastguard Worker     }
1835*c8dee2aaSAndroid Build Coastguard Worker 
1836*c8dee2aaSAndroid Build Coastguard Worker     // Assumes that Y is 1.0f.
1837*c8dee2aaSAndroid Build Coastguard Worker     skcms_Vector3 wXYZ = { { wx / wy, 1, (1 - wx - wy) / wy } };
1838*c8dee2aaSAndroid Build Coastguard Worker     skcms_Vector3 XYZ = mv_mul(&primaries_inv, &wXYZ);
1839*c8dee2aaSAndroid Build Coastguard Worker 
1840*c8dee2aaSAndroid Build Coastguard Worker     skcms_Matrix3x3 toXYZ = {{
1841*c8dee2aaSAndroid Build Coastguard Worker         { XYZ.vals[0],           0,           0 },
1842*c8dee2aaSAndroid Build Coastguard Worker         {           0, XYZ.vals[1],           0 },
1843*c8dee2aaSAndroid Build Coastguard Worker         {           0,           0, XYZ.vals[2] },
1844*c8dee2aaSAndroid Build Coastguard Worker     }};
1845*c8dee2aaSAndroid Build Coastguard Worker     toXYZ = skcms_Matrix3x3_concat(&primaries, &toXYZ);
1846*c8dee2aaSAndroid Build Coastguard Worker 
1847*c8dee2aaSAndroid Build Coastguard Worker     skcms_Matrix3x3 DXtoD50;
1848*c8dee2aaSAndroid Build Coastguard Worker     if (!skcms_AdaptToXYZD50(wx, wy, &DXtoD50)) {
1849*c8dee2aaSAndroid Build Coastguard Worker         return false;
1850*c8dee2aaSAndroid Build Coastguard Worker     }
1851*c8dee2aaSAndroid Build Coastguard Worker 
1852*c8dee2aaSAndroid Build Coastguard Worker     *toXYZD50 = skcms_Matrix3x3_concat(&DXtoD50, &toXYZ);
1853*c8dee2aaSAndroid Build Coastguard Worker     return true;
1854*c8dee2aaSAndroid Build Coastguard Worker }
1855*c8dee2aaSAndroid Build Coastguard Worker 
1856*c8dee2aaSAndroid Build Coastguard Worker 
skcms_Matrix3x3_invert(const skcms_Matrix3x3 * src,skcms_Matrix3x3 * dst)1857*c8dee2aaSAndroid Build Coastguard Worker bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1858*c8dee2aaSAndroid Build Coastguard Worker     double a00 = src->vals[0][0],
1859*c8dee2aaSAndroid Build Coastguard Worker            a01 = src->vals[1][0],
1860*c8dee2aaSAndroid Build Coastguard Worker            a02 = src->vals[2][0],
1861*c8dee2aaSAndroid Build Coastguard Worker            a10 = src->vals[0][1],
1862*c8dee2aaSAndroid Build Coastguard Worker            a11 = src->vals[1][1],
1863*c8dee2aaSAndroid Build Coastguard Worker            a12 = src->vals[2][1],
1864*c8dee2aaSAndroid Build Coastguard Worker            a20 = src->vals[0][2],
1865*c8dee2aaSAndroid Build Coastguard Worker            a21 = src->vals[1][2],
1866*c8dee2aaSAndroid Build Coastguard Worker            a22 = src->vals[2][2];
1867*c8dee2aaSAndroid Build Coastguard Worker 
1868*c8dee2aaSAndroid Build Coastguard Worker     double b0 = a00*a11 - a01*a10,
1869*c8dee2aaSAndroid Build Coastguard Worker            b1 = a00*a12 - a02*a10,
1870*c8dee2aaSAndroid Build Coastguard Worker            b2 = a01*a12 - a02*a11,
1871*c8dee2aaSAndroid Build Coastguard Worker            b3 = a20,
1872*c8dee2aaSAndroid Build Coastguard Worker            b4 = a21,
1873*c8dee2aaSAndroid Build Coastguard Worker            b5 = a22;
1874*c8dee2aaSAndroid Build Coastguard Worker 
1875*c8dee2aaSAndroid Build Coastguard Worker     double determinant = b0*b5
1876*c8dee2aaSAndroid Build Coastguard Worker                        - b1*b4
1877*c8dee2aaSAndroid Build Coastguard Worker                        + b2*b3;
1878*c8dee2aaSAndroid Build Coastguard Worker 
1879*c8dee2aaSAndroid Build Coastguard Worker     if (determinant == 0) {
1880*c8dee2aaSAndroid Build Coastguard Worker         return false;
1881*c8dee2aaSAndroid Build Coastguard Worker     }
1882*c8dee2aaSAndroid Build Coastguard Worker 
1883*c8dee2aaSAndroid Build Coastguard Worker     double invdet = 1.0 / determinant;
1884*c8dee2aaSAndroid Build Coastguard Worker     if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1885*c8dee2aaSAndroid Build Coastguard Worker         return false;
1886*c8dee2aaSAndroid Build Coastguard Worker     }
1887*c8dee2aaSAndroid Build Coastguard Worker 
1888*c8dee2aaSAndroid Build Coastguard Worker     b0 *= invdet;
1889*c8dee2aaSAndroid Build Coastguard Worker     b1 *= invdet;
1890*c8dee2aaSAndroid Build Coastguard Worker     b2 *= invdet;
1891*c8dee2aaSAndroid Build Coastguard Worker     b3 *= invdet;
1892*c8dee2aaSAndroid Build Coastguard Worker     b4 *= invdet;
1893*c8dee2aaSAndroid Build Coastguard Worker     b5 *= invdet;
1894*c8dee2aaSAndroid Build Coastguard Worker 
1895*c8dee2aaSAndroid Build Coastguard Worker     dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1896*c8dee2aaSAndroid Build Coastguard Worker     dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1897*c8dee2aaSAndroid Build Coastguard Worker     dst->vals[2][0] = (float)(        +     b2 );
1898*c8dee2aaSAndroid Build Coastguard Worker     dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1899*c8dee2aaSAndroid Build Coastguard Worker     dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1900*c8dee2aaSAndroid Build Coastguard Worker     dst->vals[2][1] = (float)(        -     b1 );
1901*c8dee2aaSAndroid Build Coastguard Worker     dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1902*c8dee2aaSAndroid Build Coastguard Worker     dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1903*c8dee2aaSAndroid Build Coastguard Worker     dst->vals[2][2] = (float)(        +     b0 );
1904*c8dee2aaSAndroid Build Coastguard Worker 
1905*c8dee2aaSAndroid Build Coastguard Worker     for (int r = 0; r < 3; ++r)
1906*c8dee2aaSAndroid Build Coastguard Worker     for (int c = 0; c < 3; ++c) {
1907*c8dee2aaSAndroid Build Coastguard Worker         if (!isfinitef_(dst->vals[r][c])) {
1908*c8dee2aaSAndroid Build Coastguard Worker             return false;
1909*c8dee2aaSAndroid Build Coastguard Worker         }
1910*c8dee2aaSAndroid Build Coastguard Worker     }
1911*c8dee2aaSAndroid Build Coastguard Worker     return true;
1912*c8dee2aaSAndroid Build Coastguard Worker }
1913*c8dee2aaSAndroid Build Coastguard Worker 
skcms_Matrix3x3_concat(const skcms_Matrix3x3 * A,const skcms_Matrix3x3 * B)1914*c8dee2aaSAndroid Build Coastguard Worker skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1915*c8dee2aaSAndroid Build Coastguard Worker     skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1916*c8dee2aaSAndroid Build Coastguard Worker     for (int r = 0; r < 3; r++)
1917*c8dee2aaSAndroid Build Coastguard Worker         for (int c = 0; c < 3; c++) {
1918*c8dee2aaSAndroid Build Coastguard Worker             m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1919*c8dee2aaSAndroid Build Coastguard Worker                          + A->vals[r][1] * B->vals[1][c]
1920*c8dee2aaSAndroid Build Coastguard Worker                          + A->vals[r][2] * B->vals[2][c];
1921*c8dee2aaSAndroid Build Coastguard Worker         }
1922*c8dee2aaSAndroid Build Coastguard Worker     return m;
1923*c8dee2aaSAndroid Build Coastguard Worker }
1924*c8dee2aaSAndroid Build Coastguard Worker 
1925*c8dee2aaSAndroid Build Coastguard Worker #if defined(__clang__)
1926*c8dee2aaSAndroid Build Coastguard Worker     [[clang::no_sanitize("float-divide-by-zero")]]  // Checked for by classify() on the way out.
1927*c8dee2aaSAndroid Build Coastguard Worker #endif
skcms_TransferFunction_invert(const skcms_TransferFunction * src,skcms_TransferFunction * dst)1928*c8dee2aaSAndroid Build Coastguard Worker bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
1929*c8dee2aaSAndroid Build Coastguard Worker     TF_PQish  pq;
1930*c8dee2aaSAndroid Build Coastguard Worker     TF_HLGish hlg;
1931*c8dee2aaSAndroid Build Coastguard Worker     switch (classify(*src, &pq, &hlg)) {
1932*c8dee2aaSAndroid Build Coastguard Worker         case skcms_TFType_Invalid: return false;
1933*c8dee2aaSAndroid Build Coastguard Worker         case skcms_TFType_sRGBish: break;  // handled below
1934*c8dee2aaSAndroid Build Coastguard Worker 
1935*c8dee2aaSAndroid Build Coastguard Worker         case skcms_TFType_PQish:
1936*c8dee2aaSAndroid Build Coastguard Worker             *dst = { TFKind_marker(skcms_TFType_PQish), -pq.A,  pq.D, 1.0f/pq.F
1937*c8dee2aaSAndroid Build Coastguard Worker                                                       ,  pq.B, -pq.E, 1.0f/pq.C};
1938*c8dee2aaSAndroid Build Coastguard Worker             return true;
1939*c8dee2aaSAndroid Build Coastguard Worker 
1940*c8dee2aaSAndroid Build Coastguard Worker         case skcms_TFType_HLGish:
1941*c8dee2aaSAndroid Build Coastguard Worker             *dst = { TFKind_marker(skcms_TFType_HLGinvish), 1.0f/hlg.R, 1.0f/hlg.G
1942*c8dee2aaSAndroid Build Coastguard Worker                                                           , 1.0f/hlg.a, hlg.b, hlg.c
1943*c8dee2aaSAndroid Build Coastguard Worker                                                           , hlg.K_minus_1 };
1944*c8dee2aaSAndroid Build Coastguard Worker             return true;
1945*c8dee2aaSAndroid Build Coastguard Worker 
1946*c8dee2aaSAndroid Build Coastguard Worker         case skcms_TFType_HLGinvish:
1947*c8dee2aaSAndroid Build Coastguard Worker             *dst = { TFKind_marker(skcms_TFType_HLGish), 1.0f/hlg.R, 1.0f/hlg.G
1948*c8dee2aaSAndroid Build Coastguard Worker                                                        , 1.0f/hlg.a, hlg.b, hlg.c
1949*c8dee2aaSAndroid Build Coastguard Worker                                                        , hlg.K_minus_1 };
1950*c8dee2aaSAndroid Build Coastguard Worker             return true;
1951*c8dee2aaSAndroid Build Coastguard Worker     }
1952*c8dee2aaSAndroid Build Coastguard Worker 
1953*c8dee2aaSAndroid Build Coastguard Worker     assert (classify(*src) == skcms_TFType_sRGBish);
1954*c8dee2aaSAndroid Build Coastguard Worker 
1955*c8dee2aaSAndroid Build Coastguard Worker     // We're inverting this function, solving for x in terms of y.
1956*c8dee2aaSAndroid Build Coastguard Worker     //   y = (cx + f)         x < d
1957*c8dee2aaSAndroid Build Coastguard Worker     //       (ax + b)^g + e   x ≥ d
1958*c8dee2aaSAndroid Build Coastguard Worker     // The inverse of this function can be expressed in the same piecewise form.
1959*c8dee2aaSAndroid Build Coastguard Worker     skcms_TransferFunction inv = {0,0,0,0,0,0,0};
1960*c8dee2aaSAndroid Build Coastguard Worker 
1961*c8dee2aaSAndroid Build Coastguard Worker     // We'll start by finding the new threshold inv.d.
1962*c8dee2aaSAndroid Build Coastguard Worker     // In principle we should be able to find that by solving for y at x=d from either side.
1963*c8dee2aaSAndroid Build Coastguard Worker     // (If those two d values aren't the same, it's a discontinuous transfer function.)
1964*c8dee2aaSAndroid Build Coastguard Worker     float d_l =       src->c * src->d + src->f,
1965*c8dee2aaSAndroid Build Coastguard Worker           d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
1966*c8dee2aaSAndroid Build Coastguard Worker     if (fabsf_(d_l - d_r) > 1/512.0f) {
1967*c8dee2aaSAndroid Build Coastguard Worker         return false;
1968*c8dee2aaSAndroid Build Coastguard Worker     }
1969*c8dee2aaSAndroid Build Coastguard Worker     inv.d = d_l;  // TODO(mtklein): better in practice to choose d_r?
1970*c8dee2aaSAndroid Build Coastguard Worker 
1971*c8dee2aaSAndroid Build Coastguard Worker     // When d=0, the linear section collapses to a point.  We leave c,d,f all zero in that case.
1972*c8dee2aaSAndroid Build Coastguard Worker     if (inv.d > 0) {
1973*c8dee2aaSAndroid Build Coastguard Worker         // Inverting the linear section is pretty straightfoward:
1974*c8dee2aaSAndroid Build Coastguard Worker         //        y       = cx + f
1975*c8dee2aaSAndroid Build Coastguard Worker         //        y - f   = cx
1976*c8dee2aaSAndroid Build Coastguard Worker         //   (1/c)y - f/c = x
1977*c8dee2aaSAndroid Build Coastguard Worker         inv.c =    1.0f/src->c;
1978*c8dee2aaSAndroid Build Coastguard Worker         inv.f = -src->f/src->c;
1979*c8dee2aaSAndroid Build Coastguard Worker     }
1980*c8dee2aaSAndroid Build Coastguard Worker 
1981*c8dee2aaSAndroid Build Coastguard Worker     // The interesting part is inverting the nonlinear section:
1982*c8dee2aaSAndroid Build Coastguard Worker     //         y                = (ax + b)^g + e.
1983*c8dee2aaSAndroid Build Coastguard Worker     //         y - e            = (ax + b)^g
1984*c8dee2aaSAndroid Build Coastguard Worker     //        (y - e)^1/g       =  ax + b
1985*c8dee2aaSAndroid Build Coastguard Worker     //        (y - e)^1/g - b   =  ax
1986*c8dee2aaSAndroid Build Coastguard Worker     //   (1/a)(y - e)^1/g - b/a =   x
1987*c8dee2aaSAndroid Build Coastguard Worker     //
1988*c8dee2aaSAndroid Build Coastguard Worker     // To make that fit our form, we need to move the (1/a) term inside the exponentiation:
1989*c8dee2aaSAndroid Build Coastguard Worker     //   let k = (1/a)^g
1990*c8dee2aaSAndroid Build Coastguard Worker     //   (1/a)( y -  e)^1/g - b/a = x
1991*c8dee2aaSAndroid Build Coastguard Worker     //        (ky - ke)^1/g - b/a = x
1992*c8dee2aaSAndroid Build Coastguard Worker 
1993*c8dee2aaSAndroid Build Coastguard Worker     float k = powf_(src->a, -src->g);  // (1/a)^g == a^-g
1994*c8dee2aaSAndroid Build Coastguard Worker     inv.g = 1.0f / src->g;
1995*c8dee2aaSAndroid Build Coastguard Worker     inv.a = k;
1996*c8dee2aaSAndroid Build Coastguard Worker     inv.b = -k * src->e;
1997*c8dee2aaSAndroid Build Coastguard Worker     inv.e = -src->b / src->a;
1998*c8dee2aaSAndroid Build Coastguard Worker 
1999*c8dee2aaSAndroid Build Coastguard Worker     // We need to enforce the same constraints here that we do when fitting a curve,
2000*c8dee2aaSAndroid Build Coastguard Worker     // a >= 0 and ad+b >= 0.  These constraints are checked by classify(), so they're true
2001*c8dee2aaSAndroid Build Coastguard Worker     // of the source function if we're here.
2002*c8dee2aaSAndroid Build Coastguard Worker 
2003*c8dee2aaSAndroid Build Coastguard Worker     // Just like when fitting the curve, there's really no way to rescue a < 0.
2004*c8dee2aaSAndroid Build Coastguard Worker     if (inv.a < 0) {
2005*c8dee2aaSAndroid Build Coastguard Worker         return false;
2006*c8dee2aaSAndroid Build Coastguard Worker     }
2007*c8dee2aaSAndroid Build Coastguard Worker     // On the other hand we can rescue an ad+b that's gone slightly negative here.
2008*c8dee2aaSAndroid Build Coastguard Worker     if (inv.a * inv.d + inv.b < 0) {
2009*c8dee2aaSAndroid Build Coastguard Worker         inv.b = -inv.a * inv.d;
2010*c8dee2aaSAndroid Build Coastguard Worker     }
2011*c8dee2aaSAndroid Build Coastguard Worker 
2012*c8dee2aaSAndroid Build Coastguard Worker     // That should usually make classify(inv) == sRGBish true, but there are a couple situations
2013*c8dee2aaSAndroid Build Coastguard Worker     // where we might still fail here, like non-finite parameter values.
2014*c8dee2aaSAndroid Build Coastguard Worker     if (classify(inv) != skcms_TFType_sRGBish) {
2015*c8dee2aaSAndroid Build Coastguard Worker         return false;
2016*c8dee2aaSAndroid Build Coastguard Worker     }
2017*c8dee2aaSAndroid Build Coastguard Worker 
2018*c8dee2aaSAndroid Build Coastguard Worker     assert (inv.a >= 0);
2019*c8dee2aaSAndroid Build Coastguard Worker     assert (inv.a * inv.d + inv.b >= 0);
2020*c8dee2aaSAndroid Build Coastguard Worker 
2021*c8dee2aaSAndroid Build Coastguard Worker     // Now in principle we're done.
2022*c8dee2aaSAndroid Build Coastguard Worker     // But to preserve the valuable invariant inv(src(1.0f)) == 1.0f, we'll tweak
2023*c8dee2aaSAndroid Build Coastguard Worker     // e or f of the inverse, depending on which segment contains src(1.0f).
2024*c8dee2aaSAndroid Build Coastguard Worker     float s = skcms_TransferFunction_eval(src, 1.0f);
2025*c8dee2aaSAndroid Build Coastguard Worker     if (!isfinitef_(s)) {
2026*c8dee2aaSAndroid Build Coastguard Worker         return false;
2027*c8dee2aaSAndroid Build Coastguard Worker     }
2028*c8dee2aaSAndroid Build Coastguard Worker 
2029*c8dee2aaSAndroid Build Coastguard Worker     float sign = s < 0 ? -1.0f : 1.0f;
2030*c8dee2aaSAndroid Build Coastguard Worker     s *= sign;
2031*c8dee2aaSAndroid Build Coastguard Worker     if (s < inv.d) {
2032*c8dee2aaSAndroid Build Coastguard Worker         inv.f = 1.0f - sign * inv.c * s;
2033*c8dee2aaSAndroid Build Coastguard Worker     } else {
2034*c8dee2aaSAndroid Build Coastguard Worker         inv.e = 1.0f - sign * powf_(inv.a * s + inv.b, inv.g);
2035*c8dee2aaSAndroid Build Coastguard Worker     }
2036*c8dee2aaSAndroid Build Coastguard Worker 
2037*c8dee2aaSAndroid Build Coastguard Worker     *dst = inv;
2038*c8dee2aaSAndroid Build Coastguard Worker     return classify(*dst) == skcms_TFType_sRGBish;
2039*c8dee2aaSAndroid Build Coastguard Worker }
2040*c8dee2aaSAndroid Build Coastguard Worker 
2041*c8dee2aaSAndroid Build Coastguard Worker // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
2042*c8dee2aaSAndroid Build Coastguard Worker 
2043*c8dee2aaSAndroid Build Coastguard Worker // From here below we're approximating an skcms_Curve with an skcms_TransferFunction{g,a,b,c,d,e,f}:
2044*c8dee2aaSAndroid Build Coastguard Worker //
2045*c8dee2aaSAndroid Build Coastguard Worker //   tf(x) =  cx + f          x < d
2046*c8dee2aaSAndroid Build Coastguard Worker //   tf(x) = (ax + b)^g + e   x ≥ d
2047*c8dee2aaSAndroid Build Coastguard Worker //
2048*c8dee2aaSAndroid Build Coastguard Worker // When fitting, we add the additional constraint that both pieces meet at d:
2049*c8dee2aaSAndroid Build Coastguard Worker //
2050*c8dee2aaSAndroid Build Coastguard Worker //   cd + f = (ad + b)^g + e
2051*c8dee2aaSAndroid Build Coastguard Worker //
2052*c8dee2aaSAndroid Build Coastguard Worker // Solving for e and folding it through gives an alternate formulation of the non-linear piece:
2053*c8dee2aaSAndroid Build Coastguard Worker //
2054*c8dee2aaSAndroid Build Coastguard Worker //   tf(x) =                           cx + f   x < d
2055*c8dee2aaSAndroid Build Coastguard Worker //   tf(x) = (ax + b)^g - (ad + b)^g + cd + f   x ≥ d
2056*c8dee2aaSAndroid Build Coastguard Worker //
2057*c8dee2aaSAndroid Build Coastguard Worker // Our overall strategy is then:
2058*c8dee2aaSAndroid Build Coastguard Worker //    For a couple tolerances,
2059*c8dee2aaSAndroid Build Coastguard Worker //       - fit_linear():    fit c,d,f iteratively to as many points as our tolerance allows
2060*c8dee2aaSAndroid Build Coastguard Worker //       - invert c,d,f
2061*c8dee2aaSAndroid Build Coastguard Worker //       - fit_nonlinear(): fit g,a,b using Gauss-Newton given those inverted c,d,f
2062*c8dee2aaSAndroid Build Coastguard Worker //                          (and by constraint, inverted e) to the inverse of the table.
2063*c8dee2aaSAndroid Build Coastguard Worker //    Return the parameters with least maximum error.
2064*c8dee2aaSAndroid Build Coastguard Worker //
2065*c8dee2aaSAndroid Build Coastguard Worker // To run Gauss-Newton to find g,a,b, we'll also need the gradient of the residuals
2066*c8dee2aaSAndroid Build Coastguard Worker // of round-trip f_inv(x), the inverse of the non-linear piece of f(x).
2067*c8dee2aaSAndroid Build Coastguard Worker //
2068*c8dee2aaSAndroid Build Coastguard Worker //    let y = Table(x)
2069*c8dee2aaSAndroid Build Coastguard Worker //    r(x) = x - f_inv(y)
2070*c8dee2aaSAndroid Build Coastguard Worker //
2071*c8dee2aaSAndroid Build Coastguard Worker //    ∂r/∂g = ln(ay + b)*(ay + b)^g
2072*c8dee2aaSAndroid Build Coastguard Worker //          - ln(ad + b)*(ad + b)^g
2073*c8dee2aaSAndroid Build Coastguard Worker //    ∂r/∂a = yg(ay + b)^(g-1)
2074*c8dee2aaSAndroid Build Coastguard Worker //          - dg(ad + b)^(g-1)
2075*c8dee2aaSAndroid Build Coastguard Worker //    ∂r/∂b =  g(ay + b)^(g-1)
2076*c8dee2aaSAndroid Build Coastguard Worker //          -  g(ad + b)^(g-1)
2077*c8dee2aaSAndroid Build Coastguard Worker 
2078*c8dee2aaSAndroid Build Coastguard Worker // Return the residual of roundtripping skcms_Curve(x) through f_inv(y) with parameters P,
2079*c8dee2aaSAndroid Build Coastguard Worker // and fill out the gradient of the residual into dfdP.
rg_nonlinear(float x,const skcms_Curve * curve,const skcms_TransferFunction * tf,float dfdP[3])2080*c8dee2aaSAndroid Build Coastguard Worker static float rg_nonlinear(float x,
2081*c8dee2aaSAndroid Build Coastguard Worker                           const skcms_Curve* curve,
2082*c8dee2aaSAndroid Build Coastguard Worker                           const skcms_TransferFunction* tf,
2083*c8dee2aaSAndroid Build Coastguard Worker                           float dfdP[3]) {
2084*c8dee2aaSAndroid Build Coastguard Worker     const float y = eval_curve(curve, x);
2085*c8dee2aaSAndroid Build Coastguard Worker 
2086*c8dee2aaSAndroid Build Coastguard Worker     const float g = tf->g, a = tf->a, b = tf->b,
2087*c8dee2aaSAndroid Build Coastguard Worker                 c = tf->c, d = tf->d, f = tf->f;
2088*c8dee2aaSAndroid Build Coastguard Worker 
2089*c8dee2aaSAndroid Build Coastguard Worker     const float Y = fmaxf_(a*y + b, 0.0f),
2090*c8dee2aaSAndroid Build Coastguard Worker                 D =        a*d + b;
2091*c8dee2aaSAndroid Build Coastguard Worker     assert (D >= 0);
2092*c8dee2aaSAndroid Build Coastguard Worker 
2093*c8dee2aaSAndroid Build Coastguard Worker     // The gradient.
2094*c8dee2aaSAndroid Build Coastguard Worker     dfdP[0] = logf_(Y)*powf_(Y, g)
2095*c8dee2aaSAndroid Build Coastguard Worker             - logf_(D)*powf_(D, g);
2096*c8dee2aaSAndroid Build Coastguard Worker     dfdP[1] = y*g*powf_(Y, g-1)
2097*c8dee2aaSAndroid Build Coastguard Worker             - d*g*powf_(D, g-1);
2098*c8dee2aaSAndroid Build Coastguard Worker     dfdP[2] =   g*powf_(Y, g-1)
2099*c8dee2aaSAndroid Build Coastguard Worker             -   g*powf_(D, g-1);
2100*c8dee2aaSAndroid Build Coastguard Worker 
2101*c8dee2aaSAndroid Build Coastguard Worker     // The residual.
2102*c8dee2aaSAndroid Build Coastguard Worker     const float f_inv = powf_(Y, g)
2103*c8dee2aaSAndroid Build Coastguard Worker                       - powf_(D, g)
2104*c8dee2aaSAndroid Build Coastguard Worker                       + c*d + f;
2105*c8dee2aaSAndroid Build Coastguard Worker     return x - f_inv;
2106*c8dee2aaSAndroid Build Coastguard Worker }
2107*c8dee2aaSAndroid Build Coastguard Worker 
gauss_newton_step(const skcms_Curve * curve,skcms_TransferFunction * tf,float x0,float dx,int N)2108*c8dee2aaSAndroid Build Coastguard Worker static bool gauss_newton_step(const skcms_Curve* curve,
2109*c8dee2aaSAndroid Build Coastguard Worker                                     skcms_TransferFunction* tf,
2110*c8dee2aaSAndroid Build Coastguard Worker                               float x0, float dx, int N) {
2111*c8dee2aaSAndroid Build Coastguard Worker     // We'll sample x from the range [x0,x1] (both inclusive) N times with even spacing.
2112*c8dee2aaSAndroid Build Coastguard Worker     //
2113*c8dee2aaSAndroid Build Coastguard Worker     // Let P = [ tf->g, tf->a, tf->b ] (the three terms that we're adjusting).
2114*c8dee2aaSAndroid Build Coastguard Worker     //
2115*c8dee2aaSAndroid Build Coastguard Worker     // We want to do P' = P + (Jf^T Jf)^-1 Jf^T r(P),
2116*c8dee2aaSAndroid Build Coastguard Worker     //   where r(P) is the residual vector
2117*c8dee2aaSAndroid Build Coastguard Worker     //   and Jf is the Jacobian matrix of f(), ∂r/∂P.
2118*c8dee2aaSAndroid Build Coastguard Worker     //
2119*c8dee2aaSAndroid Build Coastguard Worker     // Let's review the shape of each of these expressions:
2120*c8dee2aaSAndroid Build Coastguard Worker     //   r(P)   is [N x 1], a column vector with one entry per value of x tested
2121*c8dee2aaSAndroid Build Coastguard Worker     //   Jf     is [N x 3], a matrix with an entry for each (x,P) pair
2122*c8dee2aaSAndroid Build Coastguard Worker     //   Jf^T   is [3 x N], the transpose of Jf
2123*c8dee2aaSAndroid Build Coastguard Worker     //
2124*c8dee2aaSAndroid Build Coastguard Worker     //   Jf^T Jf   is [3 x N] * [N x 3] == [3 x 3], a 3x3 matrix,
2125*c8dee2aaSAndroid Build Coastguard Worker     //                                              and so is its inverse (Jf^T Jf)^-1
2126*c8dee2aaSAndroid Build Coastguard Worker     //   Jf^T r(P) is [3 x N] * [N x 1] == [3 x 1], a column vector with the same shape as P
2127*c8dee2aaSAndroid Build Coastguard Worker     //
2128*c8dee2aaSAndroid Build Coastguard Worker     // Our implementation strategy to get to the final ∆P is
2129*c8dee2aaSAndroid Build Coastguard Worker     //   1) evaluate Jf^T Jf,   call that lhs
2130*c8dee2aaSAndroid Build Coastguard Worker     //   2) evaluate Jf^T r(P), call that rhs
2131*c8dee2aaSAndroid Build Coastguard Worker     //   3) invert lhs
2132*c8dee2aaSAndroid Build Coastguard Worker     //   4) multiply inverse lhs by rhs
2133*c8dee2aaSAndroid Build Coastguard Worker     //
2134*c8dee2aaSAndroid Build Coastguard Worker     // This is a friendly implementation strategy because we don't have to have any
2135*c8dee2aaSAndroid Build Coastguard Worker     // buffers that scale with N, and equally nice don't have to perform any matrix
2136*c8dee2aaSAndroid Build Coastguard Worker     // operations that are variable size.
2137*c8dee2aaSAndroid Build Coastguard Worker     //
2138*c8dee2aaSAndroid Build Coastguard Worker     // Other implementation strategies could trade this off, e.g. evaluating the
2139*c8dee2aaSAndroid Build Coastguard Worker     // pseudoinverse of Jf ( (Jf^T Jf)^-1 Jf^T ) directly, then multiplying that by
2140*c8dee2aaSAndroid Build Coastguard Worker     // the residuals.  That would probably require implementing singular value
2141*c8dee2aaSAndroid Build Coastguard Worker     // decomposition, and would create a [3 x N] matrix to be multiplied by the
2142*c8dee2aaSAndroid Build Coastguard Worker     // [N x 1] residual vector, but on the upside I think that'd eliminate the
2143*c8dee2aaSAndroid Build Coastguard Worker     // possibility of this gauss_newton_step() function ever failing.
2144*c8dee2aaSAndroid Build Coastguard Worker 
2145*c8dee2aaSAndroid Build Coastguard Worker     // 0) start off with lhs and rhs safely zeroed.
2146*c8dee2aaSAndroid Build Coastguard Worker     skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
2147*c8dee2aaSAndroid Build Coastguard Worker     skcms_Vector3   rhs = {  {0,0,0} };
2148*c8dee2aaSAndroid Build Coastguard Worker 
2149*c8dee2aaSAndroid Build Coastguard Worker     // 1,2) evaluate lhs and evaluate rhs
2150*c8dee2aaSAndroid Build Coastguard Worker     //   We want to evaluate Jf only once, but both lhs and rhs involve Jf^T,
2151*c8dee2aaSAndroid Build Coastguard Worker     //   so we'll have to update lhs and rhs at the same time.
2152*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 0; i < N; i++) {
2153*c8dee2aaSAndroid Build Coastguard Worker         float x = x0 + static_cast<float>(i)*dx;
2154*c8dee2aaSAndroid Build Coastguard Worker 
2155*c8dee2aaSAndroid Build Coastguard Worker         float dfdP[3] = {0,0,0};
2156*c8dee2aaSAndroid Build Coastguard Worker         float resid = rg_nonlinear(x,curve,tf, dfdP);
2157*c8dee2aaSAndroid Build Coastguard Worker 
2158*c8dee2aaSAndroid Build Coastguard Worker         for (int r = 0; r < 3; r++) {
2159*c8dee2aaSAndroid Build Coastguard Worker             for (int c = 0; c < 3; c++) {
2160*c8dee2aaSAndroid Build Coastguard Worker                 lhs.vals[r][c] += dfdP[r] * dfdP[c];
2161*c8dee2aaSAndroid Build Coastguard Worker             }
2162*c8dee2aaSAndroid Build Coastguard Worker             rhs.vals[r] += dfdP[r] * resid;
2163*c8dee2aaSAndroid Build Coastguard Worker         }
2164*c8dee2aaSAndroid Build Coastguard Worker     }
2165*c8dee2aaSAndroid Build Coastguard Worker 
2166*c8dee2aaSAndroid Build Coastguard Worker     // If any of the 3 P parameters are unused, this matrix will be singular.
2167*c8dee2aaSAndroid Build Coastguard Worker     // Detect those cases and fix them up to indentity instead, so we can invert.
2168*c8dee2aaSAndroid Build Coastguard Worker     for (int k = 0; k < 3; k++) {
2169*c8dee2aaSAndroid Build Coastguard Worker         if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
2170*c8dee2aaSAndroid Build Coastguard Worker             lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
2171*c8dee2aaSAndroid Build Coastguard Worker             lhs.vals[k][k] = 1;
2172*c8dee2aaSAndroid Build Coastguard Worker         }
2173*c8dee2aaSAndroid Build Coastguard Worker     }
2174*c8dee2aaSAndroid Build Coastguard Worker 
2175*c8dee2aaSAndroid Build Coastguard Worker     // 3) invert lhs
2176*c8dee2aaSAndroid Build Coastguard Worker     skcms_Matrix3x3 lhs_inv;
2177*c8dee2aaSAndroid Build Coastguard Worker     if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
2178*c8dee2aaSAndroid Build Coastguard Worker         return false;
2179*c8dee2aaSAndroid Build Coastguard Worker     }
2180*c8dee2aaSAndroid Build Coastguard Worker 
2181*c8dee2aaSAndroid Build Coastguard Worker     // 4) multiply inverse lhs by rhs
2182*c8dee2aaSAndroid Build Coastguard Worker     skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
2183*c8dee2aaSAndroid Build Coastguard Worker     tf->g += dP.vals[0];
2184*c8dee2aaSAndroid Build Coastguard Worker     tf->a += dP.vals[1];
2185*c8dee2aaSAndroid Build Coastguard Worker     tf->b += dP.vals[2];
2186*c8dee2aaSAndroid Build Coastguard Worker     return isfinitef_(tf->g) && isfinitef_(tf->a) && isfinitef_(tf->b);
2187*c8dee2aaSAndroid Build Coastguard Worker }
2188*c8dee2aaSAndroid Build Coastguard Worker 
max_roundtrip_error_checked(const skcms_Curve * curve,const skcms_TransferFunction * tf_inv)2189*c8dee2aaSAndroid Build Coastguard Worker static float max_roundtrip_error_checked(const skcms_Curve* curve,
2190*c8dee2aaSAndroid Build Coastguard Worker                                          const skcms_TransferFunction* tf_inv) {
2191*c8dee2aaSAndroid Build Coastguard Worker     skcms_TransferFunction tf;
2192*c8dee2aaSAndroid Build Coastguard Worker     if (!skcms_TransferFunction_invert(tf_inv, &tf) || skcms_TFType_sRGBish != classify(tf)) {
2193*c8dee2aaSAndroid Build Coastguard Worker         return INFINITY_;
2194*c8dee2aaSAndroid Build Coastguard Worker     }
2195*c8dee2aaSAndroid Build Coastguard Worker 
2196*c8dee2aaSAndroid Build Coastguard Worker     skcms_TransferFunction tf_inv_again;
2197*c8dee2aaSAndroid Build Coastguard Worker     if (!skcms_TransferFunction_invert(&tf, &tf_inv_again)) {
2198*c8dee2aaSAndroid Build Coastguard Worker         return INFINITY_;
2199*c8dee2aaSAndroid Build Coastguard Worker     }
2200*c8dee2aaSAndroid Build Coastguard Worker 
2201*c8dee2aaSAndroid Build Coastguard Worker     return skcms_MaxRoundtripError(curve, &tf_inv_again);
2202*c8dee2aaSAndroid Build Coastguard Worker }
2203*c8dee2aaSAndroid Build Coastguard Worker 
2204*c8dee2aaSAndroid Build Coastguard Worker // Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
fit_nonlinear(const skcms_Curve * curve,int L,int N,skcms_TransferFunction * tf)2205*c8dee2aaSAndroid Build Coastguard Worker static bool fit_nonlinear(const skcms_Curve* curve, int L, int N, skcms_TransferFunction* tf) {
2206*c8dee2aaSAndroid Build Coastguard Worker     // This enforces a few constraints that are not modeled in gauss_newton_step()'s optimization.
2207*c8dee2aaSAndroid Build Coastguard Worker     auto fixup_tf = [tf]() {
2208*c8dee2aaSAndroid Build Coastguard Worker         // a must be non-negative. That ensures the function is monotonically increasing.
2209*c8dee2aaSAndroid Build Coastguard Worker         // We don't really know how to fix up a if it goes negative.
2210*c8dee2aaSAndroid Build Coastguard Worker         if (tf->a < 0) {
2211*c8dee2aaSAndroid Build Coastguard Worker             return false;
2212*c8dee2aaSAndroid Build Coastguard Worker         }
2213*c8dee2aaSAndroid Build Coastguard Worker         // ad+b must be non-negative. That ensures we don't end up with complex numbers in powf.
2214*c8dee2aaSAndroid Build Coastguard Worker         // We feel just barely not uneasy enough to tweak b so ad+b is zero in this case.
2215*c8dee2aaSAndroid Build Coastguard Worker         if (tf->a * tf->d + tf->b < 0) {
2216*c8dee2aaSAndroid Build Coastguard Worker             tf->b = -tf->a * tf->d;
2217*c8dee2aaSAndroid Build Coastguard Worker         }
2218*c8dee2aaSAndroid Build Coastguard Worker         assert (tf->a >= 0 &&
2219*c8dee2aaSAndroid Build Coastguard Worker                 tf->a * tf->d + tf->b >= 0);
2220*c8dee2aaSAndroid Build Coastguard Worker 
2221*c8dee2aaSAndroid Build Coastguard Worker         // cd+f must be ~= (ad+b)^g+e. That ensures the function is continuous. We keep e as a free
2222*c8dee2aaSAndroid Build Coastguard Worker         // parameter so we can guarantee this.
2223*c8dee2aaSAndroid Build Coastguard Worker         tf->e =   tf->c*tf->d + tf->f
2224*c8dee2aaSAndroid Build Coastguard Worker           - powf_(tf->a*tf->d + tf->b, tf->g);
2225*c8dee2aaSAndroid Build Coastguard Worker 
2226*c8dee2aaSAndroid Build Coastguard Worker         return isfinitef_(tf->e);
2227*c8dee2aaSAndroid Build Coastguard Worker     };
2228*c8dee2aaSAndroid Build Coastguard Worker 
2229*c8dee2aaSAndroid Build Coastguard Worker     if (!fixup_tf()) {
2230*c8dee2aaSAndroid Build Coastguard Worker         return false;
2231*c8dee2aaSAndroid Build Coastguard Worker     }
2232*c8dee2aaSAndroid Build Coastguard Worker 
2233*c8dee2aaSAndroid Build Coastguard Worker     // No matter where we start, dx should always represent N even steps from 0 to 1.
2234*c8dee2aaSAndroid Build Coastguard Worker     const float dx = 1.0f / static_cast<float>(N-1);
2235*c8dee2aaSAndroid Build Coastguard Worker 
2236*c8dee2aaSAndroid Build Coastguard Worker     skcms_TransferFunction best_tf = *tf;
2237*c8dee2aaSAndroid Build Coastguard Worker     float best_max_error = INFINITY_;
2238*c8dee2aaSAndroid Build Coastguard Worker 
2239*c8dee2aaSAndroid Build Coastguard Worker     // Need this or several curves get worse... *sigh*
2240*c8dee2aaSAndroid Build Coastguard Worker     float init_error = max_roundtrip_error_checked(curve, tf);
2241*c8dee2aaSAndroid Build Coastguard Worker     if (init_error < best_max_error) {
2242*c8dee2aaSAndroid Build Coastguard Worker         best_max_error = init_error;
2243*c8dee2aaSAndroid Build Coastguard Worker         best_tf = *tf;
2244*c8dee2aaSAndroid Build Coastguard Worker     }
2245*c8dee2aaSAndroid Build Coastguard Worker 
2246*c8dee2aaSAndroid Build Coastguard Worker     // As far as we can tell, 1 Gauss-Newton step won't converge, and 3 steps is no better than 2.
2247*c8dee2aaSAndroid Build Coastguard Worker     for (int j = 0; j < 8; j++) {
2248*c8dee2aaSAndroid Build Coastguard Worker         if (!gauss_newton_step(curve, tf, static_cast<float>(L)*dx, dx, N-L) || !fixup_tf()) {
2249*c8dee2aaSAndroid Build Coastguard Worker             *tf = best_tf;
2250*c8dee2aaSAndroid Build Coastguard Worker             return isfinitef_(best_max_error);
2251*c8dee2aaSAndroid Build Coastguard Worker         }
2252*c8dee2aaSAndroid Build Coastguard Worker 
2253*c8dee2aaSAndroid Build Coastguard Worker         float max_error = max_roundtrip_error_checked(curve, tf);
2254*c8dee2aaSAndroid Build Coastguard Worker         if (max_error < best_max_error) {
2255*c8dee2aaSAndroid Build Coastguard Worker             best_max_error = max_error;
2256*c8dee2aaSAndroid Build Coastguard Worker             best_tf = *tf;
2257*c8dee2aaSAndroid Build Coastguard Worker         }
2258*c8dee2aaSAndroid Build Coastguard Worker     }
2259*c8dee2aaSAndroid Build Coastguard Worker 
2260*c8dee2aaSAndroid Build Coastguard Worker     *tf = best_tf;
2261*c8dee2aaSAndroid Build Coastguard Worker     return isfinitef_(best_max_error);
2262*c8dee2aaSAndroid Build Coastguard Worker }
2263*c8dee2aaSAndroid Build Coastguard Worker 
skcms_ApproximateCurve(const skcms_Curve * curve,skcms_TransferFunction * approx,float * max_error)2264*c8dee2aaSAndroid Build Coastguard Worker bool skcms_ApproximateCurve(const skcms_Curve* curve,
2265*c8dee2aaSAndroid Build Coastguard Worker                             skcms_TransferFunction* approx,
2266*c8dee2aaSAndroid Build Coastguard Worker                             float* max_error) {
2267*c8dee2aaSAndroid Build Coastguard Worker     if (!curve || !approx || !max_error) {
2268*c8dee2aaSAndroid Build Coastguard Worker         return false;
2269*c8dee2aaSAndroid Build Coastguard Worker     }
2270*c8dee2aaSAndroid Build Coastguard Worker 
2271*c8dee2aaSAndroid Build Coastguard Worker     if (curve->table_entries == 0) {
2272*c8dee2aaSAndroid Build Coastguard Worker         // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
2273*c8dee2aaSAndroid Build Coastguard Worker         return false;
2274*c8dee2aaSAndroid Build Coastguard Worker     }
2275*c8dee2aaSAndroid Build Coastguard Worker 
2276*c8dee2aaSAndroid Build Coastguard Worker     if (curve->table_entries == 1 || curve->table_entries > (uint32_t)INT_MAX) {
2277*c8dee2aaSAndroid Build Coastguard Worker         // We need at least two points, and must put some reasonable cap on the maximum number.
2278*c8dee2aaSAndroid Build Coastguard Worker         return false;
2279*c8dee2aaSAndroid Build Coastguard Worker     }
2280*c8dee2aaSAndroid Build Coastguard Worker 
2281*c8dee2aaSAndroid Build Coastguard Worker     int N = (int)curve->table_entries;
2282*c8dee2aaSAndroid Build Coastguard Worker     const float dx = 1.0f / static_cast<float>(N - 1);
2283*c8dee2aaSAndroid Build Coastguard Worker 
2284*c8dee2aaSAndroid Build Coastguard Worker     *max_error = INFINITY_;
2285*c8dee2aaSAndroid Build Coastguard Worker     const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
2286*c8dee2aaSAndroid Build Coastguard Worker     for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
2287*c8dee2aaSAndroid Build Coastguard Worker         skcms_TransferFunction tf,
2288*c8dee2aaSAndroid Build Coastguard Worker                                tf_inv;
2289*c8dee2aaSAndroid Build Coastguard Worker 
2290*c8dee2aaSAndroid Build Coastguard Worker         // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
2291*c8dee2aaSAndroid Build Coastguard Worker         tf.f = 0.0f;
2292*c8dee2aaSAndroid Build Coastguard Worker         int L = fit_linear(curve, N, kTolerances[t], &tf.c, &tf.d);
2293*c8dee2aaSAndroid Build Coastguard Worker 
2294*c8dee2aaSAndroid Build Coastguard Worker         if (L == N) {
2295*c8dee2aaSAndroid Build Coastguard Worker             // If the entire data set was linear, move the coefficients to the nonlinear portion
2296*c8dee2aaSAndroid Build Coastguard Worker             // with G == 1.  This lets use a canonical representation with d == 0.
2297*c8dee2aaSAndroid Build Coastguard Worker             tf.g = 1;
2298*c8dee2aaSAndroid Build Coastguard Worker             tf.a = tf.c;
2299*c8dee2aaSAndroid Build Coastguard Worker             tf.b = tf.f;
2300*c8dee2aaSAndroid Build Coastguard Worker             tf.c = tf.d = tf.e = tf.f = 0;
2301*c8dee2aaSAndroid Build Coastguard Worker         } else if (L == N - 1) {
2302*c8dee2aaSAndroid Build Coastguard Worker             // Degenerate case with only two points in the nonlinear segment. Solve directly.
2303*c8dee2aaSAndroid Build Coastguard Worker             tf.g = 1;
2304*c8dee2aaSAndroid Build Coastguard Worker             tf.a = (eval_curve(curve, static_cast<float>(N-1)*dx) -
2305*c8dee2aaSAndroid Build Coastguard Worker                     eval_curve(curve, static_cast<float>(N-2)*dx))
2306*c8dee2aaSAndroid Build Coastguard Worker                  / dx;
2307*c8dee2aaSAndroid Build Coastguard Worker             tf.b = eval_curve(curve, static_cast<float>(N-2)*dx)
2308*c8dee2aaSAndroid Build Coastguard Worker                  - tf.a * static_cast<float>(N-2)*dx;
2309*c8dee2aaSAndroid Build Coastguard Worker             tf.e = 0;
2310*c8dee2aaSAndroid Build Coastguard Worker         } else {
2311*c8dee2aaSAndroid Build Coastguard Worker             // Start by guessing a gamma-only curve through the midpoint.
2312*c8dee2aaSAndroid Build Coastguard Worker             int mid = (L + N) / 2;
2313*c8dee2aaSAndroid Build Coastguard Worker             float mid_x = static_cast<float>(mid) / static_cast<float>(N - 1);
2314*c8dee2aaSAndroid Build Coastguard Worker             float mid_y = eval_curve(curve, mid_x);
2315*c8dee2aaSAndroid Build Coastguard Worker             tf.g = log2f_(mid_y) / log2f_(mid_x);
2316*c8dee2aaSAndroid Build Coastguard Worker             tf.a = 1;
2317*c8dee2aaSAndroid Build Coastguard Worker             tf.b = 0;
2318*c8dee2aaSAndroid Build Coastguard Worker             tf.e =    tf.c*tf.d + tf.f
2319*c8dee2aaSAndroid Build Coastguard Worker               - powf_(tf.a*tf.d + tf.b, tf.g);
2320*c8dee2aaSAndroid Build Coastguard Worker 
2321*c8dee2aaSAndroid Build Coastguard Worker 
2322*c8dee2aaSAndroid Build Coastguard Worker             if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
2323*c8dee2aaSAndroid Build Coastguard Worker                 !fit_nonlinear(curve, L,N, &tf_inv)) {
2324*c8dee2aaSAndroid Build Coastguard Worker                 continue;
2325*c8dee2aaSAndroid Build Coastguard Worker             }
2326*c8dee2aaSAndroid Build Coastguard Worker 
2327*c8dee2aaSAndroid Build Coastguard Worker             // We fit tf_inv, so calculate tf to keep in sync.
2328*c8dee2aaSAndroid Build Coastguard Worker             // fit_nonlinear() should guarantee invertibility.
2329*c8dee2aaSAndroid Build Coastguard Worker             if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
2330*c8dee2aaSAndroid Build Coastguard Worker                 assert(false);
2331*c8dee2aaSAndroid Build Coastguard Worker                 continue;
2332*c8dee2aaSAndroid Build Coastguard Worker             }
2333*c8dee2aaSAndroid Build Coastguard Worker         }
2334*c8dee2aaSAndroid Build Coastguard Worker 
2335*c8dee2aaSAndroid Build Coastguard Worker         // We'd better have a sane, sRGB-ish TF by now.
2336*c8dee2aaSAndroid Build Coastguard Worker         // Other non-Bad TFs would be fine, but we know we've only ever tried to fit sRGBish;
2337*c8dee2aaSAndroid Build Coastguard Worker         // anything else is just some accident of math and the way we pun tf.g as a type flag.
2338*c8dee2aaSAndroid Build Coastguard Worker         // fit_nonlinear() should guarantee this, but the special cases may fail this test.
2339*c8dee2aaSAndroid Build Coastguard Worker         if (skcms_TFType_sRGBish != classify(tf)) {
2340*c8dee2aaSAndroid Build Coastguard Worker             continue;
2341*c8dee2aaSAndroid Build Coastguard Worker         }
2342*c8dee2aaSAndroid Build Coastguard Worker 
2343*c8dee2aaSAndroid Build Coastguard Worker         // We find our error by roundtripping the table through tf_inv.
2344*c8dee2aaSAndroid Build Coastguard Worker         //
2345*c8dee2aaSAndroid Build Coastguard Worker         // (The most likely use case for this approximation is to be inverted and
2346*c8dee2aaSAndroid Build Coastguard Worker         // used as the transfer function for a destination color space.)
2347*c8dee2aaSAndroid Build Coastguard Worker         //
2348*c8dee2aaSAndroid Build Coastguard Worker         // We've kept tf and tf_inv in sync above, but we can't guarantee that tf is
2349*c8dee2aaSAndroid Build Coastguard Worker         // invertible, so re-verify that here (and use the new inverse for testing).
2350*c8dee2aaSAndroid Build Coastguard Worker         // fit_nonlinear() should guarantee this, but the special cases that don't use
2351*c8dee2aaSAndroid Build Coastguard Worker         // it may fail this test.
2352*c8dee2aaSAndroid Build Coastguard Worker         if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
2353*c8dee2aaSAndroid Build Coastguard Worker             continue;
2354*c8dee2aaSAndroid Build Coastguard Worker         }
2355*c8dee2aaSAndroid Build Coastguard Worker 
2356*c8dee2aaSAndroid Build Coastguard Worker         float err = skcms_MaxRoundtripError(curve, &tf_inv);
2357*c8dee2aaSAndroid Build Coastguard Worker         if (*max_error > err) {
2358*c8dee2aaSAndroid Build Coastguard Worker             *max_error = err;
2359*c8dee2aaSAndroid Build Coastguard Worker             *approx    = tf;
2360*c8dee2aaSAndroid Build Coastguard Worker         }
2361*c8dee2aaSAndroid Build Coastguard Worker     }
2362*c8dee2aaSAndroid Build Coastguard Worker     return isfinitef_(*max_error);
2363*c8dee2aaSAndroid Build Coastguard Worker }
2364*c8dee2aaSAndroid Build Coastguard Worker 
2365*c8dee2aaSAndroid Build Coastguard Worker enum class CpuType { Baseline, HSW, SKX };
2366*c8dee2aaSAndroid Build Coastguard Worker 
cpu_type()2367*c8dee2aaSAndroid Build Coastguard Worker static CpuType cpu_type() {
2368*c8dee2aaSAndroid Build Coastguard Worker     #if defined(SKCMS_PORTABLE) || !defined(__x86_64__) || defined(SKCMS_FORCE_BASELINE)
2369*c8dee2aaSAndroid Build Coastguard Worker         return CpuType::Baseline;
2370*c8dee2aaSAndroid Build Coastguard Worker     #elif defined(SKCMS_FORCE_HSW)
2371*c8dee2aaSAndroid Build Coastguard Worker         return CpuType::HSW;
2372*c8dee2aaSAndroid Build Coastguard Worker     #elif defined(SKCMS_FORCE_SKX)
2373*c8dee2aaSAndroid Build Coastguard Worker         return CpuType::SKX;
2374*c8dee2aaSAndroid Build Coastguard Worker     #else
2375*c8dee2aaSAndroid Build Coastguard Worker         static const CpuType type = []{
2376*c8dee2aaSAndroid Build Coastguard Worker             if (!sAllowRuntimeCPUDetection) {
2377*c8dee2aaSAndroid Build Coastguard Worker                 return CpuType::Baseline;
2378*c8dee2aaSAndroid Build Coastguard Worker             }
2379*c8dee2aaSAndroid Build Coastguard Worker             // See http://www.sandpile.org/x86/cpuid.htm
2380*c8dee2aaSAndroid Build Coastguard Worker 
2381*c8dee2aaSAndroid Build Coastguard Worker             // First, a basic cpuid(1) lets us check prerequisites for HSW, SKX.
2382*c8dee2aaSAndroid Build Coastguard Worker             uint32_t eax, ebx, ecx, edx;
2383*c8dee2aaSAndroid Build Coastguard Worker             __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2384*c8dee2aaSAndroid Build Coastguard Worker                                          : "0"(1), "2"(0));
2385*c8dee2aaSAndroid Build Coastguard Worker             if ((edx & (1u<<25)) &&  // SSE
2386*c8dee2aaSAndroid Build Coastguard Worker                 (edx & (1u<<26)) &&  // SSE2
2387*c8dee2aaSAndroid Build Coastguard Worker                 (ecx & (1u<< 0)) &&  // SSE3
2388*c8dee2aaSAndroid Build Coastguard Worker                 (ecx & (1u<< 9)) &&  // SSSE3
2389*c8dee2aaSAndroid Build Coastguard Worker                 (ecx & (1u<<12)) &&  // FMA (N.B. not used, avoided even)
2390*c8dee2aaSAndroid Build Coastguard Worker                 (ecx & (1u<<19)) &&  // SSE4.1
2391*c8dee2aaSAndroid Build Coastguard Worker                 (ecx & (1u<<20)) &&  // SSE4.2
2392*c8dee2aaSAndroid Build Coastguard Worker                 (ecx & (1u<<26)) &&  // XSAVE
2393*c8dee2aaSAndroid Build Coastguard Worker                 (ecx & (1u<<27)) &&  // OSXSAVE
2394*c8dee2aaSAndroid Build Coastguard Worker                 (ecx & (1u<<28)) &&  // AVX
2395*c8dee2aaSAndroid Build Coastguard Worker                 (ecx & (1u<<29))) {  // F16C
2396*c8dee2aaSAndroid Build Coastguard Worker 
2397*c8dee2aaSAndroid Build Coastguard Worker                 // Call cpuid(7) to check for AVX2 and AVX-512 bits.
2398*c8dee2aaSAndroid Build Coastguard Worker                 __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2399*c8dee2aaSAndroid Build Coastguard Worker                                              : "0"(7), "2"(0));
2400*c8dee2aaSAndroid Build Coastguard Worker                 // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
2401*c8dee2aaSAndroid Build Coastguard Worker                 uint32_t xcr0, dont_need_edx;
2402*c8dee2aaSAndroid Build Coastguard Worker                 __asm__ __volatile__("xgetbv" : "=a"(xcr0), "=d"(dont_need_edx) : "c"(0));
2403*c8dee2aaSAndroid Build Coastguard Worker 
2404*c8dee2aaSAndroid Build Coastguard Worker                 if ((xcr0 & (1u<<1)) &&  // XMM register state saved?
2405*c8dee2aaSAndroid Build Coastguard Worker                     (xcr0 & (1u<<2)) &&  // YMM register state saved?
2406*c8dee2aaSAndroid Build Coastguard Worker                     (ebx  & (1u<<5))) {  // AVX2
2407*c8dee2aaSAndroid Build Coastguard Worker                     // At this point we're at least HSW.  Continue checking for SKX.
2408*c8dee2aaSAndroid Build Coastguard Worker                     if ((xcr0 & (1u<< 5)) && // Opmasks state saved?
2409*c8dee2aaSAndroid Build Coastguard Worker                         (xcr0 & (1u<< 6)) && // First 16 ZMM registers saved?
2410*c8dee2aaSAndroid Build Coastguard Worker                         (xcr0 & (1u<< 7)) && // High 16 ZMM registers saved?
2411*c8dee2aaSAndroid Build Coastguard Worker                         (ebx  & (1u<<16)) && // AVX512F
2412*c8dee2aaSAndroid Build Coastguard Worker                         (ebx  & (1u<<17)) && // AVX512DQ
2413*c8dee2aaSAndroid Build Coastguard Worker                         (ebx  & (1u<<28)) && // AVX512CD
2414*c8dee2aaSAndroid Build Coastguard Worker                         (ebx  & (1u<<30)) && // AVX512BW
2415*c8dee2aaSAndroid Build Coastguard Worker                         (ebx  & (1u<<31))) { // AVX512VL
2416*c8dee2aaSAndroid Build Coastguard Worker                         return CpuType::SKX;
2417*c8dee2aaSAndroid Build Coastguard Worker                     }
2418*c8dee2aaSAndroid Build Coastguard Worker                     return CpuType::HSW;
2419*c8dee2aaSAndroid Build Coastguard Worker                 }
2420*c8dee2aaSAndroid Build Coastguard Worker             }
2421*c8dee2aaSAndroid Build Coastguard Worker             return CpuType::Baseline;
2422*c8dee2aaSAndroid Build Coastguard Worker         }();
2423*c8dee2aaSAndroid Build Coastguard Worker         return type;
2424*c8dee2aaSAndroid Build Coastguard Worker     #endif
2425*c8dee2aaSAndroid Build Coastguard Worker }
2426*c8dee2aaSAndroid Build Coastguard Worker 
tf_is_gamma(const skcms_TransferFunction & tf)2427*c8dee2aaSAndroid Build Coastguard Worker static bool tf_is_gamma(const skcms_TransferFunction& tf) {
2428*c8dee2aaSAndroid Build Coastguard Worker     return tf.g > 0 && tf.a == 1 &&
2429*c8dee2aaSAndroid Build Coastguard Worker            tf.b == 0 && tf.c == 0 && tf.d == 0 && tf.e == 0 && tf.f == 0;
2430*c8dee2aaSAndroid Build Coastguard Worker }
2431*c8dee2aaSAndroid Build Coastguard Worker 
2432*c8dee2aaSAndroid Build Coastguard Worker struct OpAndArg {
2433*c8dee2aaSAndroid Build Coastguard Worker     Op          op;
2434*c8dee2aaSAndroid Build Coastguard Worker     const void* arg;
2435*c8dee2aaSAndroid Build Coastguard Worker };
2436*c8dee2aaSAndroid Build Coastguard Worker 
select_curve_op(const skcms_Curve * curve,int channel)2437*c8dee2aaSAndroid Build Coastguard Worker static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
2438*c8dee2aaSAndroid Build Coastguard Worker     struct OpType {
2439*c8dee2aaSAndroid Build Coastguard Worker         Op sGamma, sRGBish, PQish, HLGish, HLGinvish, table;
2440*c8dee2aaSAndroid Build Coastguard Worker     };
2441*c8dee2aaSAndroid Build Coastguard Worker     static constexpr OpType kOps[] = {
2442*c8dee2aaSAndroid Build Coastguard Worker         { Op::gamma_r, Op::tf_r, Op::pq_r, Op::hlg_r, Op::hlginv_r, Op::table_r },
2443*c8dee2aaSAndroid Build Coastguard Worker         { Op::gamma_g, Op::tf_g, Op::pq_g, Op::hlg_g, Op::hlginv_g, Op::table_g },
2444*c8dee2aaSAndroid Build Coastguard Worker         { Op::gamma_b, Op::tf_b, Op::pq_b, Op::hlg_b, Op::hlginv_b, Op::table_b },
2445*c8dee2aaSAndroid Build Coastguard Worker         { Op::gamma_a, Op::tf_a, Op::pq_a, Op::hlg_a, Op::hlginv_a, Op::table_a },
2446*c8dee2aaSAndroid Build Coastguard Worker     };
2447*c8dee2aaSAndroid Build Coastguard Worker     const auto& op = kOps[channel];
2448*c8dee2aaSAndroid Build Coastguard Worker 
2449*c8dee2aaSAndroid Build Coastguard Worker     if (curve->table_entries == 0) {
2450*c8dee2aaSAndroid Build Coastguard Worker         const OpAndArg noop = { Op::load_a8/*doesn't matter*/, nullptr };
2451*c8dee2aaSAndroid Build Coastguard Worker 
2452*c8dee2aaSAndroid Build Coastguard Worker         const skcms_TransferFunction& tf = curve->parametric;
2453*c8dee2aaSAndroid Build Coastguard Worker 
2454*c8dee2aaSAndroid Build Coastguard Worker         if (tf_is_gamma(tf)) {
2455*c8dee2aaSAndroid Build Coastguard Worker             return tf.g != 1 ? OpAndArg{op.sGamma, &tf}
2456*c8dee2aaSAndroid Build Coastguard Worker                              : noop;
2457*c8dee2aaSAndroid Build Coastguard Worker         }
2458*c8dee2aaSAndroid Build Coastguard Worker 
2459*c8dee2aaSAndroid Build Coastguard Worker         switch (classify(tf)) {
2460*c8dee2aaSAndroid Build Coastguard Worker             case skcms_TFType_Invalid:    return noop;
2461*c8dee2aaSAndroid Build Coastguard Worker             case skcms_TFType_sRGBish:    return OpAndArg{op.sRGBish,   &tf};
2462*c8dee2aaSAndroid Build Coastguard Worker             case skcms_TFType_PQish:      return OpAndArg{op.PQish,     &tf};
2463*c8dee2aaSAndroid Build Coastguard Worker             case skcms_TFType_HLGish:     return OpAndArg{op.HLGish,    &tf};
2464*c8dee2aaSAndroid Build Coastguard Worker             case skcms_TFType_HLGinvish:  return OpAndArg{op.HLGinvish, &tf};
2465*c8dee2aaSAndroid Build Coastguard Worker         }
2466*c8dee2aaSAndroid Build Coastguard Worker     }
2467*c8dee2aaSAndroid Build Coastguard Worker     return OpAndArg{op.table, curve};
2468*c8dee2aaSAndroid Build Coastguard Worker }
2469*c8dee2aaSAndroid Build Coastguard Worker 
select_curve_ops(const skcms_Curve * curves,int numChannels,OpAndArg * ops)2470*c8dee2aaSAndroid Build Coastguard Worker static int select_curve_ops(const skcms_Curve* curves, int numChannels, OpAndArg* ops) {
2471*c8dee2aaSAndroid Build Coastguard Worker     // We process the channels in reverse order, yielding ops in ABGR order.
2472*c8dee2aaSAndroid Build Coastguard Worker     // (Working backwards allows us to fuse trailing B+G+R ops into a single RGB op.)
2473*c8dee2aaSAndroid Build Coastguard Worker     int cursor = 0;
2474*c8dee2aaSAndroid Build Coastguard Worker     for (int index = numChannels; index-- > 0; ) {
2475*c8dee2aaSAndroid Build Coastguard Worker         ops[cursor] = select_curve_op(&curves[index], index);
2476*c8dee2aaSAndroid Build Coastguard Worker         if (ops[cursor].arg) {
2477*c8dee2aaSAndroid Build Coastguard Worker             ++cursor;
2478*c8dee2aaSAndroid Build Coastguard Worker         }
2479*c8dee2aaSAndroid Build Coastguard Worker     }
2480*c8dee2aaSAndroid Build Coastguard Worker 
2481*c8dee2aaSAndroid Build Coastguard Worker     // Identify separate B+G+R ops and fuse them into a single RGB op.
2482*c8dee2aaSAndroid Build Coastguard Worker     if (cursor >= 3) {
2483*c8dee2aaSAndroid Build Coastguard Worker         struct FusableOps {
2484*c8dee2aaSAndroid Build Coastguard Worker             Op r, g, b, rgb;
2485*c8dee2aaSAndroid Build Coastguard Worker         };
2486*c8dee2aaSAndroid Build Coastguard Worker         static constexpr FusableOps kFusableOps[] = {
2487*c8dee2aaSAndroid Build Coastguard Worker             {Op::gamma_r,  Op::gamma_g,  Op::gamma_b,  Op::gamma_rgb},
2488*c8dee2aaSAndroid Build Coastguard Worker             {Op::tf_r,     Op::tf_g,     Op::tf_b,     Op::tf_rgb},
2489*c8dee2aaSAndroid Build Coastguard Worker             {Op::pq_r,     Op::pq_g,     Op::pq_b,     Op::pq_rgb},
2490*c8dee2aaSAndroid Build Coastguard Worker             {Op::hlg_r,    Op::hlg_g,    Op::hlg_b,    Op::hlg_rgb},
2491*c8dee2aaSAndroid Build Coastguard Worker             {Op::hlginv_r, Op::hlginv_g, Op::hlginv_b, Op::hlginv_rgb},
2492*c8dee2aaSAndroid Build Coastguard Worker         };
2493*c8dee2aaSAndroid Build Coastguard Worker 
2494*c8dee2aaSAndroid Build Coastguard Worker         int posR = cursor - 1;
2495*c8dee2aaSAndroid Build Coastguard Worker         int posG = cursor - 2;
2496*c8dee2aaSAndroid Build Coastguard Worker         int posB = cursor - 3;
2497*c8dee2aaSAndroid Build Coastguard Worker         for (const FusableOps& fusableOp : kFusableOps) {
2498*c8dee2aaSAndroid Build Coastguard Worker             if (ops[posR].op == fusableOp.r &&
2499*c8dee2aaSAndroid Build Coastguard Worker                 ops[posG].op == fusableOp.g &&
2500*c8dee2aaSAndroid Build Coastguard Worker                 ops[posB].op == fusableOp.b &&
2501*c8dee2aaSAndroid Build Coastguard Worker                 (0 == memcmp(ops[posR].arg, ops[posG].arg, sizeof(skcms_TransferFunction))) &&
2502*c8dee2aaSAndroid Build Coastguard Worker                 (0 == memcmp(ops[posR].arg, ops[posB].arg, sizeof(skcms_TransferFunction)))) {
2503*c8dee2aaSAndroid Build Coastguard Worker                 // Fuse the three matching ops into one.
2504*c8dee2aaSAndroid Build Coastguard Worker                 ops[posB].op = fusableOp.rgb;
2505*c8dee2aaSAndroid Build Coastguard Worker                 cursor -= 2;
2506*c8dee2aaSAndroid Build Coastguard Worker                 break;
2507*c8dee2aaSAndroid Build Coastguard Worker             }
2508*c8dee2aaSAndroid Build Coastguard Worker         }
2509*c8dee2aaSAndroid Build Coastguard Worker     }
2510*c8dee2aaSAndroid Build Coastguard Worker 
2511*c8dee2aaSAndroid Build Coastguard Worker     return cursor;
2512*c8dee2aaSAndroid Build Coastguard Worker }
2513*c8dee2aaSAndroid Build Coastguard Worker 
bytes_per_pixel(skcms_PixelFormat fmt)2514*c8dee2aaSAndroid Build Coastguard Worker static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2515*c8dee2aaSAndroid Build Coastguard Worker     switch (fmt >> 1) {   // ignore rgb/bgr
2516*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_A_8              >> 1: return  1;
2517*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_G_8              >> 1: return  1;
2518*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_GA_88            >> 1: return  2;
2519*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_ABGR_4444        >> 1: return  2;
2520*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_565          >> 1: return  2;
2521*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_888          >> 1: return  3;
2522*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_8888        >> 1: return  4;
2523*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_8888_sRGB   >> 1: return  4;
2524*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_1010102     >> 1: return  4;
2525*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_101010x_XR   >> 1: return  4;
2526*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_161616LE     >> 1: return  6;
2527*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_10101010_XR >> 1: return  8;
2528*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_16161616LE  >> 1: return  8;
2529*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_161616BE     >> 1: return  6;
2530*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_16161616BE  >> 1: return  8;
2531*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_hhh_Norm     >> 1: return  6;
2532*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_hhhh_Norm   >> 1: return  8;
2533*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_hhh          >> 1: return  6;
2534*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_hhhh        >> 1: return  8;
2535*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_fff          >> 1: return 12;
2536*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_ffff        >> 1: return 16;
2537*c8dee2aaSAndroid Build Coastguard Worker     }
2538*c8dee2aaSAndroid Build Coastguard Worker     assert(false);
2539*c8dee2aaSAndroid Build Coastguard Worker     return 0;
2540*c8dee2aaSAndroid Build Coastguard Worker }
2541*c8dee2aaSAndroid Build Coastguard Worker 
prep_for_destination(const skcms_ICCProfile * profile,skcms_Matrix3x3 * fromXYZD50,skcms_TransferFunction * invR,skcms_TransferFunction * invG,skcms_TransferFunction * invB)2542*c8dee2aaSAndroid Build Coastguard Worker static bool prep_for_destination(const skcms_ICCProfile* profile,
2543*c8dee2aaSAndroid Build Coastguard Worker                                  skcms_Matrix3x3* fromXYZD50,
2544*c8dee2aaSAndroid Build Coastguard Worker                                  skcms_TransferFunction* invR,
2545*c8dee2aaSAndroid Build Coastguard Worker                                  skcms_TransferFunction* invG,
2546*c8dee2aaSAndroid Build Coastguard Worker                                  skcms_TransferFunction* invB) {
2547*c8dee2aaSAndroid Build Coastguard Worker     // skcms_Transform() supports B2A destinations...
2548*c8dee2aaSAndroid Build Coastguard Worker     if (profile->has_B2A) { return true; }
2549*c8dee2aaSAndroid Build Coastguard Worker     // ...and destinations with parametric transfer functions and an XYZD50 gamut matrix.
2550*c8dee2aaSAndroid Build Coastguard Worker     return profile->has_trc
2551*c8dee2aaSAndroid Build Coastguard Worker         && profile->has_toXYZD50
2552*c8dee2aaSAndroid Build Coastguard Worker         && profile->trc[0].table_entries == 0
2553*c8dee2aaSAndroid Build Coastguard Worker         && profile->trc[1].table_entries == 0
2554*c8dee2aaSAndroid Build Coastguard Worker         && profile->trc[2].table_entries == 0
2555*c8dee2aaSAndroid Build Coastguard Worker         && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2556*c8dee2aaSAndroid Build Coastguard Worker         && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2557*c8dee2aaSAndroid Build Coastguard Worker         && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2558*c8dee2aaSAndroid Build Coastguard Worker         && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2559*c8dee2aaSAndroid Build Coastguard Worker }
2560*c8dee2aaSAndroid Build Coastguard Worker 
skcms_Transform(const void * src,skcms_PixelFormat srcFmt,skcms_AlphaFormat srcAlpha,const skcms_ICCProfile * srcProfile,void * dst,skcms_PixelFormat dstFmt,skcms_AlphaFormat dstAlpha,const skcms_ICCProfile * dstProfile,size_t nz)2561*c8dee2aaSAndroid Build Coastguard Worker bool skcms_Transform(const void*             src,
2562*c8dee2aaSAndroid Build Coastguard Worker                      skcms_PixelFormat       srcFmt,
2563*c8dee2aaSAndroid Build Coastguard Worker                      skcms_AlphaFormat       srcAlpha,
2564*c8dee2aaSAndroid Build Coastguard Worker                      const skcms_ICCProfile* srcProfile,
2565*c8dee2aaSAndroid Build Coastguard Worker                      void*                   dst,
2566*c8dee2aaSAndroid Build Coastguard Worker                      skcms_PixelFormat       dstFmt,
2567*c8dee2aaSAndroid Build Coastguard Worker                      skcms_AlphaFormat       dstAlpha,
2568*c8dee2aaSAndroid Build Coastguard Worker                      const skcms_ICCProfile* dstProfile,
2569*c8dee2aaSAndroid Build Coastguard Worker                      size_t                  nz) {
2570*c8dee2aaSAndroid Build Coastguard Worker     const size_t dst_bpp = bytes_per_pixel(dstFmt),
2571*c8dee2aaSAndroid Build Coastguard Worker                  src_bpp = bytes_per_pixel(srcFmt);
2572*c8dee2aaSAndroid Build Coastguard Worker     // Let's just refuse if the request is absurdly big.
2573*c8dee2aaSAndroid Build Coastguard Worker     if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2574*c8dee2aaSAndroid Build Coastguard Worker         return false;
2575*c8dee2aaSAndroid Build Coastguard Worker     }
2576*c8dee2aaSAndroid Build Coastguard Worker     int n = (int)nz;
2577*c8dee2aaSAndroid Build Coastguard Worker 
2578*c8dee2aaSAndroid Build Coastguard Worker     // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2579*c8dee2aaSAndroid Build Coastguard Worker     if (!srcProfile) {
2580*c8dee2aaSAndroid Build Coastguard Worker         srcProfile = skcms_sRGB_profile();
2581*c8dee2aaSAndroid Build Coastguard Worker     }
2582*c8dee2aaSAndroid Build Coastguard Worker     if (!dstProfile) {
2583*c8dee2aaSAndroid Build Coastguard Worker         dstProfile = skcms_sRGB_profile();
2584*c8dee2aaSAndroid Build Coastguard Worker     }
2585*c8dee2aaSAndroid Build Coastguard Worker 
2586*c8dee2aaSAndroid Build Coastguard Worker     // We can't transform in place unless the PixelFormats are the same size.
2587*c8dee2aaSAndroid Build Coastguard Worker     if (dst == src && dst_bpp != src_bpp) {
2588*c8dee2aaSAndroid Build Coastguard Worker         return false;
2589*c8dee2aaSAndroid Build Coastguard Worker     }
2590*c8dee2aaSAndroid Build Coastguard Worker     // TODO: more careful alias rejection (like, dst == src + 1)?
2591*c8dee2aaSAndroid Build Coastguard Worker 
2592*c8dee2aaSAndroid Build Coastguard Worker     Op          program[32];
2593*c8dee2aaSAndroid Build Coastguard Worker     const void* context[32];
2594*c8dee2aaSAndroid Build Coastguard Worker 
2595*c8dee2aaSAndroid Build Coastguard Worker     Op*          ops      = program;
2596*c8dee2aaSAndroid Build Coastguard Worker     const void** contexts = context;
2597*c8dee2aaSAndroid Build Coastguard Worker 
2598*c8dee2aaSAndroid Build Coastguard Worker     auto add_op = [&](Op o) {
2599*c8dee2aaSAndroid Build Coastguard Worker         *ops++ = o;
2600*c8dee2aaSAndroid Build Coastguard Worker         *contexts++ = nullptr;
2601*c8dee2aaSAndroid Build Coastguard Worker     };
2602*c8dee2aaSAndroid Build Coastguard Worker 
2603*c8dee2aaSAndroid Build Coastguard Worker     auto add_op_ctx = [&](Op o, const void* c) {
2604*c8dee2aaSAndroid Build Coastguard Worker         *ops++ = o;
2605*c8dee2aaSAndroid Build Coastguard Worker         *contexts++ = c;
2606*c8dee2aaSAndroid Build Coastguard Worker     };
2607*c8dee2aaSAndroid Build Coastguard Worker 
2608*c8dee2aaSAndroid Build Coastguard Worker     auto add_curve_ops = [&](const skcms_Curve* curves, int numChannels) {
2609*c8dee2aaSAndroid Build Coastguard Worker         OpAndArg oa[4];
2610*c8dee2aaSAndroid Build Coastguard Worker         assert(numChannels <= ARRAY_COUNT(oa));
2611*c8dee2aaSAndroid Build Coastguard Worker 
2612*c8dee2aaSAndroid Build Coastguard Worker         int numOps = select_curve_ops(curves, numChannels, oa);
2613*c8dee2aaSAndroid Build Coastguard Worker 
2614*c8dee2aaSAndroid Build Coastguard Worker         for (int i = 0; i < numOps; ++i) {
2615*c8dee2aaSAndroid Build Coastguard Worker             add_op_ctx(oa[i].op, oa[i].arg);
2616*c8dee2aaSAndroid Build Coastguard Worker         }
2617*c8dee2aaSAndroid Build Coastguard Worker     };
2618*c8dee2aaSAndroid Build Coastguard Worker 
2619*c8dee2aaSAndroid Build Coastguard Worker     // These are always parametric curves of some sort.
2620*c8dee2aaSAndroid Build Coastguard Worker     skcms_Curve dst_curves[3];
2621*c8dee2aaSAndroid Build Coastguard Worker     dst_curves[0].table_entries =
2622*c8dee2aaSAndroid Build Coastguard Worker     dst_curves[1].table_entries =
2623*c8dee2aaSAndroid Build Coastguard Worker     dst_curves[2].table_entries = 0;
2624*c8dee2aaSAndroid Build Coastguard Worker 
2625*c8dee2aaSAndroid Build Coastguard Worker     skcms_Matrix3x3        from_xyz;
2626*c8dee2aaSAndroid Build Coastguard Worker 
2627*c8dee2aaSAndroid Build Coastguard Worker     switch (srcFmt >> 1) {
2628*c8dee2aaSAndroid Build Coastguard Worker         default: return false;
2629*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_A_8              >> 1: add_op(Op::load_a8);          break;
2630*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_G_8              >> 1: add_op(Op::load_g8);          break;
2631*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_GA_88            >> 1: add_op(Op::load_ga88);        break;
2632*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_ABGR_4444        >> 1: add_op(Op::load_4444);        break;
2633*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_565          >> 1: add_op(Op::load_565);         break;
2634*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_888          >> 1: add_op(Op::load_888);         break;
2635*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_8888        >> 1: add_op(Op::load_8888);        break;
2636*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_1010102     >> 1: add_op(Op::load_1010102);     break;
2637*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_101010x_XR   >> 1: add_op(Op::load_101010x_XR);  break;
2638*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_10101010_XR >> 1: add_op(Op::load_10101010_XR); break;
2639*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_161616LE     >> 1: add_op(Op::load_161616LE);    break;
2640*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_16161616LE  >> 1: add_op(Op::load_16161616LE);  break;
2641*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_161616BE     >> 1: add_op(Op::load_161616BE);    break;
2642*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_16161616BE  >> 1: add_op(Op::load_16161616BE);  break;
2643*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_hhh_Norm     >> 1: add_op(Op::load_hhh);         break;
2644*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_hhhh_Norm   >> 1: add_op(Op::load_hhhh);        break;
2645*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_hhh          >> 1: add_op(Op::load_hhh);         break;
2646*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_hhhh        >> 1: add_op(Op::load_hhhh);        break;
2647*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_fff          >> 1: add_op(Op::load_fff);         break;
2648*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_ffff        >> 1: add_op(Op::load_ffff);        break;
2649*c8dee2aaSAndroid Build Coastguard Worker 
2650*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
2651*c8dee2aaSAndroid Build Coastguard Worker             add_op(Op::load_8888);
2652*c8dee2aaSAndroid Build Coastguard Worker             add_op_ctx(Op::tf_rgb, skcms_sRGB_TransferFunction());
2653*c8dee2aaSAndroid Build Coastguard Worker             break;
2654*c8dee2aaSAndroid Build Coastguard Worker     }
2655*c8dee2aaSAndroid Build Coastguard Worker     if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2656*c8dee2aaSAndroid Build Coastguard Worker         srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2657*c8dee2aaSAndroid Build Coastguard Worker         add_op(Op::clamp);
2658*c8dee2aaSAndroid Build Coastguard Worker     }
2659*c8dee2aaSAndroid Build Coastguard Worker     if (srcFmt & 1) {
2660*c8dee2aaSAndroid Build Coastguard Worker         add_op(Op::swap_rb);
2661*c8dee2aaSAndroid Build Coastguard Worker     }
2662*c8dee2aaSAndroid Build Coastguard Worker     skcms_ICCProfile gray_dst_profile;
2663*c8dee2aaSAndroid Build Coastguard Worker     switch (dstFmt >> 1) {
2664*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_G_8:
2665*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_GA_88:
2666*c8dee2aaSAndroid Build Coastguard Worker             // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2667*c8dee2aaSAndroid Build Coastguard Worker             // luminance (Y) by the destination transfer function.
2668*c8dee2aaSAndroid Build Coastguard Worker             gray_dst_profile = *dstProfile;
2669*c8dee2aaSAndroid Build Coastguard Worker             skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2670*c8dee2aaSAndroid Build Coastguard Worker             dstProfile = &gray_dst_profile;
2671*c8dee2aaSAndroid Build Coastguard Worker             break;
2672*c8dee2aaSAndroid Build Coastguard Worker         default:
2673*c8dee2aaSAndroid Build Coastguard Worker             break;
2674*c8dee2aaSAndroid Build Coastguard Worker     }
2675*c8dee2aaSAndroid Build Coastguard Worker 
2676*c8dee2aaSAndroid Build Coastguard Worker     if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2677*c8dee2aaSAndroid Build Coastguard Worker         // Photoshop creates CMYK images as inverse CMYK.
2678*c8dee2aaSAndroid Build Coastguard Worker         // These happen to be the only ones we've _ever_ seen.
2679*c8dee2aaSAndroid Build Coastguard Worker         add_op(Op::invert);
2680*c8dee2aaSAndroid Build Coastguard Worker         // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2681*c8dee2aaSAndroid Build Coastguard Worker         srcAlpha = skcms_AlphaFormat_Unpremul;
2682*c8dee2aaSAndroid Build Coastguard Worker     }
2683*c8dee2aaSAndroid Build Coastguard Worker 
2684*c8dee2aaSAndroid Build Coastguard Worker     if (srcAlpha == skcms_AlphaFormat_Opaque) {
2685*c8dee2aaSAndroid Build Coastguard Worker         add_op(Op::force_opaque);
2686*c8dee2aaSAndroid Build Coastguard Worker     } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2687*c8dee2aaSAndroid Build Coastguard Worker         add_op(Op::unpremul);
2688*c8dee2aaSAndroid Build Coastguard Worker     }
2689*c8dee2aaSAndroid Build Coastguard Worker 
2690*c8dee2aaSAndroid Build Coastguard Worker     if (dstProfile != srcProfile) {
2691*c8dee2aaSAndroid Build Coastguard Worker 
2692*c8dee2aaSAndroid Build Coastguard Worker         if (!prep_for_destination(dstProfile,
2693*c8dee2aaSAndroid Build Coastguard Worker                                   &from_xyz,
2694*c8dee2aaSAndroid Build Coastguard Worker                                   &dst_curves[0].parametric,
2695*c8dee2aaSAndroid Build Coastguard Worker                                   &dst_curves[1].parametric,
2696*c8dee2aaSAndroid Build Coastguard Worker                                   &dst_curves[2].parametric)) {
2697*c8dee2aaSAndroid Build Coastguard Worker             return false;
2698*c8dee2aaSAndroid Build Coastguard Worker         }
2699*c8dee2aaSAndroid Build Coastguard Worker 
2700*c8dee2aaSAndroid Build Coastguard Worker         if (srcProfile->has_A2B) {
2701*c8dee2aaSAndroid Build Coastguard Worker             if (srcProfile->A2B.input_channels) {
2702*c8dee2aaSAndroid Build Coastguard Worker                 add_curve_ops(srcProfile->A2B.input_curves,
2703*c8dee2aaSAndroid Build Coastguard Worker                               (int)srcProfile->A2B.input_channels);
2704*c8dee2aaSAndroid Build Coastguard Worker                 add_op(Op::clamp);
2705*c8dee2aaSAndroid Build Coastguard Worker                 add_op_ctx(Op::clut_A2B, &srcProfile->A2B);
2706*c8dee2aaSAndroid Build Coastguard Worker             }
2707*c8dee2aaSAndroid Build Coastguard Worker 
2708*c8dee2aaSAndroid Build Coastguard Worker             if (srcProfile->A2B.matrix_channels == 3) {
2709*c8dee2aaSAndroid Build Coastguard Worker                 add_curve_ops(srcProfile->A2B.matrix_curves, /*numChannels=*/3);
2710*c8dee2aaSAndroid Build Coastguard Worker 
2711*c8dee2aaSAndroid Build Coastguard Worker                 static const skcms_Matrix3x4 I = {{
2712*c8dee2aaSAndroid Build Coastguard Worker                     {1,0,0,0},
2713*c8dee2aaSAndroid Build Coastguard Worker                     {0,1,0,0},
2714*c8dee2aaSAndroid Build Coastguard Worker                     {0,0,1,0},
2715*c8dee2aaSAndroid Build Coastguard Worker                 }};
2716*c8dee2aaSAndroid Build Coastguard Worker                 if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2717*c8dee2aaSAndroid Build Coastguard Worker                     add_op_ctx(Op::matrix_3x4, &srcProfile->A2B.matrix);
2718*c8dee2aaSAndroid Build Coastguard Worker                 }
2719*c8dee2aaSAndroid Build Coastguard Worker             }
2720*c8dee2aaSAndroid Build Coastguard Worker 
2721*c8dee2aaSAndroid Build Coastguard Worker             if (srcProfile->A2B.output_channels == 3) {
2722*c8dee2aaSAndroid Build Coastguard Worker                 add_curve_ops(srcProfile->A2B.output_curves, /*numChannels=*/3);
2723*c8dee2aaSAndroid Build Coastguard Worker             }
2724*c8dee2aaSAndroid Build Coastguard Worker 
2725*c8dee2aaSAndroid Build Coastguard Worker             if (srcProfile->pcs == skcms_Signature_Lab) {
2726*c8dee2aaSAndroid Build Coastguard Worker                 add_op(Op::lab_to_xyz);
2727*c8dee2aaSAndroid Build Coastguard Worker             }
2728*c8dee2aaSAndroid Build Coastguard Worker 
2729*c8dee2aaSAndroid Build Coastguard Worker         } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2730*c8dee2aaSAndroid Build Coastguard Worker             add_curve_ops(srcProfile->trc, /*numChannels=*/3);
2731*c8dee2aaSAndroid Build Coastguard Worker         } else {
2732*c8dee2aaSAndroid Build Coastguard Worker             return false;
2733*c8dee2aaSAndroid Build Coastguard Worker         }
2734*c8dee2aaSAndroid Build Coastguard Worker 
2735*c8dee2aaSAndroid Build Coastguard Worker         // A2B sources are in XYZD50 by now, but TRC sources are still in their original gamut.
2736*c8dee2aaSAndroid Build Coastguard Worker         assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2737*c8dee2aaSAndroid Build Coastguard Worker 
2738*c8dee2aaSAndroid Build Coastguard Worker         if (dstProfile->has_B2A) {
2739*c8dee2aaSAndroid Build Coastguard Worker             // B2A needs its input in XYZD50, so transform TRC sources now.
2740*c8dee2aaSAndroid Build Coastguard Worker             if (!srcProfile->has_A2B) {
2741*c8dee2aaSAndroid Build Coastguard Worker                 add_op_ctx(Op::matrix_3x3, &srcProfile->toXYZD50);
2742*c8dee2aaSAndroid Build Coastguard Worker             }
2743*c8dee2aaSAndroid Build Coastguard Worker 
2744*c8dee2aaSAndroid Build Coastguard Worker             if (dstProfile->pcs == skcms_Signature_Lab) {
2745*c8dee2aaSAndroid Build Coastguard Worker                 add_op(Op::xyz_to_lab);
2746*c8dee2aaSAndroid Build Coastguard Worker             }
2747*c8dee2aaSAndroid Build Coastguard Worker 
2748*c8dee2aaSAndroid Build Coastguard Worker             if (dstProfile->B2A.input_channels == 3) {
2749*c8dee2aaSAndroid Build Coastguard Worker                 add_curve_ops(dstProfile->B2A.input_curves, /*numChannels=*/3);
2750*c8dee2aaSAndroid Build Coastguard Worker             }
2751*c8dee2aaSAndroid Build Coastguard Worker 
2752*c8dee2aaSAndroid Build Coastguard Worker             if (dstProfile->B2A.matrix_channels == 3) {
2753*c8dee2aaSAndroid Build Coastguard Worker                 static const skcms_Matrix3x4 I = {{
2754*c8dee2aaSAndroid Build Coastguard Worker                     {1,0,0,0},
2755*c8dee2aaSAndroid Build Coastguard Worker                     {0,1,0,0},
2756*c8dee2aaSAndroid Build Coastguard Worker                     {0,0,1,0},
2757*c8dee2aaSAndroid Build Coastguard Worker                 }};
2758*c8dee2aaSAndroid Build Coastguard Worker                 if (0 != memcmp(&I, &dstProfile->B2A.matrix, sizeof(I))) {
2759*c8dee2aaSAndroid Build Coastguard Worker                     add_op_ctx(Op::matrix_3x4, &dstProfile->B2A.matrix);
2760*c8dee2aaSAndroid Build Coastguard Worker                 }
2761*c8dee2aaSAndroid Build Coastguard Worker 
2762*c8dee2aaSAndroid Build Coastguard Worker                 add_curve_ops(dstProfile->B2A.matrix_curves, /*numChannels=*/3);
2763*c8dee2aaSAndroid Build Coastguard Worker             }
2764*c8dee2aaSAndroid Build Coastguard Worker 
2765*c8dee2aaSAndroid Build Coastguard Worker             if (dstProfile->B2A.output_channels) {
2766*c8dee2aaSAndroid Build Coastguard Worker                 add_op(Op::clamp);
2767*c8dee2aaSAndroid Build Coastguard Worker                 add_op_ctx(Op::clut_B2A, &dstProfile->B2A);
2768*c8dee2aaSAndroid Build Coastguard Worker 
2769*c8dee2aaSAndroid Build Coastguard Worker                 add_curve_ops(dstProfile->B2A.output_curves,
2770*c8dee2aaSAndroid Build Coastguard Worker                               (int)dstProfile->B2A.output_channels);
2771*c8dee2aaSAndroid Build Coastguard Worker             }
2772*c8dee2aaSAndroid Build Coastguard Worker         } else {
2773*c8dee2aaSAndroid Build Coastguard Worker             // This is a TRC destination.
2774*c8dee2aaSAndroid Build Coastguard Worker             // We'll concat any src->xyz matrix with our xyz->dst matrix into one src->dst matrix.
2775*c8dee2aaSAndroid Build Coastguard Worker             // (A2B sources are already in XYZD50, making that src->xyz matrix I.)
2776*c8dee2aaSAndroid Build Coastguard Worker             static const skcms_Matrix3x3 I = {{
2777*c8dee2aaSAndroid Build Coastguard Worker                 { 1.0f, 0.0f, 0.0f },
2778*c8dee2aaSAndroid Build Coastguard Worker                 { 0.0f, 1.0f, 0.0f },
2779*c8dee2aaSAndroid Build Coastguard Worker                 { 0.0f, 0.0f, 1.0f },
2780*c8dee2aaSAndroid Build Coastguard Worker             }};
2781*c8dee2aaSAndroid Build Coastguard Worker             const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2782*c8dee2aaSAndroid Build Coastguard Worker 
2783*c8dee2aaSAndroid Build Coastguard Worker             // There's a chance the source and destination gamuts are identical,
2784*c8dee2aaSAndroid Build Coastguard Worker             // in which case we can skip the gamut transform.
2785*c8dee2aaSAndroid Build Coastguard Worker             if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2786*c8dee2aaSAndroid Build Coastguard Worker                 // Concat the entire gamut transform into from_xyz,
2787*c8dee2aaSAndroid Build Coastguard Worker                 // now slightly misnamed but it's a handy spot to stash the result.
2788*c8dee2aaSAndroid Build Coastguard Worker                 from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2789*c8dee2aaSAndroid Build Coastguard Worker                 add_op_ctx(Op::matrix_3x3, &from_xyz);
2790*c8dee2aaSAndroid Build Coastguard Worker             }
2791*c8dee2aaSAndroid Build Coastguard Worker 
2792*c8dee2aaSAndroid Build Coastguard Worker             // Encode back to dst RGB using its parametric transfer functions.
2793*c8dee2aaSAndroid Build Coastguard Worker             OpAndArg oa[3];
2794*c8dee2aaSAndroid Build Coastguard Worker             int numOps = select_curve_ops(dst_curves, /*numChannels=*/3, oa);
2795*c8dee2aaSAndroid Build Coastguard Worker             for (int index = 0; index < numOps; ++index) {
2796*c8dee2aaSAndroid Build Coastguard Worker                 assert(oa[index].op != Op::table_r &&
2797*c8dee2aaSAndroid Build Coastguard Worker                        oa[index].op != Op::table_g &&
2798*c8dee2aaSAndroid Build Coastguard Worker                        oa[index].op != Op::table_b &&
2799*c8dee2aaSAndroid Build Coastguard Worker                        oa[index].op != Op::table_a);
2800*c8dee2aaSAndroid Build Coastguard Worker                 add_op_ctx(oa[index].op, oa[index].arg);
2801*c8dee2aaSAndroid Build Coastguard Worker             }
2802*c8dee2aaSAndroid Build Coastguard Worker         }
2803*c8dee2aaSAndroid Build Coastguard Worker     }
2804*c8dee2aaSAndroid Build Coastguard Worker 
2805*c8dee2aaSAndroid Build Coastguard Worker     // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
2806*c8dee2aaSAndroid Build Coastguard Worker     // not just to values that fit in [0,1].
2807*c8dee2aaSAndroid Build Coastguard Worker     //
2808*c8dee2aaSAndroid Build Coastguard Worker     // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
2809*c8dee2aaSAndroid Build Coastguard Worker     // but would be carrying r > 1, which is really unexpected for downstream consumers.
2810*c8dee2aaSAndroid Build Coastguard Worker     if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2811*c8dee2aaSAndroid Build Coastguard Worker         add_op(Op::clamp);
2812*c8dee2aaSAndroid Build Coastguard Worker     }
2813*c8dee2aaSAndroid Build Coastguard Worker 
2814*c8dee2aaSAndroid Build Coastguard Worker     if (dstProfile->data_color_space == skcms_Signature_CMYK) {
2815*c8dee2aaSAndroid Build Coastguard Worker         // Photoshop creates CMYK images as inverse CMYK.
2816*c8dee2aaSAndroid Build Coastguard Worker         // These happen to be the only ones we've _ever_ seen.
2817*c8dee2aaSAndroid Build Coastguard Worker         add_op(Op::invert);
2818*c8dee2aaSAndroid Build Coastguard Worker 
2819*c8dee2aaSAndroid Build Coastguard Worker         // CMYK has no alpha channel, so make sure dstAlpha is a no-op.
2820*c8dee2aaSAndroid Build Coastguard Worker         dstAlpha = skcms_AlphaFormat_Unpremul;
2821*c8dee2aaSAndroid Build Coastguard Worker     }
2822*c8dee2aaSAndroid Build Coastguard Worker 
2823*c8dee2aaSAndroid Build Coastguard Worker     if (dstAlpha == skcms_AlphaFormat_Opaque) {
2824*c8dee2aaSAndroid Build Coastguard Worker         add_op(Op::force_opaque);
2825*c8dee2aaSAndroid Build Coastguard Worker     } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2826*c8dee2aaSAndroid Build Coastguard Worker         add_op(Op::premul);
2827*c8dee2aaSAndroid Build Coastguard Worker     }
2828*c8dee2aaSAndroid Build Coastguard Worker     if (dstFmt & 1) {
2829*c8dee2aaSAndroid Build Coastguard Worker         add_op(Op::swap_rb);
2830*c8dee2aaSAndroid Build Coastguard Worker     }
2831*c8dee2aaSAndroid Build Coastguard Worker     switch (dstFmt >> 1) {
2832*c8dee2aaSAndroid Build Coastguard Worker         default: return false;
2833*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_A_8             >> 1: add_op(Op::store_a8);         break;
2834*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_G_8             >> 1: add_op(Op::store_g8);         break;
2835*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_GA_88           >> 1: add_op(Op::store_ga88);       break;
2836*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_ABGR_4444       >> 1: add_op(Op::store_4444);       break;
2837*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_565         >> 1: add_op(Op::store_565);        break;
2838*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_888         >> 1: add_op(Op::store_888);        break;
2839*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_8888       >> 1: add_op(Op::store_8888);       break;
2840*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_1010102    >> 1: add_op(Op::store_1010102);    break;
2841*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_161616LE    >> 1: add_op(Op::store_161616LE);   break;
2842*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_16161616LE >> 1: add_op(Op::store_16161616LE); break;
2843*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_161616BE    >> 1: add_op(Op::store_161616BE);   break;
2844*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_16161616BE >> 1: add_op(Op::store_16161616BE); break;
2845*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_hhh_Norm    >> 1: add_op(Op::store_hhh);        break;
2846*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_hhhh_Norm  >> 1: add_op(Op::store_hhhh);       break;
2847*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_101010x_XR  >> 1: add_op(Op::store_101010x_XR); break;
2848*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_hhh         >> 1: add_op(Op::store_hhh);        break;
2849*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_hhhh       >> 1: add_op(Op::store_hhhh);       break;
2850*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGB_fff         >> 1: add_op(Op::store_fff);        break;
2851*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_ffff       >> 1: add_op(Op::store_ffff);       break;
2852*c8dee2aaSAndroid Build Coastguard Worker 
2853*c8dee2aaSAndroid Build Coastguard Worker         case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
2854*c8dee2aaSAndroid Build Coastguard Worker             add_op_ctx(Op::tf_rgb, skcms_sRGB_Inverse_TransferFunction());
2855*c8dee2aaSAndroid Build Coastguard Worker             add_op(Op::store_8888);
2856*c8dee2aaSAndroid Build Coastguard Worker             break;
2857*c8dee2aaSAndroid Build Coastguard Worker     }
2858*c8dee2aaSAndroid Build Coastguard Worker 
2859*c8dee2aaSAndroid Build Coastguard Worker     assert(ops      <= program + ARRAY_COUNT(program));
2860*c8dee2aaSAndroid Build Coastguard Worker     assert(contexts <= context + ARRAY_COUNT(context));
2861*c8dee2aaSAndroid Build Coastguard Worker 
2862*c8dee2aaSAndroid Build Coastguard Worker     auto run = baseline::run_program;
2863*c8dee2aaSAndroid Build Coastguard Worker     switch (cpu_type()) {
2864*c8dee2aaSAndroid Build Coastguard Worker         case CpuType::SKX:
2865*c8dee2aaSAndroid Build Coastguard Worker             #if !defined(SKCMS_DISABLE_SKX)
2866*c8dee2aaSAndroid Build Coastguard Worker                 run = skx::run_program;
2867*c8dee2aaSAndroid Build Coastguard Worker                 break;
2868*c8dee2aaSAndroid Build Coastguard Worker             #endif
2869*c8dee2aaSAndroid Build Coastguard Worker 
2870*c8dee2aaSAndroid Build Coastguard Worker         case CpuType::HSW:
2871*c8dee2aaSAndroid Build Coastguard Worker             #if !defined(SKCMS_DISABLE_HSW)
2872*c8dee2aaSAndroid Build Coastguard Worker                 run = hsw::run_program;
2873*c8dee2aaSAndroid Build Coastguard Worker                 break;
2874*c8dee2aaSAndroid Build Coastguard Worker             #endif
2875*c8dee2aaSAndroid Build Coastguard Worker 
2876*c8dee2aaSAndroid Build Coastguard Worker         case CpuType::Baseline:
2877*c8dee2aaSAndroid Build Coastguard Worker             break;
2878*c8dee2aaSAndroid Build Coastguard Worker     }
2879*c8dee2aaSAndroid Build Coastguard Worker 
2880*c8dee2aaSAndroid Build Coastguard Worker     run(program, context, ops - program, (const char*)src, (char*)dst, n, src_bpp,dst_bpp);
2881*c8dee2aaSAndroid Build Coastguard Worker     return true;
2882*c8dee2aaSAndroid Build Coastguard Worker }
2883*c8dee2aaSAndroid Build Coastguard Worker 
assert_usable_as_destination(const skcms_ICCProfile * profile)2884*c8dee2aaSAndroid Build Coastguard Worker static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2885*c8dee2aaSAndroid Build Coastguard Worker #if defined(NDEBUG)
2886*c8dee2aaSAndroid Build Coastguard Worker     (void)profile;
2887*c8dee2aaSAndroid Build Coastguard Worker #else
2888*c8dee2aaSAndroid Build Coastguard Worker     skcms_Matrix3x3 fromXYZD50;
2889*c8dee2aaSAndroid Build Coastguard Worker     skcms_TransferFunction invR, invG, invB;
2890*c8dee2aaSAndroid Build Coastguard Worker     assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2891*c8dee2aaSAndroid Build Coastguard Worker #endif
2892*c8dee2aaSAndroid Build Coastguard Worker }
2893*c8dee2aaSAndroid Build Coastguard Worker 
skcms_MakeUsableAsDestination(skcms_ICCProfile * profile)2894*c8dee2aaSAndroid Build Coastguard Worker bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2895*c8dee2aaSAndroid Build Coastguard Worker     if (!profile->has_B2A) {
2896*c8dee2aaSAndroid Build Coastguard Worker         skcms_Matrix3x3 fromXYZD50;
2897*c8dee2aaSAndroid Build Coastguard Worker         if (!profile->has_trc || !profile->has_toXYZD50
2898*c8dee2aaSAndroid Build Coastguard Worker             || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2899*c8dee2aaSAndroid Build Coastguard Worker             return false;
2900*c8dee2aaSAndroid Build Coastguard Worker         }
2901*c8dee2aaSAndroid Build Coastguard Worker 
2902*c8dee2aaSAndroid Build Coastguard Worker         skcms_TransferFunction tf[3];
2903*c8dee2aaSAndroid Build Coastguard Worker         for (int i = 0; i < 3; i++) {
2904*c8dee2aaSAndroid Build Coastguard Worker             skcms_TransferFunction inv;
2905*c8dee2aaSAndroid Build Coastguard Worker             if (profile->trc[i].table_entries == 0
2906*c8dee2aaSAndroid Build Coastguard Worker                 && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2907*c8dee2aaSAndroid Build Coastguard Worker                 tf[i] = profile->trc[i].parametric;
2908*c8dee2aaSAndroid Build Coastguard Worker                 continue;
2909*c8dee2aaSAndroid Build Coastguard Worker             }
2910*c8dee2aaSAndroid Build Coastguard Worker 
2911*c8dee2aaSAndroid Build Coastguard Worker             float max_error;
2912*c8dee2aaSAndroid Build Coastguard Worker             // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2913*c8dee2aaSAndroid Build Coastguard Worker             if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2914*c8dee2aaSAndroid Build Coastguard Worker                 return false;
2915*c8dee2aaSAndroid Build Coastguard Worker             }
2916*c8dee2aaSAndroid Build Coastguard Worker         }
2917*c8dee2aaSAndroid Build Coastguard Worker 
2918*c8dee2aaSAndroid Build Coastguard Worker         for (int i = 0; i < 3; ++i) {
2919*c8dee2aaSAndroid Build Coastguard Worker             profile->trc[i].table_entries = 0;
2920*c8dee2aaSAndroid Build Coastguard Worker             profile->trc[i].parametric = tf[i];
2921*c8dee2aaSAndroid Build Coastguard Worker         }
2922*c8dee2aaSAndroid Build Coastguard Worker     }
2923*c8dee2aaSAndroid Build Coastguard Worker     assert_usable_as_destination(profile);
2924*c8dee2aaSAndroid Build Coastguard Worker     return true;
2925*c8dee2aaSAndroid Build Coastguard Worker }
2926*c8dee2aaSAndroid Build Coastguard Worker 
skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile * profile)2927*c8dee2aaSAndroid Build Coastguard Worker bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2928*c8dee2aaSAndroid Build Coastguard Worker     // Call skcms_MakeUsableAsDestination() with B2A disabled;
2929*c8dee2aaSAndroid Build Coastguard Worker     // on success that'll return a TRC/XYZ profile with three skcms_TransferFunctions.
2930*c8dee2aaSAndroid Build Coastguard Worker     skcms_ICCProfile result = *profile;
2931*c8dee2aaSAndroid Build Coastguard Worker     result.has_B2A = false;
2932*c8dee2aaSAndroid Build Coastguard Worker     if (!skcms_MakeUsableAsDestination(&result)) {
2933*c8dee2aaSAndroid Build Coastguard Worker         return false;
2934*c8dee2aaSAndroid Build Coastguard Worker     }
2935*c8dee2aaSAndroid Build Coastguard Worker 
2936*c8dee2aaSAndroid Build Coastguard Worker     // Of the three, pick the transfer function that best fits the other two.
2937*c8dee2aaSAndroid Build Coastguard Worker     int best_tf = 0;
2938*c8dee2aaSAndroid Build Coastguard Worker     float min_max_error = INFINITY_;
2939*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 0; i < 3; i++) {
2940*c8dee2aaSAndroid Build Coastguard Worker         skcms_TransferFunction inv;
2941*c8dee2aaSAndroid Build Coastguard Worker         if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
2942*c8dee2aaSAndroid Build Coastguard Worker             return false;
2943*c8dee2aaSAndroid Build Coastguard Worker         }
2944*c8dee2aaSAndroid Build Coastguard Worker 
2945*c8dee2aaSAndroid Build Coastguard Worker         float err = 0;
2946*c8dee2aaSAndroid Build Coastguard Worker         for (int j = 0; j < 3; ++j) {
2947*c8dee2aaSAndroid Build Coastguard Worker             err = fmaxf_(err, skcms_MaxRoundtripError(&profile->trc[j], &inv));
2948*c8dee2aaSAndroid Build Coastguard Worker         }
2949*c8dee2aaSAndroid Build Coastguard Worker         if (min_max_error > err) {
2950*c8dee2aaSAndroid Build Coastguard Worker             min_max_error = err;
2951*c8dee2aaSAndroid Build Coastguard Worker             best_tf = i;
2952*c8dee2aaSAndroid Build Coastguard Worker         }
2953*c8dee2aaSAndroid Build Coastguard Worker     }
2954*c8dee2aaSAndroid Build Coastguard Worker 
2955*c8dee2aaSAndroid Build Coastguard Worker     for (int i = 0; i < 3; i++) {
2956*c8dee2aaSAndroid Build Coastguard Worker         result.trc[i].parametric = result.trc[best_tf].parametric;
2957*c8dee2aaSAndroid Build Coastguard Worker     }
2958*c8dee2aaSAndroid Build Coastguard Worker 
2959*c8dee2aaSAndroid Build Coastguard Worker     *profile = result;
2960*c8dee2aaSAndroid Build Coastguard Worker     assert_usable_as_destination(profile);
2961*c8dee2aaSAndroid Build Coastguard Worker     return true;
2962*c8dee2aaSAndroid Build Coastguard Worker }
2963