Coverage Report

Created: 2026-05-30 06:47

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