Coverage Report

Created: 2026-08-31 07:15

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
1.10k
#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
32.3M
static float log2f_(float x) {
51
    // The first approximation of log2(x) is its exponent 'e', minus 127.
52
32.3M
    int32_t bits;
53
32.3M
    memcpy(&bits, &x, sizeof(bits));
54
55
32.3M
    float e = (float)bits * (1.0f / (1<<23));
56
57
    // If we use the mantissa too we can refine the error signficantly.
58
32.3M
    int32_t m_bits = (bits & 0x007fffff) | 0x3f000000;
59
32.3M
    float m;
60
32.3M
    memcpy(&m, &m_bits, sizeof(m));
61
62
32.3M
    return (e - 124.225514990f
63
32.3M
              -   1.498030302f*m
64
32.3M
              -   1.725879990f/(0.3520887068f + m));
65
32.3M
}
66
13.7M
static float logf_(float x) {
67
13.7M
    const float ln2 = 0.69314718f;
68
13.7M
    return ln2*log2f_(x);
69
13.7M
}
70
71
18.6M
static float exp2f_(float x) {
72
18.6M
    if (x > 128.0f) {
73
31.5k
        return INFINITY_;
74
18.5M
    } else if (x < -127.0f) {
75
655k
        return 0.0f;
76
655k
    }
77
17.9M
    float fract = x - floorf_(x);
78
79
17.9M
    float fbits = (1.0f * (1<<23)) * (x + 121.274057500f
80
17.9M
                                        -   1.490129070f*fract
81
17.9M
                                        +  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
17.9M
    if (fbits >= (float)INT_MAX) {
88
0
        return INFINITY_;
89
17.9M
    } else if (fbits < 0) {
90
45.8k
        return 0;
91
45.8k
    }
92
93
17.8M
    int32_t bits = (int32_t)fbits;
94
17.8M
    memcpy(&x, &bits, sizeof(x));
95
17.8M
    return x;
96
17.9M
}
97
98
// Not static, as it's used by some test tools.
99
58.5M
float powf_(float x, float y) {
100
58.5M
    if (x <= 0.f) {
101
39.7M
        return 0.f;
102
39.7M
    }
103
18.8M
    if (x == 1.f) {
104
183k
        return 1.f;
105
183k
    }
106
18.6M
    return exp2f_(log2f_(x) * y);
107
18.8M
}
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
30.5M
static float fmaxf_(float x, float y) { return x > y ? x : y; }
115
15.3M
static float fminf_(float x, float y) { return x < y ? x : y; }
116
117
8.94M
static bool isfinitef_(float x) { return 0 == x*0; }
118
119
15.2M
static float minus_1_ulp(float x) {
120
15.2M
    int32_t bits;
121
15.2M
    memcpy(&bits, &x, sizeof(bits));
122
15.2M
    bits = bits - 1;
123
15.2M
    memcpy(&x, &bits, sizeof(bits));
124
15.2M
    return x;
125
15.2M
}
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
654
static float TFKind_marker(skcms_TFType kind) {
137
    // We'd use different NaNs, but those aren't guaranteed to be preserved by WASM.
138
654
    return -(float)kind;
139
654
}
140
141
static skcms_TFType classify(const skcms_TransferFunction& tf, TF_PQish*   pq = nullptr
142
8.52M
                                                             , TF_HLGish* hlg = nullptr) {
143
8.52M
    if (tf.g < 0) {
144
        // Negative "g" is mapped to enum values; large negative are for sure invalid.
145
3.63k
        if (tf.g < -128) {
146
295
            return skcms_TFType_Invalid;
147
295
        }
148
3.33k
        int enum_g = -static_cast<int>(tf.g);
149
        // Non-whole "g" values are invalid as well.
150
3.33k
        if (static_cast<float>(-enum_g) != tf.g) {
151
1.19k
            return skcms_TFType_Invalid;
152
1.19k
        }
153
        // TODO: soundness checks for PQ/HLG like we do for sRGBish?
154
2.14k
        switch (enum_g) {
155
569
            case skcms_TFType_PQish:
156
569
                if (pq) {
157
259
                    memcpy(pq , &tf.a, sizeof(*pq ));
158
259
                }
159
569
                return skcms_TFType_PQish;
160
339
            case skcms_TFType_HLGish:
161
339
                if (hlg) {
162
196
                    memcpy(hlg, &tf.a, sizeof(*hlg));
163
196
                }
164
339
                return skcms_TFType_HLGish;
165
303
            case skcms_TFType_HLGinvish:
166
303
                if (hlg) {
167
107
                    memcpy(hlg, &tf.a, sizeof(*hlg));
168
107
                }
169
303
                return skcms_TFType_HLGinvish;
170
419
            case skcms_TFType_PQ:
171
419
                if (tf.b != 0.f || tf.c != 0.f || tf.d != 0.f || tf.e != 0.f || tf.f != 0.f) {
172
337
                    return skcms_TFType_Invalid;
173
337
                }
174
82
                return skcms_TFType_PQ;
175
295
            case skcms_TFType_HLG:
176
295
                if (tf.d != 0.f || tf.e != 0.f || tf.f != 0.f) {
177
201
                    return skcms_TFType_Invalid;
178
201
                }
179
94
                return skcms_TFType_HLG;
180
2.14k
        }
181
219
        return skcms_TFType_Invalid;
182
2.14k
    }
183
184
    // Basic soundness checks for sRGBish transfer functions.
185
8.51M
    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
8.51M
            && tf.a >= 0
188
8.51M
            && tf.c >= 0
189
8.51M
            && tf.d >= 0
190
8.51M
            && tf.g >= 0
191
            // Raising a negative value to a fractional tf->g produces complex numbers.
192
8.51M
            && tf.a * tf.d + tf.b >= 0) {
193
8.51M
        return skcms_TFType_sRGBish;
194
8.51M
    }
195
196
604
    return skcms_TFType_Invalid;
197
8.51M
}
198
199
0
skcms_TFType skcms_TransferFunction_getType(const skcms_TransferFunction* tf) {
200
0
    return classify(*tf);
201
0
}
202
2.95k
bool skcms_TransferFunction_isSRGBish(const skcms_TransferFunction* tf) {
203
2.95k
    return classify(*tf) == skcms_TFType_sRGBish;
204
2.95k
}
205
56
bool skcms_TransferFunction_isPQish(const skcms_TransferFunction* tf) {
206
56
    return classify(*tf) == skcms_TFType_PQish;
207
56
}
208
36
bool skcms_TransferFunction_isHLGish(const skcms_TransferFunction* tf) {
209
36
    return classify(*tf) == skcms_TFType_HLGish;
210
36
}
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
56
                                      float D, float E, float F) {
221
56
    *tf = { TFKind_marker(skcms_TFType_PQish), A,B,C,D,E,F };
222
56
    assert(skcms_TransferFunction_isPQish(tf));
223
56
    return true;
224
56
}
225
226
bool skcms_TransferFunction_makeScaledHLGish(skcms_TransferFunction* tf,
227
                                             float K, float R, float G,
228
36
                                             float a, float b, float c) {
229
36
    *tf = { TFKind_marker(skcms_TFType_HLGish), R,G, a,b,c, K-1.0f };
230
36
    assert(skcms_TransferFunction_isHLGish(tf));
231
36
    return true;
232
36
}
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
8.38M
float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
257
8.38M
    float sign = x < 0 ? -1.0f : 1.0f;
258
8.38M
    x *= sign;
259
260
8.38M
    TF_PQish  pq;
261
8.38M
    TF_HLGish hlg;
262
8.38M
    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
8.38M
        case skcms_TFType_sRGBish:
287
8.38M
            return sign * (x < tf->d ?       tf->c * x + tf->f
288
8.38M
                                     : 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
8.38M
    }
304
0
    return 0;
305
8.38M
}
306
307
308
15.2M
static float eval_curve(const skcms_Curve* curve, float x) {
309
15.2M
    if (curve->table_entries == 0) {
310
0
        return skcms_TransferFunction_eval(&curve->parametric, x);
311
0
    }
312
313
15.2M
    float ix = fmaxf_(0, fminf_(x, 1)) * static_cast<float>(curve->table_entries - 1);
314
15.2M
    int   lo = (int)                   ix        ,
315
15.2M
          hi = (int)(float)minus_1_ulp(ix + 1.0f);
316
15.2M
    float t = ix - (float)lo;
317
318
15.2M
    float l, h;
319
15.2M
    if (curve->table_8) {
320
12.3k
        l = curve->table_8[lo] * (1/255.0f);
321
12.3k
        h = curve->table_8[hi] * (1/255.0f);
322
15.2M
    } else {
323
15.2M
        uint16_t be_l, be_h;
324
15.2M
        memcpy(&be_l, curve->table_16 + 2*lo, 2);
325
15.2M
        memcpy(&be_h, curve->table_16 + 2*hi, 2);
326
15.2M
        uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
327
15.2M
        uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
328
15.2M
        l = le_l * (1/65535.0f);
329
15.2M
        h = le_h * (1/65535.0f);
330
15.2M
    }
331
15.2M
    return l + (h-l)*t;
332
15.2M
}
333
334
6.77k
float skcms_MaxRoundtripError(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
335
6.77k
    uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
336
6.77k
    const float dx = 1.0f / static_cast<float>(N - 1);
337
6.77k
    float err = 0;
338
8.36M
    for (uint32_t i = 0; i < N; i++) {
339
8.35M
        float x = static_cast<float>(i) * dx,
340
8.35M
              y = eval_curve(curve, x);
341
8.35M
        err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
342
8.35M
    }
343
6.77k
    return err;
344
6.77k
}
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.85k
static uint16_t read_big_u16(const uint8_t* ptr) {
386
4.85k
    uint16_t be;
387
4.85k
    memcpy(&be, ptr, sizeof(be));
388
#if defined(_MSC_VER)
389
    return _byteswap_ushort(be);
390
#else
391
4.85k
    return __builtin_bswap16(be);
392
4.85k
#endif
393
4.85k
}
394
395
285k
static uint32_t read_big_u32(const uint8_t* ptr) {
396
285k
    uint32_t be;
397
285k
    memcpy(&be, ptr, sizeof(be));
398
#if defined(_MSC_VER)
399
    return _byteswap_ulong(be);
400
#else
401
285k
    return __builtin_bswap32(be);
402
285k
#endif
403
285k
}
404
405
35.9k
static int32_t read_big_i32(const uint8_t* ptr) {
406
35.9k
    return (int32_t)read_big_u32(ptr);
407
35.9k
}
408
409
35.9k
static float read_big_fixed(const uint8_t* ptr) {
410
35.9k
    return static_cast<float>(read_big_i32(ptr)) * (1.0f / 65536.0f);
411
35.9k
}
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
31.8k
static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
446
31.8k
    return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
447
31.8k
}
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.48k
static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
487
3.48k
    if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
488
162
        return false;
489
162
    }
490
491
3.32k
    const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
492
493
3.32k
    *x = read_big_fixed(xyzTag->X);
494
3.32k
    *y = read_big_fixed(xyzTag->Y);
495
3.32k
    *z = read_big_fixed(xyzTag->Z);
496
3.32k
    return true;
497
3.48k
}
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.21k
                           const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
570
1.21k
    return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
571
1.16k
           read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
572
1.10k
           read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
573
1.21k
}
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.98k
                            skcms_Curve* curve, uint32_t* curve_size) {
585
2.98k
    if (size < SAFE_FIXED_SIZE(para_Layout)) {
586
3
        return false;
587
3
    }
588
589
2.97k
    const para_Layout* paraTag = (const para_Layout*)buf;
590
591
2.97k
    enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
592
2.97k
    uint16_t function_type = read_big_u16(paraTag->function_type);
593
2.97k
    if (function_type > kGABCDEF) {
594
13
        return false;
595
13
    }
596
597
2.96k
    static const uint32_t curve_bytes[] = { 4, 12, 16, 20, 28 };
598
2.96k
    if (size < SAFE_FIXED_SIZE(para_Layout) + curve_bytes[function_type]) {
599
5
        return false;
600
5
    }
601
602
2.96k
    if (curve_size) {
603
2.77k
        *curve_size = SAFE_FIXED_SIZE(para_Layout) + curve_bytes[function_type];
604
2.77k
    }
605
606
2.96k
    curve->table_entries = 0;
607
2.96k
    curve->parametric.a  = 1.0f;
608
2.96k
    curve->parametric.b  = 0.0f;
609
2.96k
    curve->parametric.c  = 0.0f;
610
2.96k
    curve->parametric.d  = 0.0f;
611
2.96k
    curve->parametric.e  = 0.0f;
612
2.96k
    curve->parametric.f  = 0.0f;
613
2.96k
    curve->parametric.g  = read_big_fixed(paraTag->variable);
614
615
2.96k
    switch (function_type) {
616
117
        case kGAB:
617
117
            curve->parametric.a = read_big_fixed(paraTag->variable + 4);
618
117
            curve->parametric.b = read_big_fixed(paraTag->variable + 8);
619
117
            if (curve->parametric.a == 0) {
620
1
                return false;
621
1
            }
622
116
            curve->parametric.d = -curve->parametric.b / curve->parametric.a;
623
116
            break;
624
67
        case kGABC:
625
67
            curve->parametric.a = read_big_fixed(paraTag->variable + 4);
626
67
            curve->parametric.b = read_big_fixed(paraTag->variable + 8);
627
67
            curve->parametric.e = read_big_fixed(paraTag->variable + 12);
628
67
            if (curve->parametric.a == 0) {
629
1
                return false;
630
1
            }
631
66
            curve->parametric.d = -curve->parametric.b / curve->parametric.a;
632
66
            curve->parametric.f = curve->parametric.e;
633
66
            break;
634
172
        case kGABCD:
635
172
            curve->parametric.a = read_big_fixed(paraTag->variable + 4);
636
172
            curve->parametric.b = read_big_fixed(paraTag->variable + 8);
637
172
            curve->parametric.c = read_big_fixed(paraTag->variable + 12);
638
172
            curve->parametric.d = read_big_fixed(paraTag->variable + 16);
639
172
            break;
640
103
        case kGABCDEF:
641
103
            curve->parametric.a = read_big_fixed(paraTag->variable + 4);
642
103
            curve->parametric.b = read_big_fixed(paraTag->variable + 8);
643
103
            curve->parametric.c = read_big_fixed(paraTag->variable + 12);
644
103
            curve->parametric.d = read_big_fixed(paraTag->variable + 16);
645
103
            curve->parametric.e = read_big_fixed(paraTag->variable + 20);
646
103
            curve->parametric.f = read_big_fixed(paraTag->variable + 24);
647
103
            break;
648
2.96k
    }
649
2.95k
    return skcms_TransferFunction_isSRGBish(&curve->parametric);
650
2.96k
}
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.59k
                            skcms_Curve* curve, uint32_t* curve_size) {
664
9.59k
    if (size < SAFE_FIXED_SIZE(curv_Layout)) {
665
4
        return false;
666
4
    }
667
668
9.58k
    const curv_Layout* curvTag = (const curv_Layout*)buf;
669
670
9.58k
    uint32_t value_count = read_big_u32(curvTag->value_count);
671
9.58k
    if (size < SAFE_FIXED_SIZE(curv_Layout) + value_count * SAFE_SIZEOF(uint16_t)) {
672
53
        return false;
673
53
    }
674
675
9.53k
    if (curve_size) {
676
6.07k
        *curve_size = SAFE_FIXED_SIZE(curv_Layout) + value_count * SAFE_SIZEOF(uint16_t);
677
6.07k
    }
678
679
9.53k
    if (value_count < 2) {
680
2.44k
        curve->table_entries = 0;
681
2.44k
        curve->parametric.a  = 1.0f;
682
2.44k
        curve->parametric.b  = 0.0f;
683
2.44k
        curve->parametric.c  = 0.0f;
684
2.44k
        curve->parametric.d  = 0.0f;
685
2.44k
        curve->parametric.e  = 0.0f;
686
2.44k
        curve->parametric.f  = 0.0f;
687
2.44k
        if (value_count == 0) {
688
            // Empty tables are a shorthand for an identity curve
689
1.30k
            curve->parametric.g = 1.0f;
690
1.30k
        } else {
691
            // Single entry tables are a shorthand for simple gamma
692
1.14k
            curve->parametric.g = read_big_u16(curvTag->variable) * (1.0f / 256.0f);
693
1.14k
        }
694
2.44k
        return true;
695
2.44k
    }
696
7.08k
    if (value_count > kMaxTableEntries) {
697
0
        return false;
698
0
    }
699
7.08k
    curve->table_8       = nullptr;
700
7.08k
    curve->table_16      = curvTag->variable;
701
7.08k
    curve->table_entries = value_count;
702
7.08k
    return true;
703
7.08k
}
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.7k
                       skcms_Curve* curve, uint32_t* curve_size) {
709
12.7k
    if (!buf || size < 4 || !curve) {
710
10
        return false;
711
10
    }
712
713
12.7k
    uint32_t type = read_big_u32(buf);
714
12.7k
    if (type == skcms_Signature_para) {
715
2.98k
        return read_curve_para(buf, size, curve, curve_size);
716
9.73k
    } else if (type == skcms_Signature_curv) {
717
9.59k
        return read_curve_curv(buf, size, curve, curve_size);
718
9.59k
    }
719
720
143
    return false;
721
12.7k
}
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
342
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
342
    a2b->matrix_channels = 0;
754
342
    a2b-> input_channels = mftTag-> input_channels[0];
755
342
    a2b->output_channels = mftTag->output_channels[0];
756
757
    // We require exactly three (ie XYZ/Lab/RGB) output channels
758
342
    if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
759
17
        return false;
760
17
    }
761
    // We require at least one, and no more than four (ie CMYK) input channels
762
325
    if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
763
19
        return false;
764
19
    }
765
766
306
    uint64_t total_grid_points = 1;
767
1.16k
    for (uint32_t i = 0; i < a2b->input_channels; ++i) {
768
862
        a2b->grid_points[i] = mftTag->grid_points[0];
769
862
        total_grid_points *= a2b->grid_points[i];
770
862
    }
771
    // The grid only makes sense with at least two points along each axis
772
306
    if (a2b->grid_points[0] < 2 || total_grid_points > SKCMS_MAX_GRID_POINTS) {
773
11
        return false;
774
11
    }
775
295
    return true;
776
306
}
777
778
// All as the A2B version above, except where noted.
779
281
static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_B2A* b2a) {
780
    // Same as A2B.
781
281
    b2a->matrix_channels = 0;
782
281
    b2a-> input_channels = mftTag-> input_channels[0];
783
281
    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
281
    if (b2a->input_channels != ARRAY_COUNT(b2a->input_curves)) {
788
29
        return false;
789
29
    }
790
252
    if (b2a->output_channels < 3 || b2a->output_channels > ARRAY_COUNT(b2a->output_curves)) {
791
24
        return false;
792
24
    }
793
794
    // Same as A2B.
795
228
    uint64_t total_grid_points = 1;
796
912
    for (uint32_t i = 0; i < b2a->input_channels; ++i) {
797
684
        b2a->grid_points[i] = mftTag->grid_points[0];
798
684
        total_grid_points *= b2a->grid_points[i];
799
684
    }
800
228
    if (b2a->grid_points[0] < 2 || total_grid_points > SKCMS_MAX_GRID_POINTS) {
801
7
        return false;
802
7
    }
803
221
    return true;
804
228
}
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
452
                        A2B_or_B2A* out) {
810
    // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
811
452
    uint32_t byte_len_per_input_table  = input_table_entries * byte_width;
812
452
    uint32_t byte_len_per_output_table = output_table_entries * byte_width;
813
814
    // [input|output]_channels are <= 4, so still no overflow
815
452
    uint32_t byte_len_all_input_tables  = out->input_channels * byte_len_per_input_table;
816
452
    uint32_t byte_len_all_output_tables = out->output_channels * byte_len_per_output_table;
817
818
452
    uint64_t grid_size = out->output_channels * byte_width;
819
1.74k
    for (uint32_t axis = 0; axis < out->input_channels; ++axis) {
820
1.29k
        grid_size *= out->grid_points[axis];
821
1.29k
    }
822
823
452
    if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
824
133
        return false;
825
133
    }
826
827
1.19k
    for (uint32_t i = 0; i < out->input_channels; ++i) {
828
877
        out->input_curves[i].table_entries = input_table_entries;
829
877
        if (byte_width == 1) {
830
237
            out->input_curves[i].table_8  = table_base + i * byte_len_per_input_table;
831
237
            out->input_curves[i].table_16 = nullptr;
832
640
        } else {
833
640
            out->input_curves[i].table_8  = nullptr;
834
640
            out->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
835
640
        }
836
877
    }
837
838
319
    if (byte_width == 1) {
839
88
        out->grid_8  = table_base + byte_len_all_input_tables;
840
88
        out->grid_16 = nullptr;
841
231
    } else {
842
231
        out->grid_8  = nullptr;
843
231
        out->grid_16 = table_base + byte_len_all_input_tables;
844
231
    }
845
846
319
    const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
847
1.29k
    for (uint32_t i = 0; i < out->output_channels; ++i) {
848
976
        out->output_curves[i].table_entries = output_table_entries;
849
976
        if (byte_width == 1) {
850
272
            out->output_curves[i].table_8  = output_table_base + i * byte_len_per_output_table;
851
272
            out->output_curves[i].table_16 = nullptr;
852
704
        } else {
853
704
            out->output_curves[i].table_8  = nullptr;
854
704
            out->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
855
704
        }
856
976
    }
857
858
319
    return true;
859
452
}
skcms.cc:bool init_tables<skcms_A2B>(unsigned char const*, unsigned long, unsigned int, unsigned int, unsigned int, skcms_A2B*)
Line
Count
Source
809
263
                        A2B_or_B2A* out) {
810
    // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
811
263
    uint32_t byte_len_per_input_table  = input_table_entries * byte_width;
812
263
    uint32_t byte_len_per_output_table = output_table_entries * byte_width;
813
814
    // [input|output]_channels are <= 4, so still no overflow
815
263
    uint32_t byte_len_all_input_tables  = out->input_channels * byte_len_per_input_table;
816
263
    uint32_t byte_len_all_output_tables = out->output_channels * byte_len_per_output_table;
817
818
263
    uint64_t grid_size = out->output_channels * byte_width;
819
988
    for (uint32_t axis = 0; axis < out->input_channels; ++axis) {
820
725
        grid_size *= out->grid_points[axis];
821
725
    }
822
823
263
    if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
824
71
        return false;
825
71
    }
826
827
688
    for (uint32_t i = 0; i < out->input_channels; ++i) {
828
496
        out->input_curves[i].table_entries = input_table_entries;
829
496
        if (byte_width == 1) {
830
147
            out->input_curves[i].table_8  = table_base + i * byte_len_per_input_table;
831
147
            out->input_curves[i].table_16 = nullptr;
832
349
        } else {
833
349
            out->input_curves[i].table_8  = nullptr;
834
349
            out->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
835
349
        }
836
496
    }
837
838
192
    if (byte_width == 1) {
839
58
        out->grid_8  = table_base + byte_len_all_input_tables;
840
58
        out->grid_16 = nullptr;
841
134
    } else {
842
134
        out->grid_8  = nullptr;
843
134
        out->grid_16 = table_base + byte_len_all_input_tables;
844
134
    }
845
846
192
    const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
847
768
    for (uint32_t i = 0; i < out->output_channels; ++i) {
848
576
        out->output_curves[i].table_entries = output_table_entries;
849
576
        if (byte_width == 1) {
850
174
            out->output_curves[i].table_8  = output_table_base + i * byte_len_per_output_table;
851
174
            out->output_curves[i].table_16 = nullptr;
852
402
        } else {
853
402
            out->output_curves[i].table_8  = nullptr;
854
402
            out->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
855
402
        }
856
576
    }
857
858
192
    return true;
859
263
}
skcms.cc:bool init_tables<skcms_B2A>(unsigned char const*, unsigned long, unsigned int, unsigned int, unsigned int, skcms_B2A*)
Line
Count
Source
809
189
                        A2B_or_B2A* out) {
810
    // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
811
189
    uint32_t byte_len_per_input_table  = input_table_entries * byte_width;
812
189
    uint32_t byte_len_per_output_table = output_table_entries * byte_width;
813
814
    // [input|output]_channels are <= 4, so still no overflow
815
189
    uint32_t byte_len_all_input_tables  = out->input_channels * byte_len_per_input_table;
816
189
    uint32_t byte_len_all_output_tables = out->output_channels * byte_len_per_output_table;
817
818
189
    uint64_t grid_size = out->output_channels * byte_width;
819
756
    for (uint32_t axis = 0; axis < out->input_channels; ++axis) {
820
567
        grid_size *= out->grid_points[axis];
821
567
    }
822
823
189
    if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
824
62
        return false;
825
62
    }
826
827
508
    for (uint32_t i = 0; i < out->input_channels; ++i) {
828
381
        out->input_curves[i].table_entries = input_table_entries;
829
381
        if (byte_width == 1) {
830
90
            out->input_curves[i].table_8  = table_base + i * byte_len_per_input_table;
831
90
            out->input_curves[i].table_16 = nullptr;
832
291
        } else {
833
291
            out->input_curves[i].table_8  = nullptr;
834
291
            out->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
835
291
        }
836
381
    }
837
838
127
    if (byte_width == 1) {
839
30
        out->grid_8  = table_base + byte_len_all_input_tables;
840
30
        out->grid_16 = nullptr;
841
97
    } else {
842
97
        out->grid_8  = nullptr;
843
97
        out->grid_16 = table_base + byte_len_all_input_tables;
844
97
    }
845
846
127
    const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
847
527
    for (uint32_t i = 0; i < out->output_channels; ++i) {
848
400
        out->output_curves[i].table_entries = output_table_entries;
849
400
        if (byte_width == 1) {
850
98
            out->output_curves[i].table_8  = output_table_base + i * byte_len_per_output_table;
851
98
            out->output_curves[i].table_16 = nullptr;
852
302
        } else {
853
302
            out->output_curves[i].table_8  = nullptr;
854
302
            out->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
855
302
        }
856
400
    }
857
858
127
    return true;
859
189
}
860
861
template <typename A2B_or_B2A>
862
217
static bool read_tag_mft1(const skcms_ICCTag* tag, A2B_or_B2A* out) {
863
217
    if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
864
12
        return false;
865
12
    }
866
867
205
    const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
868
205
    if (!read_mft_common(mftTag->common, out)) {
869
56
        return false;
870
56
    }
871
872
149
    uint32_t input_table_entries  = 256;
873
149
    uint32_t output_table_entries = 256;
874
875
149
    return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft1_Layout), 1,
876
149
                       input_table_entries, output_table_entries, out);
877
205
}
skcms.cc:bool read_tag_mft1<skcms_A2B>(skcms_ICCTag const*, skcms_A2B*)
Line
Count
Source
862
121
static bool read_tag_mft1(const skcms_ICCTag* tag, A2B_or_B2A* out) {
863
121
    if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
864
6
        return false;
865
6
    }
866
867
115
    const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
868
115
    if (!read_mft_common(mftTag->common, out)) {
869
25
        return false;
870
25
    }
871
872
90
    uint32_t input_table_entries  = 256;
873
90
    uint32_t output_table_entries = 256;
874
875
    return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft1_Layout), 1,
876
90
                       input_table_entries, output_table_entries, out);
877
115
}
skcms.cc:bool read_tag_mft1<skcms_B2A>(skcms_ICCTag const*, skcms_B2A*)
Line
Count
Source
862
96
static bool read_tag_mft1(const skcms_ICCTag* tag, A2B_or_B2A* out) {
863
96
    if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
864
6
        return false;
865
6
    }
866
867
90
    const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
868
90
    if (!read_mft_common(mftTag->common, out)) {
869
31
        return false;
870
31
    }
871
872
59
    uint32_t input_table_entries  = 256;
873
59
    uint32_t output_table_entries = 256;
874
875
    return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft1_Layout), 1,
876
59
                       input_table_entries, output_table_entries, out);
877
90
}
878
879
template <typename A2B_or_B2A>
880
430
static bool read_tag_mft2(const skcms_ICCTag* tag, A2B_or_B2A* out) {
881
430
    if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
882
12
        return false;
883
12
    }
884
885
418
    const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
886
418
    if (!read_mft_common(mftTag->common, out)) {
887
51
        return false;
888
51
    }
889
890
367
    uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
891
367
    uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
892
893
    // ICC spec mandates that 2 <= table_entries <= 4096
894
367
    if (input_table_entries < 2 || input_table_entries > 4096 ||
895
340
        output_table_entries < 2 || output_table_entries > 4096) {
896
64
        return false;
897
64
    }
898
899
303
    return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft2_Layout), 2,
900
303
                       input_table_entries, output_table_entries, out);
901
367
}
skcms.cc:bool read_tag_mft2<skcms_A2B>(skcms_ICCTag const*, skcms_A2B*)
Line
Count
Source
880
232
static bool read_tag_mft2(const skcms_ICCTag* tag, A2B_or_B2A* out) {
881
232
    if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
882
5
        return false;
883
5
    }
884
885
227
    const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
886
227
    if (!read_mft_common(mftTag->common, out)) {
887
22
        return false;
888
22
    }
889
890
205
    uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
891
205
    uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
892
893
    // ICC spec mandates that 2 <= table_entries <= 4096
894
205
    if (input_table_entries < 2 || input_table_entries > 4096 ||
895
190
        output_table_entries < 2 || output_table_entries > 4096) {
896
32
        return false;
897
32
    }
898
899
173
    return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft2_Layout), 2,
900
173
                       input_table_entries, output_table_entries, out);
901
205
}
skcms.cc:bool read_tag_mft2<skcms_B2A>(skcms_ICCTag const*, skcms_B2A*)
Line
Count
Source
880
198
static bool read_tag_mft2(const skcms_ICCTag* tag, A2B_or_B2A* out) {
881
198
    if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
882
7
        return false;
883
7
    }
884
885
191
    const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
886
191
    if (!read_mft_common(mftTag->common, out)) {
887
29
        return false;
888
29
    }
889
890
162
    uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
891
162
    uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
892
893
    // ICC spec mandates that 2 <= table_entries <= 4096
894
162
    if (input_table_entries < 2 || input_table_entries > 4096 ||
895
150
        output_table_entries < 2 || output_table_entries > 4096) {
896
32
        return false;
897
32
    }
898
899
130
    return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft2_Layout), 2,
900
130
                       input_table_entries, output_table_entries, out);
901
162
}
902
903
static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
904
3.52k
                        uint32_t num_curves, skcms_Curve* curves) {
905
12.3k
    for (uint32_t i = 0; i < num_curves; ++i) {
906
9.42k
        if (curve_offset > size) {
907
380
            return false;
908
380
        }
909
910
9.04k
        uint32_t curve_bytes;
911
9.04k
        if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
912
206
            return false;
913
206
        }
914
915
8.84k
        if (curve_bytes > UINT32_MAX - 3) {
916
0
            return false;
917
0
        }
918
8.84k
        curve_bytes = (curve_bytes + 3) & ~3U;
919
920
8.84k
        uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
921
8.84k
        curve_offset = (uint32_t)new_offset_64;
922
8.84k
        if (new_offset_64 != curve_offset) {
923
0
            return false;
924
0
        }
925
8.84k
    }
926
927
2.93k
    return true;
928
3.52k
}
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
855
                         const uint8_t* endOfBuffer) {
953
855
    if (tag->size < SAFE_SIZEOF(mAB_or_mBA_Layout)) {
954
5
        return false;
955
5
    }
956
957
850
    const mAB_or_mBA_Layout* mABTag = (const mAB_or_mBA_Layout*)tag->buf;
958
959
850
    a2b->input_channels  = mABTag->input_channels[0];
960
850
    a2b->output_channels = mABTag->output_channels[0];
961
962
    // We require exactly three (ie XYZ/Lab/RGB) output channels
963
850
    if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
964
4
        return false;
965
4
    }
966
    // We require no more than four (ie CMYK) input channels
967
846
    if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
968
5
        return false;
969
5
    }
970
971
841
    uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
972
841
    uint32_t matrix_offset  = read_big_u32(mABTag->matrix_offset);
973
841
    uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
974
841
    uint32_t clut_offset    = read_big_u32(mABTag->clut_offset);
975
841
    uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
976
977
    // "B" curves must be present
978
841
    if (0 == b_curve_offset) {
979
2
        return false;
980
2
    }
981
982
839
    if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
983
839
                     a2b->output_curves)) {
984
170
        return false;
985
170
    }
986
987
    // "M" curves and Matrix must be used together
988
669
    if (0 != m_curve_offset) {
989
397
        if (0 == matrix_offset) {
990
5
            return false;
991
5
        }
992
392
        a2b->matrix_channels = a2b->output_channels;
993
392
        if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
994
392
                         a2b->matrix_curves)) {
995
68
            return false;
996
68
        }
997
998
        // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
999
324
        if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
1000
57
            return false;
1001
57
        }
1002
267
        float encoding_factor = pcs_is_xyz ? (65535 / 32768.0f) : 1.0f;
1003
267
        const uint8_t* mtx_buf = tag->buf + matrix_offset;
1004
267
        a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf +  0);
1005
267
        a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf +  4);
1006
267
        a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf +  8);
1007
267
        a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
1008
267
        a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
1009
267
        a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
1010
267
        a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
1011
267
        a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
1012
267
        a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
1013
267
        a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
1014
267
        a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
1015
267
        a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
1016
272
    } else {
1017
272
        if (0 != matrix_offset) {
1018
42
            return false;
1019
42
        }
1020
230
        a2b->matrix_channels = 0;
1021
230
    }
1022
1023
    // "A" curves and CLUT must be used together
1024
497
    if (0 != a_curve_offset) {
1025
427
        if (0 == clut_offset) {
1026
1
            return false;
1027
1
        }
1028
426
        if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
1029
426
                         a2b->input_curves)) {
1030
38
            return false;
1031
38
        }
1032
1033
388
        if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout)) {
1034
66
            return false;
1035
66
        }
1036
322
        const CLUT_Layout* clut = (const CLUT_Layout*)(tag->buf + clut_offset);
1037
1038
322
        if (clut->grid_byte_width[0] == 1) {
1039
196
            a2b->grid_8  = clut->variable;
1040
196
            a2b->grid_16 = nullptr;
1041
196
        } else if (clut->grid_byte_width[0] == 2) {
1042
110
            a2b->grid_8  = nullptr;
1043
110
            a2b->grid_16 = clut->variable;
1044
110
        } else {
1045
16
            return false;
1046
16
        }
1047
1048
306
        uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];  // the payload
1049
306
        uint64_t total_grid_points = 1;
1050
1.08k
        for (uint32_t i = 0; i < a2b->input_channels; ++i) {
1051
784
            a2b->grid_points[i] = clut->grid_points[i];
1052
            // The grid only makes sense with at least two points along each axis
1053
784
            if (a2b->grid_points[i] < 2) {
1054
4
                return false;
1055
4
            }
1056
780
            grid_size *= a2b->grid_points[i];
1057
780
            total_grid_points *= a2b->grid_points[i];
1058
780
        }
1059
1060
302
        if (total_grid_points > SKCMS_MAX_GRID_POINTS) {
1061
8
            return false;
1062
8
        }
1063
1064
294
        const uint64_t table_size = clut_offset + SAFE_FIXED_SIZE(CLUT_Layout) + grid_size;
1065
294
        if (table_size > tag->size) {
1066
65
            return false;
1067
65
        }
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
229
        uint32_t slack = 0;
1072
229
        if (a2b->output_channels == 3) {
1073
229
            slack = clut->grid_byte_width[0] == 1 ? 1 : 2;
1074
229
        }
1075
229
        if (tag->buf + table_size + slack > endOfBuffer) {
1076
0
            return false;
1077
0
        }
1078
229
    } else {
1079
70
        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
20
        if (a2b->input_channels != a2b->output_channels) {
1085
3
            return false;
1086
3
        }
1087
1088
        // Zero out the number of input channels to signal that we're skipping this stage
1089
17
        a2b->input_channels = 0;
1090
17
    }
1091
1092
246
    return true;
1093
497
}
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
962
                         const uint8_t* endOfBuffer) {
1099
962
    if (tag->size < SAFE_SIZEOF(mAB_or_mBA_Layout)) {
1100
5
        return false;
1101
5
    }
1102
1103
957
    const mAB_or_mBA_Layout* mBATag = (const mAB_or_mBA_Layout*)tag->buf;
1104
1105
957
    b2a->input_channels  = mBATag->input_channels[0];
1106
957
    b2a->output_channels = mBATag->output_channels[0];
1107
1108
    // Require exactly 3 inputs (XYZ) and 3 (RGB) or 4 (CMYK) outputs.
1109
957
    if (b2a->input_channels != ARRAY_COUNT(b2a->input_curves)) {
1110
10
        return false;
1111
10
    }
1112
947
    if (b2a->output_channels < 3 || b2a->output_channels > ARRAY_COUNT(b2a->output_curves)) {
1113
12
        return false;
1114
12
    }
1115
1116
935
    uint32_t b_curve_offset = read_big_u32(mBATag->b_curve_offset);
1117
935
    uint32_t matrix_offset  = read_big_u32(mBATag->matrix_offset);
1118
935
    uint32_t m_curve_offset = read_big_u32(mBATag->m_curve_offset);
1119
935
    uint32_t clut_offset    = read_big_u32(mBATag->clut_offset);
1120
935
    uint32_t a_curve_offset = read_big_u32(mBATag->a_curve_offset);
1121
1122
935
    if (0 == b_curve_offset) {
1123
1
        return false;
1124
1
    }
1125
1126
    // "B" curves are our inputs, not outputs.
1127
934
    if (!read_curves(tag->buf, tag->size, b_curve_offset, b2a->input_channels,
1128
934
                     b2a->input_curves)) {
1129
165
        return false;
1130
165
    }
1131
1132
769
    if (0 != m_curve_offset) {
1133
507
        if (0 == matrix_offset) {
1134
5
            return false;
1135
5
        }
1136
        // Matrix channels is tied to input_channels (3), not output_channels.
1137
502
        b2a->matrix_channels = b2a->input_channels;
1138
1139
502
        if (!read_curves(tag->buf, tag->size, m_curve_offset, b2a->matrix_channels,
1140
502
                         b2a->matrix_curves)) {
1141
73
            return false;
1142
73
        }
1143
1144
429
        if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
1145
55
            return false;
1146
55
        }
1147
374
        float encoding_factor = pcs_is_xyz ? (32768 / 65535.0f) : 1.0f;  // TODO: understand
1148
374
        const uint8_t* mtx_buf = tag->buf + matrix_offset;
1149
374
        b2a->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf +  0);
1150
374
        b2a->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf +  4);
1151
374
        b2a->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf +  8);
1152
374
        b2a->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
1153
374
        b2a->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
1154
374
        b2a->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
1155
374
        b2a->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
1156
374
        b2a->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
1157
374
        b2a->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
1158
374
        b2a->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
1159
374
        b2a->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
1160
374
        b2a->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
1161
374
    } else {
1162
262
        if (0 != matrix_offset) {
1163
50
            return false;
1164
50
        }
1165
212
        b2a->matrix_channels = 0;
1166
212
    }
1167
1168
586
    if (0 != a_curve_offset) {
1169
430
        if (0 == clut_offset) {
1170
1
            return false;
1171
1
        }
1172
1173
        // "A" curves are our output, not input.
1174
429
        if (!read_curves(tag->buf, tag->size, a_curve_offset, b2a->output_channels,
1175
429
                         b2a->output_curves)) {
1176
72
            return false;
1177
72
        }
1178
1179
357
        if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout)) {
1180
61
            return false;
1181
61
        }
1182
296
        const CLUT_Layout* clut = (const CLUT_Layout*)(tag->buf + clut_offset);
1183
1184
296
        if (clut->grid_byte_width[0] == 1) {
1185
227
            b2a->grid_8  = clut->variable;
1186
227
            b2a->grid_16 = nullptr;
1187
227
        } else if (clut->grid_byte_width[0] == 2) {
1188
55
            b2a->grid_8  = nullptr;
1189
55
            b2a->grid_16 = clut->variable;
1190
55
        } else {
1191
14
            return false;
1192
14
        }
1193
1194
282
        uint64_t grid_size = b2a->output_channels * clut->grid_byte_width[0];
1195
282
        uint64_t total_grid_points = 1;
1196
1.11k
        for (uint32_t i = 0; i < b2a->input_channels; ++i) {
1197
837
            b2a->grid_points[i] = clut->grid_points[i];
1198
837
            if (b2a->grid_points[i] < 2) {
1199
6
                return false;
1200
6
            }
1201
831
            grid_size *= b2a->grid_points[i];
1202
831
            total_grid_points *= b2a->grid_points[i];
1203
831
        }
1204
1205
276
        if (total_grid_points > SKCMS_MAX_GRID_POINTS) {
1206
0
            return false;
1207
0
        }
1208
1209
276
        if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout) + grid_size) {
1210
41
            return false;
1211
41
        }
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
235
        uint32_t slack = 0;
1216
235
        if (b2a->output_channels == 3) {
1217
80
            slack = clut->grid_byte_width[0] == 1 ? 1 : 2;
1218
80
        }
1219
235
        if (tag->buf + clut_offset + SAFE_FIXED_SIZE(CLUT_Layout) + grid_size + slack > endOfBuffer) {
1220
1
            return false;
1221
1
        }
1222
235
    } else {
1223
156
        if (0 != clut_offset) {
1224
46
            return false;
1225
46
        }
1226
1227
110
        if (b2a->input_channels != b2a->output_channels) {
1228
1
            return false;
1229
1
        }
1230
1231
        // Zero out *output* channels to skip this stage.
1232
109
        b2a->output_channels = 0;
1233
109
    }
1234
343
    return true;
1235
586
}
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.99k
                      float* c, float* d, float* f = nullptr) {
1241
5.99k
    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.99k
    const float dx = 1.0f / static_cast<float>(N - 1);
1253
1254
5.99k
    int lin_points = 1;
1255
1256
5.99k
    float f_zero = 0.0f;
1257
5.99k
    if (f) {
1258
4.06k
        *f = eval_curve(curve, 0);
1259
4.06k
    } else {
1260
1.93k
        f = &f_zero;
1261
1.93k
    }
1262
1263
1264
5.99k
    float slope_min = -INFINITY_;
1265
5.99k
    float slope_max = +INFINITY_;
1266
59.2k
    for (int i = 1; i < N; ++i) {
1267
57.8k
        float x = static_cast<float>(i) * dx;
1268
57.8k
        float y = eval_curve(curve, x);
1269
1270
57.8k
        float slope_max_i = (y + tol - *f) / x,
1271
57.8k
              slope_min_i = (y - tol - *f) / x;
1272
57.8k
        if (slope_max_i < slope_min || slope_max < slope_min_i) {
1273
            // Slope intervals would no longer overlap.
1274
4.50k
            break;
1275
4.50k
        }
1276
53.2k
        slope_max = fminf_(slope_max, slope_max_i);
1277
53.2k
        slope_min = fmaxf_(slope_min, slope_min_i);
1278
1279
53.2k
        float cur_slope = (y - *f) / x;
1280
53.2k
        if (slope_min <= cur_slope && cur_slope <= slope_max) {
1281
50.4k
            lin_points = i + 1;
1282
50.4k
            *c = cur_slope;
1283
50.4k
        }
1284
53.2k
    }
1285
1286
    // Set D to the last point that met our tolerance.
1287
5.99k
    *d = static_cast<float>(lin_points - 1) * dx;
1288
5.99k
    return lin_points;
1289
5.99k
}
1290
1291
// If this skcms_Curve holds an identity table, rewrite it as an identity skcms_TransferFunction.
1292
6.56k
static void canonicalize_identity(skcms_Curve* curve) {
1293
6.56k
    if (curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
1294
4.06k
        int N = (int)curve->table_entries;
1295
1296
4.06k
        float c = 0.0f, d = 0.0f, f = 0.0f;
1297
4.06k
        if (N == fit_linear(curve, N, 1.0f/static_cast<float>(2*N), &c,&d,&f)
1298
1.32k
            && c == 1.0f
1299
249
            && f == 0.0f) {
1300
249
            curve->table_entries = 0;
1301
249
            curve->table_8       = nullptr;
1302
249
            curve->table_16      = nullptr;
1303
249
            curve->parametric    = skcms_TransferFunction{1,1,0,0,0,0,0};
1304
249
        }
1305
4.06k
    }
1306
6.56k
}
1307
1308
1.29k
static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz, const uint8_t* eob) {
1309
1.29k
    bool ok = false;
1310
1.29k
    if (tag->type == skcms_Signature_mft1) { ok = read_tag_mft1(tag, a2b); }
1311
1.29k
    if (tag->type == skcms_Signature_mft2) { ok = read_tag_mft2(tag, a2b); }
1312
1.29k
    if (tag->type == skcms_Signature_mAB ) { ok = read_tag_mab(tag, a2b, pcs_is_xyz, eob); }
1313
1.29k
    if (!ok) {
1314
856
        return false;
1315
856
    }
1316
1317
438
    if (a2b->input_channels > 0) { canonicalize_identity(a2b->input_curves + 0); }
1318
438
    if (a2b->input_channels > 1) { canonicalize_identity(a2b->input_curves + 1); }
1319
438
    if (a2b->input_channels > 2) { canonicalize_identity(a2b->input_curves + 2); }
1320
438
    if (a2b->input_channels > 3) { canonicalize_identity(a2b->input_curves + 3); }
1321
1322
438
    if (a2b->matrix_channels > 0) { canonicalize_identity(a2b->matrix_curves + 0); }
1323
438
    if (a2b->matrix_channels > 1) { canonicalize_identity(a2b->matrix_curves + 1); }
1324
438
    if (a2b->matrix_channels > 2) { canonicalize_identity(a2b->matrix_curves + 2); }
1325
1326
438
    if (a2b->output_channels > 0) { canonicalize_identity(a2b->output_curves + 0); }
1327
438
    if (a2b->output_channels > 1) { canonicalize_identity(a2b->output_curves + 1); }
1328
438
    if (a2b->output_channels > 2) { canonicalize_identity(a2b->output_curves + 2); }
1329
1330
438
    return true;
1331
1.29k
}
1332
1333
1.36k
static bool read_b2a(const skcms_ICCTag* tag, skcms_B2A* b2a, bool pcs_is_xyz, const uint8_t* eob) {
1334
1.36k
    bool ok = false;
1335
1.36k
    if (tag->type == skcms_Signature_mft1) { ok = read_tag_mft1(tag, b2a); }
1336
1.36k
    if (tag->type == skcms_Signature_mft2) { ok = read_tag_mft2(tag, b2a); }
1337
1.36k
    if (tag->type == skcms_Signature_mBA ) { ok = read_tag_mba(tag, b2a, pcs_is_xyz, eob); }
1338
1.36k
    if (!ok) {
1339
893
        return false;
1340
893
    }
1341
1342
470
    if (b2a->input_channels > 0) { canonicalize_identity(b2a->input_curves + 0); }
1343
470
    if (b2a->input_channels > 1) { canonicalize_identity(b2a->input_curves + 1); }
1344
470
    if (b2a->input_channels > 2) { canonicalize_identity(b2a->input_curves + 2); }
1345
1346
470
    if (b2a->matrix_channels > 0) { canonicalize_identity(b2a->matrix_curves + 0); }
1347
470
    if (b2a->matrix_channels > 1) { canonicalize_identity(b2a->matrix_curves + 1); }
1348
470
    if (b2a->matrix_channels > 2) { canonicalize_identity(b2a->matrix_curves + 2); }
1349
1350
470
    if (b2a->output_channels > 0) { canonicalize_identity(b2a->output_curves + 0); }
1351
470
    if (b2a->output_channels > 1) { canonicalize_identity(b2a->output_curves + 1); }
1352
470
    if (b2a->output_channels > 2) { canonicalize_identity(b2a->output_curves + 2); }
1353
470
    if (b2a->output_channels > 3) { canonicalize_identity(b2a->output_curves + 3); }
1354
1355
470
    return true;
1356
1.36k
}
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
114
static bool read_cicp(const skcms_ICCTag* tag, skcms_CICP* cicp) {
1368
114
    if (tag->type != skcms_Signature_CICP || tag->size < SAFE_SIZEOF(CICP_Layout)) {
1369
67
        return false;
1370
67
    }
1371
1372
47
    const CICP_Layout* cicpTag = (const CICP_Layout*)tag->buf;
1373
1374
47
    cicp->color_primaries          = cicpTag->color_primaries[0];
1375
47
    cicp->transfer_characteristics = cicpTag->transfer_characteristics[0];
1376
47
    cicp->matrix_coefficients      = cicpTag->matrix_coefficients[0];
1377
47
    cicp->video_full_range_flag    = cicpTag->video_full_range_flag[0];
1378
47
    return true;
1379
114
}
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
27.5k
bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
1392
27.5k
    if (!profile || !profile->buffer || !tag) { return false; }
1393
27.5k
    const tag_Layout* tags = get_tag_table(profile);
1394
144k
    for (uint32_t i = 0; i < profile->tag_count; ++i) {
1395
127k
        if (read_big_u32(tags[i].signature) == sig) {
1396
10.2k
            tag->signature = sig;
1397
10.2k
            tag->size      = read_big_u32(tags[i].size);
1398
10.2k
            tag->buf       = read_big_u32(tags[i].offset) + profile->buffer;
1399
10.2k
            tag->type      = read_big_u32(tag->buf);
1400
10.2k
            return true;
1401
10.2k
        }
1402
127k
    }
1403
17.3k
    return false;
1404
27.5k
}
1405
1406
2.13k
static bool usable_as_src(const skcms_ICCProfile* profile) {
1407
2.13k
    return profile->has_A2B
1408
1.75k
       || (profile->has_trc && profile->has_toXYZD50);
1409
2.13k
}
1410
1411
bool skcms_ParseWithA2BPriority(const void* buf, size_t len,
1412
                                const int priority[], const int priorities,
1413
4.53k
                                skcms_ICCProfile* profile) {
1414
4.53k
    static_assert(SAFE_SIZEOF(header_Layout) == 132, "need to update header code");
1415
1416
4.53k
    if (!profile) {
1417
0
        return false;
1418
0
    }
1419
4.53k
    memset(profile, 0, SAFE_SIZEOF(*profile));
1420
1421
4.53k
    if (len < SAFE_SIZEOF(header_Layout)) {
1422
14
        return false;
1423
14
    }
1424
1425
    // Byte-swap all header fields
1426
4.52k
    const header_Layout* header  = (const header_Layout*)buf;
1427
4.52k
    profile->buffer              = (const uint8_t*)buf;
1428
4.52k
    profile->size                = read_big_u32(header->size);
1429
4.52k
    uint32_t version             = read_big_u32(header->version);
1430
4.52k
    profile->data_color_space    = read_big_u32(header->data_color_space);
1431
4.52k
    profile->pcs                 = read_big_u32(header->pcs);
1432
4.52k
    uint32_t signature           = read_big_u32(header->signature);
1433
4.52k
    float illuminant_X           = read_big_fixed(header->illuminant_X);
1434
4.52k
    float illuminant_Y           = read_big_fixed(header->illuminant_Y);
1435
4.52k
    float illuminant_Z           = read_big_fixed(header->illuminant_Z);
1436
4.52k
    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.52k
    uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
1441
4.52k
    if (signature != skcms_Signature_acsp ||
1442
4.46k
        profile->size > len ||
1443
4.42k
        profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
1444
4.35k
        (version >> 24) > 4) {
1445
173
        return false;
1446
173
    }
1447
1448
    // Validate that illuminant is D50 white
1449
4.34k
    if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
1450
4.34k
        fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
1451
4.34k
        fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
1452
5
        return false;
1453
5
    }
1454
1455
    // Validate that all tag entries have sane offset + size
1456
4.34k
    const tag_Layout* tags = get_tag_table(profile);
1457
20.5k
    for (uint32_t i = 0; i < profile->tag_count; ++i) {
1458
16.2k
        uint32_t tag_offset = read_big_u32(tags[i].offset);
1459
16.2k
        uint32_t tag_size   = read_big_u32(tags[i].size);
1460
16.2k
        uint64_t tag_end    = (uint64_t)tag_offset + (uint64_t)tag_size;
1461
16.2k
        if (tag_size < 4 || tag_end > profile->size) {
1462
95
            return false;
1463
95
        }
1464
16.2k
    }
1465
1466
4.24k
    if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
1467
90
        return false;
1468
90
    }
1469
1470
4.15k
    bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
1471
1472
    // Pre-parse commonly used tags.
1473
4.15k
    skcms_ICCTag kTRC;
1474
4.15k
    if (profile->data_color_space == skcms_Signature_Gray &&
1475
537
        skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
1476
515
        if (!read_curve(kTRC.buf, kTRC.size, &profile->trc[0], nullptr)) {
1477
            // Malformed tag
1478
37
            return false;
1479
37
        }
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
475
            profile->toXYZD50.vals[0][0] = illuminant_X;
1486
475
            profile->toXYZD50.vals[1][1] = illuminant_Y;
1487
475
            profile->toXYZD50.vals[2][2] = illuminant_Z;
1488
475
            profile->has_toXYZD50 = true;
1489
475
        }
1490
3.64k
    } else {
1491
3.64k
        skcms_ICCTag rTRC, gTRC, bTRC;
1492
3.64k
        if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
1493
1.08k
            skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
1494
1.06k
            skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
1495
1.05k
            if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
1496
1.05k
                !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
1497
1.05k
                !read_curve(bTRC.buf, bTRC.size, &profile->trc[2], nullptr)) {
1498
                // Malformed TRC tags
1499
6
                return false;
1500
6
            }
1501
1.05k
            profile->has_trc = true;
1502
1.05k
        }
1503
1504
3.63k
        skcms_ICCTag rXYZ, gXYZ, bXYZ;
1505
3.63k
        if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
1506
1.27k
            skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
1507
1.22k
            skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
1508
1.21k
            if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
1509
                // Malformed XYZ tags
1510
162
                return false;
1511
162
            }
1512
1.05k
            profile->has_toXYZD50 = true;
1513
1.05k
        }
1514
3.63k
    }
1515
1516
3.95k
    const uint8_t* endOfBuffer = (const uint8_t*)buf + len;
1517
9.95k
    for (int i = 0; i < priorities; i++) {
1518
        // enum { perceptual, relative_colormetric, saturation }
1519
7.29k
        if (priority[i] < 0 || priority[i] > 2) {
1520
0
            return false;
1521
0
        }
1522
7.29k
        uint32_t sig = skcms_Signature_A2B0 + static_cast<uint32_t>(priority[i]);
1523
7.29k
        skcms_ICCTag tag;
1524
7.29k
        if (skcms_GetTagBySignature(profile, sig, &tag)) {
1525
1.29k
            if (!read_a2b(&tag, &profile->A2B, pcs_is_xyz, endOfBuffer)) {
1526
                // Malformed A2B tag
1527
856
                return false;
1528
856
            }
1529
438
            profile->has_A2B = true;
1530
438
            break;
1531
1.29k
        }
1532
7.29k
    }
1533
1534
7.30k
    for (int i = 0; i < priorities; i++) {
1535
        // enum { perceptual, relative_colormetric, saturation }
1536
5.57k
        if (priority[i] < 0 || priority[i] > 2) {
1537
0
            return false;
1538
0
        }
1539
5.57k
        uint32_t sig = skcms_Signature_B2A0 + static_cast<uint32_t>(priority[i]);
1540
5.57k
        skcms_ICCTag tag;
1541
5.57k
        if (skcms_GetTagBySignature(profile, sig, &tag)) {
1542
1.36k
            if (!read_b2a(&tag, &profile->B2A, pcs_is_xyz, endOfBuffer)) {
1543
                // Malformed B2A tag
1544
893
                return false;
1545
893
            }
1546
470
            profile->has_B2A = true;
1547
470
            break;
1548
1.36k
        }
1549
5.57k
    }
1550
1551
2.20k
    skcms_ICCTag cicp_tag;
1552
2.20k
    if (skcms_GetTagBySignature(profile, skcms_Signature_CICP, &cicp_tag)) {
1553
114
        if (!read_cicp(&cicp_tag, &profile->CICP)) {
1554
            // Malformed CICP tag
1555
67
            return false;
1556
67
        }
1557
47
        profile->has_CICP = true;
1558
47
    }
1559
1560
2.13k
    return usable_as_src(profile);
1561
2.20k
}
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.14k
static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1870
8.14k
    skcms_Vector3 dst = {{0,0,0}};
1871
32.5k
    for (int row = 0; row < 3; ++row) {
1872
24.4k
        dst.vals[row] = m->vals[row][0] * v->vals[0]
1873
24.4k
                      + m->vals[row][1] * v->vals[1]
1874
24.4k
                      + m->vals[row][2] * v->vals[2];
1875
24.4k
    }
1876
8.14k
    return dst;
1877
8.14k
}
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
36.9k
bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1966
36.9k
    double a00 = src->vals[0][0],
1967
36.9k
           a01 = src->vals[1][0],
1968
36.9k
           a02 = src->vals[2][0],
1969
36.9k
           a10 = src->vals[0][1],
1970
36.9k
           a11 = src->vals[1][1],
1971
36.9k
           a12 = src->vals[2][1],
1972
36.9k
           a20 = src->vals[0][2],
1973
36.9k
           a21 = src->vals[1][2],
1974
36.9k
           a22 = src->vals[2][2];
1975
1976
36.9k
    double b0 = a00*a11 - a01*a10,
1977
36.9k
           b1 = a00*a12 - a02*a10,
1978
36.9k
           b2 = a01*a12 - a02*a11,
1979
36.9k
           b3 = a20,
1980
36.9k
           b4 = a21,
1981
36.9k
           b5 = a22;
1982
1983
36.9k
    double determinant = b0*b5
1984
36.9k
                       - b1*b4
1985
36.9k
                       + b2*b3;
1986
1987
36.9k
    if (determinant == 0) {
1988
78
        return false;
1989
78
    }
1990
1991
36.8k
    double invdet = 1.0 / determinant;
1992
36.8k
    if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1993
403
        return false;
1994
403
    }
1995
1996
36.4k
    b0 *= invdet;
1997
36.4k
    b1 *= invdet;
1998
36.4k
    b2 *= invdet;
1999
36.4k
    b3 *= invdet;
2000
36.4k
    b4 *= invdet;
2001
36.4k
    b5 *= invdet;
2002
2003
36.4k
    dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
2004
36.4k
    dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
2005
36.4k
    dst->vals[2][0] = (float)(        +     b2 );
2006
36.4k
    dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
2007
36.4k
    dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
2008
36.4k
    dst->vals[2][1] = (float)(        -     b1 );
2009
36.4k
    dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
2010
36.4k
    dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
2011
36.4k
    dst->vals[2][2] = (float)(        +     b0 );
2012
2013
145k
    for (int r = 0; r < 3; ++r)
2014
436k
    for (int c = 0; c < 3; ++c) {
2015
327k
        if (!isfinitef_(dst->vals[r][c])) {
2016
28
            return false;
2017
28
        }
2018
327k
    }
2019
36.3k
    return true;
2020
36.4k
}
2021
2022
3.24k
skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
2023
3.24k
    skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
2024
12.9k
    for (int r = 0; r < 3; r++)
2025
38.9k
        for (int c = 0; c < 3; c++) {
2026
29.2k
            m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
2027
29.2k
                         + A->vals[r][1] * B->vals[1][c]
2028
29.2k
                         + A->vals[r][2] * B->vals[2][c];
2029
29.2k
        }
2030
3.24k
    return m;
2031
3.24k
}
2032
2033
#if defined(__clang__)
2034
    [[clang::no_sanitize("float-divide-by-zero")]]  // Checked for by classify() on the way out.
2035
#endif
2036
31.7k
bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
2037
31.7k
    TF_PQish  pq;
2038
31.7k
    TF_HLGish hlg;
2039
31.7k
    switch (classify(*src, &pq, &hlg)) {
2040
2.23k
        case skcms_TFType_Invalid: return false;
2041
81
        case skcms_TFType_PQ:      return false;
2042
93
        case skcms_TFType_HLG:     return false;
2043
28.7k
        case skcms_TFType_sRGBish: break;  // handled below
2044
2045
259
        case skcms_TFType_PQish:
2046
259
            *dst = { TFKind_marker(skcms_TFType_PQish), -pq.A,  pq.D, 1.0f/pq.F
2047
259
                                                      ,  pq.B, -pq.E, 1.0f/pq.C};
2048
259
            return true;
2049
2050
196
        case skcms_TFType_HLGish:
2051
196
            *dst = { TFKind_marker(skcms_TFType_HLGinvish), 1.0f/hlg.R, 1.0f/hlg.G
2052
196
                                                          , 1.0f/hlg.a, hlg.b, hlg.c
2053
196
                                                          , hlg.K_minus_1 };
2054
196
            return true;
2055
2056
107
        case skcms_TFType_HLGinvish:
2057
107
            *dst = { TFKind_marker(skcms_TFType_HLGish), 1.0f/hlg.R, 1.0f/hlg.G
2058
107
                                                       , 1.0f/hlg.a, hlg.b, hlg.c
2059
107
                                                       , hlg.K_minus_1 };
2060
107
            return true;
2061
31.7k
    }
2062
2063
31.7k
    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
28.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
28.7k
    float d_l =       src->c * src->d + src->f,
2075
28.7k
          d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
2076
28.7k
    if (fabsf_(d_l - d_r) > 1/512.0f) {
2077
872
        return false;
2078
872
    }
2079
27.9k
    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
27.9k
    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
19.6k
        inv.c =    1.0f/src->c;
2088
19.6k
        inv.f = -src->f/src->c;
2089
19.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
27.9k
    float k = powf_(src->a, -src->g);  // (1/a)^g == a^-g
2104
27.9k
    inv.g = 1.0f / src->g;
2105
27.9k
    inv.a = k;
2106
27.9k
    inv.b = -k * src->e;
2107
27.9k
    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
27.9k
    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
27.9k
    if (inv.a * inv.d + inv.b < 0) {
2119
1.78k
        inv.b = -inv.a * inv.d;
2120
1.78k
    }
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
27.9k
    if (classify(inv) != skcms_TFType_sRGBish) {
2125
463
        return false;
2126
463
    }
2127
2128
27.9k
    assert (inv.a >= 0);
2129
27.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
27.4k
    float s = skcms_TransferFunction_eval(src, 1.0f);
2135
27.4k
    if (!isfinitef_(s)) {
2136
236
        return false;
2137
236
    }
2138
2139
27.2k
    float sign = s < 0 ? -1.0f : 1.0f;
2140
27.2k
    s *= sign;
2141
27.2k
    if (s < inv.d) {
2142
1.26k
        inv.f = 1.0f - sign * inv.c * s;
2143
25.9k
    } else {
2144
25.9k
        inv.e = 1.0f - sign * powf_(inv.a * s + inv.b, inv.g);
2145
25.9k
    }
2146
2147
27.2k
    *dst = inv;
2148
27.2k
    return classify(*dst) == skcms_TFType_sRGBish;
2149
27.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
6.87M
                          float dfdP[3]) {
2194
6.87M
    const float y = eval_curve(curve, x);
2195
2196
6.87M
    const float g = tf->g, a = tf->a, b = tf->b,
2197
6.87M
                c = tf->c, d = tf->d, f = tf->f;
2198
2199
6.87M
    const float Y = fmaxf_(a*y + b, 0.0f),
2200
6.87M
                D =        a*d + b;
2201
6.87M
    assert (D >= 0);
2202
2203
    // The gradient.
2204
6.87M
    dfdP[0] = logf_(Y)*powf_(Y, g)
2205
6.87M
            - logf_(D)*powf_(D, g);
2206
6.87M
    dfdP[1] = y*g*powf_(Y, g-1)
2207
6.87M
            - d*g*powf_(D, g-1);
2208
6.87M
    dfdP[2] =   g*powf_(Y, g-1)
2209
6.87M
            -   g*powf_(D, g-1);
2210
2211
    // The residual.
2212
6.87M
    const float f_inv = powf_(Y, g)
2213
6.87M
                      - powf_(D, g)
2214
6.87M
                      + c*d + f;
2215
6.87M
    return x - f_inv;
2216
6.87M
}
2217
2218
static bool gauss_newton_step(const skcms_Curve* curve,
2219
                                    skcms_TransferFunction* tf,
2220
8.63k
                              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.63k
    skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
2257
8.63k
    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
6.88M
    for (int i = 0; i < N; i++) {
2263
6.87M
        float x = x0 + static_cast<float>(i)*dx;
2264
2265
6.87M
        float dfdP[3] = {0,0,0};
2266
6.87M
        float resid = rg_nonlinear(x,curve,tf, dfdP);
2267
2268
27.5M
        for (int r = 0; r < 3; r++) {
2269
82.5M
            for (int c = 0; c < 3; c++) {
2270
61.8M
                lhs.vals[r][c] += dfdP[r] * dfdP[c];
2271
61.8M
            }
2272
20.6M
            rhs.vals[r] += dfdP[r] * resid;
2273
20.6M
        }
2274
6.87M
    }
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.5k
    for (int k = 0; k < 3; k++) {
2279
25.9k
        if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
2280
4.32k
            lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
2281
4.32k
            lhs.vals[k][k] = 1;
2282
4.32k
        }
2283
25.9k
    }
2284
2285
    // 3) invert lhs
2286
8.63k
    skcms_Matrix3x3 lhs_inv;
2287
8.63k
    if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
2288
490
        return false;
2289
490
    }
2290
2291
    // 4) multiply inverse lhs by rhs
2292
8.14k
    skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
2293
8.14k
    tf->g += dP.vals[0];
2294
8.14k
    tf->a += dP.vals[1];
2295
8.14k
    tf->b += dP.vals[2];
2296
8.14k
    return isfinitef_(tf->g) && isfinitef_(tf->a) && isfinitef_(tf->b);
2297
8.63k
}
2298
2299
static float max_roundtrip_error_checked(const skcms_Curve* curve,
2300
9.31k
                                         const skcms_TransferFunction* tf_inv) {
2301
9.31k
    skcms_TransferFunction tf;
2302
9.31k
    if (!skcms_TransferFunction_invert(tf_inv, &tf) || skcms_TFType_sRGBish != classify(tf)) {
2303
3.28k
        return INFINITY_;
2304
3.28k
    }
2305
2306
6.02k
    skcms_TransferFunction tf_inv_again;
2307
6.02k
    if (!skcms_TransferFunction_invert(&tf, &tf_inv_again)) {
2308
907
        return INFINITY_;
2309
907
    }
2310
2311
5.11k
    return skcms_MaxRoundtripError(curve, &tf_inv_again);
2312
6.02k
}
2313
2314
// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
2315
1.82k
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
9.95k
    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
9.95k
        if (tf->a < 0) {
2321
581
            return false;
2322
581
        }
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.37k
        if (tf->a * tf->d + tf->b < 0) {
2326
1.17k
            tf->b = -tf->a * tf->d;
2327
1.17k
        }
2328
9.37k
        assert (tf->a >= 0 &&
2329
9.37k
                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.37k
        tf->e =   tf->c*tf->d + tf->f
2334
9.37k
          - powf_(tf->a*tf->d + tf->b, tf->g);
2335
2336
9.37k
        return isfinitef_(tf->e);
2337
9.37k
    };
2338
2339
1.82k
    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.82k
    const float dx = 1.0f / static_cast<float>(N-1);
2345
2346
1.82k
    skcms_TransferFunction best_tf = *tf;
2347
1.82k
    float best_max_error = INFINITY_;
2348
2349
    // Need this or several curves get worse... *sigh*
2350
1.82k
    float init_error = max_roundtrip_error_checked(curve, tf);
2351
1.82k
    if (init_error < best_max_error) {
2352
1.52k
        best_max_error = init_error;
2353
1.52k
        best_tf = *tf;
2354
1.52k
    }
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.31k
    for (int j = 0; j < 8; j++) {
2358
8.63k
        if (!gauss_newton_step(curve, tf, static_cast<float>(L)*dx, dx, N-L) || !fixup_tf()) {
2359
1.14k
            *tf = best_tf;
2360
1.14k
            return isfinitef_(best_max_error);
2361
1.14k
        }
2362
2363
7.48k
        float max_error = max_roundtrip_error_checked(curve, tf);
2364
7.48k
        if (max_error < best_max_error) {
2365
1.42k
            best_max_error = max_error;
2366
1.42k
            best_tf = *tf;
2367
1.42k
        }
2368
7.48k
    }
2369
2370
679
    *tf = best_tf;
2371
679
    return isfinitef_(best_max_error);
2372
1.82k
}
2373
2374
bool skcms_ApproximateCurve(const skcms_Curve* curve,
2375
                            skcms_TransferFunction* approx,
2376
986
                            float* max_error) {
2377
986
    if (!curve || !approx || !max_error) {
2378
0
        return false;
2379
0
    }
2380
2381
986
    if (curve->table_entries == 0) {
2382
        // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
2383
18
        return false;
2384
18
    }
2385
2386
968
    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
968
    int N = (int)curve->table_entries;
2392
968
    const float dx = 1.0f / static_cast<float>(N - 1);
2393
2394
968
    *max_error = INFINITY_;
2395
968
    const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
2396
2.90k
    for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
2397
1.93k
        skcms_TransferFunction tf,
2398
1.93k
                               tf_inv;
2399
2400
        // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
2401
1.93k
        tf.f = 0.0f;
2402
1.93k
        int L = fit_linear(curve, N, kTolerances[t], &tf.c, &tf.d);
2403
2404
1.93k
        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
39
            tf.g = 1;
2408
39
            tf.a = tf.c;
2409
39
            tf.b = tf.f;
2410
39
            tf.c = tf.d = tf.e = tf.f = 0;
2411
1.89k
        } else if (L == N - 1) {
2412
            // Degenerate case with only two points in the nonlinear segment. Solve directly.
2413
72
            tf.g = 1;
2414
72
            tf.a = (eval_curve(curve, static_cast<float>(N-1)*dx) -
2415
72
                    eval_curve(curve, static_cast<float>(N-2)*dx))
2416
72
                 / dx;
2417
72
            tf.b = eval_curve(curve, static_cast<float>(N-2)*dx)
2418
72
                 - tf.a * static_cast<float>(N-2)*dx;
2419
72
            tf.e = 0;
2420
1.82k
        } else {
2421
            // Start by guessing a gamma-only curve through the midpoint.
2422
1.82k
            int mid = (L + N) / 2;
2423
1.82k
            float mid_x = static_cast<float>(mid) / static_cast<float>(N - 1);
2424
1.82k
            float mid_y = eval_curve(curve, mid_x);
2425
1.82k
            tf.g = log2f_(mid_y) / log2f_(mid_x);
2426
1.82k
            tf.a = 1;
2427
1.82k
            tf.b = 0;
2428
1.82k
            tf.e =    tf.c*tf.d + tf.f
2429
1.82k
              - powf_(tf.a*tf.d + tf.b, tf.g);
2430
2431
2432
1.82k
            if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
2433
1.82k
                !fit_nonlinear(curve, L,N, &tf_inv)) {
2434
240
                continue;
2435
240
            }
2436
2437
            // We fit tf_inv, so calculate tf to keep in sync.
2438
            // fit_nonlinear() should guarantee invertibility.
2439
1.58k
            if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
2440
0
                assert(false);
2441
0
                continue;
2442
0
            }
2443
1.58k
        }
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.69k
        if (skcms_TFType_sRGBish != classify(tf)) {
2450
29
            continue;
2451
29
        }
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.66k
        if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
2463
15
            continue;
2464
15
        }
2465
2466
1.65k
        float err = skcms_MaxRoundtripError(curve, &tf_inv);
2467
1.65k
        if (*max_error > err) {
2468
1.01k
            *max_error = err;
2469
1.01k
            *approx    = tf;
2470
1.01k
        }
2471
1.65k
    }
2472
968
    return isfinitef_(*max_error);
2473
968
}
2474
2475
enum class CpuType { Baseline, HSW, SKX };
2476
2477
8.47k
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
8.47k
        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
8.47k
        return type;
2534
8.47k
    #endif
2535
8.47k
}
2536
2537
32.5k
static bool tf_is_gamma(const skcms_TransferFunction& tf) {
2538
32.5k
    return tf.g > 0 && tf.a == 1 &&
2539
25.9k
           tf.b == 0 && tf.c == 0 && tf.d == 0 && tf.e == 0 && tf.f == 0;
2540
32.5k
}
2541
2542
struct OpAndArg {
2543
    Op          op;
2544
    const void* arg;
2545
};
2546
2547
73.9k
static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
2548
73.9k
    struct OpType {
2549
73.9k
        Op sGamma, sRGBish, PQish, HLGish, HLGinvish, table;
2550
73.9k
    };
2551
73.9k
    static constexpr OpType kOps[] = {
2552
73.9k
        { Op::gamma_r, Op::tf_r, Op::pq_r, Op::hlg_r, Op::hlginv_r, Op::table_r },
2553
73.9k
        { Op::gamma_g, Op::tf_g, Op::pq_g, Op::hlg_g, Op::hlginv_g, Op::table_g },
2554
73.9k
        { Op::gamma_b, Op::tf_b, Op::pq_b, Op::hlg_b, Op::hlginv_b, Op::table_b },
2555
73.9k
        { Op::gamma_a, Op::tf_a, Op::pq_a, Op::hlg_a, Op::hlginv_a, Op::table_a },
2556
73.9k
    };
2557
73.9k
    const auto& op = kOps[channel];
2558
2559
73.9k
    if (curve->table_entries == 0) {
2560
32.5k
        const OpAndArg noop = { Op::load_a8/*doesn't matter*/, nullptr };
2561
2562
32.5k
        const skcms_TransferFunction& tf = curve->parametric;
2563
2564
32.5k
        if (tf_is_gamma(tf)) {
2565
20.2k
            return tf.g != 1 ? OpAndArg{op.sGamma, &tf}
2566
20.2k
                             : noop;
2567
20.2k
        }
2568
2569
12.2k
        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
11.9k
            case skcms_TFType_sRGBish:    return OpAndArg{op.sRGBish,   &tf};
2577
162
            case skcms_TFType_PQish:      return OpAndArg{op.PQish,     &tf};
2578
0
            case skcms_TFType_HLGish:     return OpAndArg{op.HLGish,    &tf};
2579
108
            case skcms_TFType_HLGinvish:  return OpAndArg{op.HLGinvish, &tf};
2580
12.2k
        }
2581
12.2k
    }
2582
    // The table_* ops make this assumption.
2583
73.9k
    assert(curve->table_entries <= kMaxTableEntries);
2584
41.4k
    return OpAndArg{op.table, curve};
2585
41.4k
}
2586
2587
// Returns negative if any of the curves are malformed.
2588
24.1k
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
24.1k
    int cursor = 0;
2592
98.1k
    for (int index = numChannels; index-- > 0; ) {
2593
73.9k
        if (curves[index].table_entries > kMaxTableEntries) {
2594
0
            return -1;
2595
0
        }
2596
73.9k
        ops[cursor] = select_curve_op(&curves[index], index);
2597
73.9k
        if (ops[cursor].arg) {
2598
67.7k
            ++cursor;
2599
67.7k
        }
2600
73.9k
    }
2601
2602
    // Identify separate B+G+R ops and fuse them into a single RGB op.
2603
24.1k
    if (cursor >= 3) {
2604
19.7k
        struct FusableOps {
2605
19.7k
            Op r, g, b, rgb;
2606
19.7k
        };
2607
19.7k
        static constexpr FusableOps kFusableOps[] = {
2608
19.7k
            {Op::gamma_r,  Op::gamma_g,  Op::gamma_b,  Op::gamma_rgb},
2609
19.7k
            {Op::tf_r,     Op::tf_g,     Op::tf_b,     Op::tf_rgb},
2610
19.7k
            {Op::pq_r,     Op::pq_g,     Op::pq_b,     Op::pq_rgb},
2611
19.7k
            {Op::hlg_r,    Op::hlg_g,    Op::hlg_b,    Op::hlg_rgb},
2612
19.7k
            {Op::hlginv_r, Op::hlginv_g, Op::hlginv_b, Op::hlginv_rgb},
2613
19.7k
        };
2614
2615
19.7k
        int posR = cursor - 1;
2616
19.7k
        int posG = cursor - 2;
2617
19.7k
        int posB = cursor - 3;
2618
90.4k
        for (const FusableOps& fusableOp : kFusableOps) {
2619
90.4k
            if (ops[posR].op == fusableOp.r &&
2620
7.86k
                ops[posG].op == fusableOp.g &&
2621
6.30k
                ops[posB].op == fusableOp.b &&
2622
4.98k
                (0 == memcmp(ops[posR].arg, ops[posG].arg, sizeof(skcms_TransferFunction))) &&
2623
3.28k
                (0 == memcmp(ops[posR].arg, ops[posB].arg, sizeof(skcms_TransferFunction)))) {
2624
                // Fuse the three matching ops into one.
2625
2.66k
                ops[posB].op = fusableOp.rgb;
2626
2.66k
                cursor -= 2;
2627
2.66k
                break;
2628
2.66k
            }
2629
90.4k
        }
2630
19.7k
    }
2631
2632
24.1k
    return cursor;
2633
24.1k
}
2634
2635
68.2k
static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2636
68.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
68.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
68.2k
    }
2659
68.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
43.2k
static bool has_cicp_pq_trc(const skcms_ICCProfile* profile) {
2668
43.2k
    return profile->has_CICP
2669
692
        && profile->CICP.transfer_characteristics == kTransferCicpIdPQ;
2670
43.2k
}
2671
2672
43.1k
static bool has_cicp_hlg_trc(const skcms_ICCProfile* profile) {
2673
43.1k
    return profile->has_CICP
2674
636
        && profile->CICP.transfer_characteristics == kTransferCicpIdHLG;
2675
43.1k
}
2676
2677
// Set tf to be the PQ transfer function, scaled such that 1.0 will map to 10,000 / 203.
2678
56
static void set_reference_pq_ish_trc(skcms_TransferFunction* tf) {
2679
    // Initialize such that 1.0 maps to 1.0.
2680
56
    skcms_TransferFunction_makePQish(tf,
2681
56
        -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
56
    const float w = 10000.0f / 203.0f;
2687
56
    const float ws = powf_(w, 1.0f / tf->f);
2688
56
    tf->a = ws * tf->a;
2689
56
    tf->b = ws * tf->b;
2690
56
}
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
36
static void set_sdr_hlg_ish_trc(skcms_TransferFunction* tf) {
2697
36
    skcms_TransferFunction_makeHLGish(tf,
2698
36
        2.0f, 2.0f, 1/0.17883277f, 0.28466892f, 0.55991073f);
2699
36
    tf->f = 1.0f / 12.0f - 1.0f;
2700
36
}
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.7k
                                 bool* dst_using_hlg_ootf) {
2709
34.7k
    const bool has_xyzd50 =
2710
34.7k
        profile->has_toXYZD50 &&
2711
27.8k
        skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2712
34.7k
    *dst_using_B2A = false;
2713
34.7k
    *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.7k
    if (has_cicp_pq_trc(profile) && has_xyzd50) {
2719
56
        skcms_TransferFunction trc_pq;
2720
56
        set_reference_pq_ish_trc(&trc_pq);
2721
56
        skcms_TransferFunction_invert(&trc_pq, invR);
2722
56
        skcms_TransferFunction_invert(&trc_pq, invG);
2723
56
        skcms_TransferFunction_invert(&trc_pq, invB);
2724
56
        return true;
2725
56
    }
2726
34.7k
    if (has_cicp_hlg_trc(profile) && has_xyzd50) {
2727
36
        skcms_TransferFunction trc_hlg;
2728
36
        set_sdr_hlg_ish_trc(&trc_hlg);
2729
36
        skcms_TransferFunction_invert(&trc_hlg, invR);
2730
36
        skcms_TransferFunction_invert(&trc_hlg, invG);
2731
36
        skcms_TransferFunction_invert(&trc_hlg, invB);
2732
36
        *dst_using_hlg_ootf = true;
2733
36
        return true;
2734
36
    }
2735
2736
    // Then prefer the B2A transformation.
2737
    // skcms_Transform() supports B2A destinations.
2738
34.6k
    if (profile->has_B2A) {
2739
5.45k
        *dst_using_B2A = true;
2740
5.45k
        return true;
2741
5.45k
    }
2742
2743
    // Finally use parametric transfer functions.
2744
    // TODO: Reject non sRGB-ish transfer functions here.
2745
29.2k
    return has_xyzd50
2746
25.2k
        && profile->has_trc
2747
25.2k
        && profile->trc[0].table_entries == 0
2748
4.29k
        && profile->trc[1].table_entries == 0
2749
4.03k
        && profile->trc[2].table_entries == 0
2750
3.70k
        && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2751
3.60k
        && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2752
3.56k
        && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB);
2753
34.6k
}
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
34.1k
                     size_t                  nz) {
2764
34.1k
    const size_t dst_bpp = bytes_per_pixel(dstFmt),
2765
34.1k
                 src_bpp = bytes_per_pixel(srcFmt);
2766
    // Let's just refuse if the request is absurdly big.
2767
34.1k
    if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2768
0
        return false;
2769
0
    }
2770
34.1k
    int n = (int)nz;
2771
2772
    // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2773
34.1k
    if (!srcProfile) {
2774
0
        srcProfile = skcms_sRGB_profile();
2775
0
    }
2776
34.1k
    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
34.1k
    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
34.1k
    Op          program[32];
2787
34.1k
    const void* context[32];
2788
2789
34.1k
    Op*          ops      = program;
2790
34.1k
    const void** contexts = context;
2791
2792
85.7k
    auto add_op = [&](Op o) {
2793
85.7k
        *ops++ = o;
2794
85.7k
        *contexts++ = nullptr;
2795
85.7k
    };
2796
2797
78.0k
    auto add_op_ctx = [&](Op o, const void* c) {
2798
78.0k
        *ops++ = o;
2799
78.0k
        *contexts++ = c;
2800
78.0k
    };
2801
2802
34.1k
    auto add_curve_ops = [&](const skcms_Curve* curves, int numChannels) -> bool {
2803
20.8k
        OpAndArg oa[4];
2804
20.8k
        assert(numChannels <= ARRAY_COUNT(oa));
2805
2806
20.8k
        int numOps = select_curve_ops(curves, numChannels, oa);
2807
20.8k
        if (numOps < 0) {
2808
0
            return false;
2809
0
        }
2810
2811
78.8k
        for (int i = 0; i < numOps; ++i) {
2812
58.0k
            add_op_ctx(oa[i].op, oa[i].arg);
2813
58.0k
        }
2814
20.8k
        return true;
2815
20.8k
    };
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
34.1k
    skcms_TransferFunction src_cicp_trc;
2820
2821
    // These are always parametric curves of some sort.
2822
34.1k
    skcms_Curve dst_curves[3];
2823
34.1k
    dst_curves[0].table_entries =
2824
34.1k
    dst_curves[1].table_entries =
2825
34.1k
    dst_curves[2].table_entries = 0;
2826
2827
    // This will store the XYZD50 to destination gamut conversion matrix, if it is needed.
2828
34.1k
    skcms_Matrix3x3        dst_from_xyz;
2829
2830
    // This will store the full source to destination gamut conversion matrix, if it is needed.
2831
34.1k
    skcms_Matrix3x3        dst_from_src;
2832
2833
34.1k
    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
34.1k
        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
34.1k
    }
2861
34.1k
    if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2862
34.1k
        srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2863
0
        add_op(Op::clamp);
2864
0
    }
2865
34.1k
    if (srcFmt & 1) {
2866
0
        add_op(Op::swap_rb);
2867
0
    }
2868
34.1k
    skcms_ICCProfile gray_dst_profile;
2869
34.1k
    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
34.1k
        default:
2879
34.1k
            break;
2880
34.1k
    }
2881
2882
34.1k
    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
34.1k
    if (srcAlpha == skcms_AlphaFormat_Opaque) {
2891
11.2k
        add_op(Op::force_opaque);
2892
22.8k
    } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2893
11.2k
        add_op(Op::unpremul);
2894
11.2k
    }
2895
2896
34.1k
    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
34.1k
        bool src_using_A2B = false;
2901
34.1k
        bool src_using_hlg_ootf = false;
2902
34.1k
        bool dst_using_B2A = false;
2903
34.1k
        bool dst_using_hlg_ootf = false;
2904
2905
34.1k
        if (!prep_for_destination(dstProfile,
2906
34.1k
                                  &dst_from_xyz,
2907
34.1k
                                  &dst_curves[0].parametric,
2908
34.1k
                                  &dst_curves[1].parametric,
2909
34.1k
                                  &dst_curves[2].parametric,
2910
34.1k
                                  &dst_using_B2A,
2911
34.1k
                                  &dst_using_hlg_ootf)) {
2912
25.6k
            return false;
2913
25.6k
        }
2914
2915
8.47k
        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
8.47k
        } 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
8.47k
        } 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
8.47k
        } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2959
8.47k
            if (!add_curve_ops(srcProfile->trc, /*numChannels=*/3)) {
2960
0
                return false;
2961
0
            }
2962
8.47k
        } 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
8.47k
        assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2968
2969
8.47k
        if (dst_using_B2A) {
2970
            // B2A needs its input in XYZD50, so transform TRC sources now.
2971
5.16k
            if (!src_using_A2B) {
2972
5.16k
                add_op_ctx(Op::matrix_3x3, &srcProfile->toXYZD50);
2973
                // Apply the HLG OOTF in XYZD50 space, if needed.
2974
5.16k
                if (src_using_hlg_ootf) {
2975
0
                    add_op(Op::hlg_ootf_scale);
2976
0
                }
2977
5.16k
            }
2978
2979
5.16k
            if (dstProfile->pcs == skcms_Signature_Lab) {
2980
2.48k
                add_op(Op::xyz_to_lab);
2981
2.48k
            }
2982
2983
5.16k
            if (dstProfile->B2A.input_channels == 3) {
2984
5.16k
                if (!add_curve_ops(dstProfile->B2A.input_curves, /*numChannels=*/3)) {
2985
0
                    return false;
2986
0
                }
2987
5.16k
            }
2988
2989
5.16k
            if (dstProfile->B2A.matrix_channels == 3) {
2990
3.58k
                static const skcms_Matrix3x4 I = {{
2991
3.58k
                    {1,0,0,0},
2992
3.58k
                    {0,1,0,0},
2993
3.58k
                    {0,0,1,0},
2994
3.58k
                }};
2995
3.58k
                if (0 != memcmp(&I, &dstProfile->B2A.matrix, sizeof(I))) {
2996
3.56k
                    add_op_ctx(Op::matrix_3x4, &dstProfile->B2A.matrix);
2997
3.56k
                }
2998
2999
3.58k
                if (!add_curve_ops(dstProfile->B2A.matrix_curves, /*numChannels=*/3)) {
3000
0
                    return false;
3001
0
                }
3002
3.58k
            }
3003
3004
5.16k
            if (dstProfile->B2A.output_channels) {
3005
3.58k
                add_op(Op::clamp);
3006
3.58k
                add_op_ctx(Op::clut_B2A, &dstProfile->B2A);
3007
3008
3.58k
                if (!add_curve_ops(dstProfile->B2A.output_curves,
3009
3.58k
                              (int)dstProfile->B2A.output_channels)) {
3010
0
                    return false;
3011
0
                }
3012
3.58k
            }
3013
5.16k
        } else {
3014
            // This is a TRC destination.
3015
3016
            // Transform to the destination gamut.
3017
3.31k
            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
36
                if (!src_using_A2B) {
3021
36
                    add_op_ctx(Op::matrix_3x3, &srcProfile->toXYZD50);
3022
36
                }
3023
36
                if (src_using_hlg_ootf) {
3024
0
                    add_op(Op::hlg_ootf_scale);
3025
0
                }
3026
36
                if (dst_using_hlg_ootf) {
3027
36
                    add_op(Op::hlginv_ootf_scale);
3028
36
                }
3029
36
                add_op_ctx(Op::matrix_3x3, &dst_from_xyz);
3030
3.27k
            } 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.27k
            } else {
3035
3.27k
                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.27k
                if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
3039
                    // Concat the entire gamut transform into dst_from_src.
3040
3.24k
                    dst_from_src = skcms_Matrix3x3_concat(&dst_from_xyz, to_xyz);
3041
3.24k
                    add_op_ctx(Op::matrix_3x3, &dst_from_src);
3042
3.24k
                }
3043
3.27k
            }
3044
3045
            // Encode back to dst RGB using its parametric transfer functions.
3046
3.31k
            OpAndArg oa[3];
3047
3.31k
            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.31k
            assert(numOps >= 0);
3051
7.65k
            for (int index = 0; index < numOps; ++index) {
3052
4.33k
                assert(oa[index].op != Op::table_r &&
3053
4.33k
                       oa[index].op != Op::table_g &&
3054
4.33k
                       oa[index].op != Op::table_b &&
3055
4.33k
                       oa[index].op != Op::table_a);
3056
4.33k
                add_op_ctx(oa[index].op, oa[index].arg);
3057
4.33k
            }
3058
3.31k
        }
3059
8.47k
    }
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
8.47k
    if (dstFmt < skcms_PixelFormat_RGB_hhh) {
3067
8.47k
        add_op(Op::clamp);
3068
8.47k
    }
3069
3070
8.47k
    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
180
        add_op(Op::invert);
3074
3075
        // CMYK has no alpha channel, so make sure dstAlpha is a no-op.
3076
180
        dstAlpha = skcms_AlphaFormat_Unpremul;
3077
180
    }
3078
3079
8.47k
    if (dstAlpha == skcms_AlphaFormat_Opaque) {
3080
2.76k
        add_op(Op::force_opaque);
3081
5.71k
    } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
3082
2.76k
        add_op(Op::premul);
3083
2.76k
    }
3084
8.47k
    if (dstFmt & 1) {
3085
0
        add_op(Op::swap_rb);
3086
0
    }
3087
8.47k
    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
8.47k
        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
8.47k
    }
3115
3116
8.47k
    assert(ops      <= program + ARRAY_COUNT(program));
3117
8.47k
    assert(contexts <= context + ARRAY_COUNT(context));
3118
3119
8.47k
    auto run = baseline::run_program;
3120
8.47k
    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
8.47k
        case CpuType::HSW:
3128
            #if !defined(SKCMS_DISABLE_HSW)
3129
                run = hsw::run_program;
3130
                break;
3131
            #endif
3132
3133
8.47k
        case CpuType::Baseline:
3134
8.47k
            break;
3135
8.47k
    }
3136
3137
8.47k
    run(program, context, ops - program, (const char*)src, (char*)dst, n, src_bpp,dst_bpp);
3138
8.47k
    return true;
3139
8.47k
}
3140
3141
631
static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
3142
#if defined(NDEBUG)
3143
    (void)profile;
3144
#else
3145
631
    skcms_Matrix3x3 fromXYZD50;
3146
631
    skcms_TransferFunction invR, invG, invB;
3147
631
    bool useB2A = false;
3148
631
    bool useHlgOotf = false;
3149
631
    assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB, &useB2A, &useHlgOotf));
3150
631
#endif
3151
631
}
3152
3153
948
bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
3154
948
    if (!profile->has_B2A) {
3155
661
        skcms_Matrix3x3 fromXYZD50;
3156
661
        if (!profile->has_trc || !profile->has_toXYZD50
3157
440
            || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
3158
222
            return false;
3159
222
        }
3160
3161
439
        skcms_TransferFunction tf[3];
3162
1.49k
        for (int i = 0; i < 3; i++) {
3163
1.14k
            skcms_TransferFunction inv;
3164
1.14k
            if (profile->trc[i].table_entries == 0
3165
178
                && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
3166
160
                tf[i] = profile->trc[i].parametric;
3167
160
                continue;
3168
160
            }
3169
3170
986
            float max_error;
3171
            // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
3172
986
            if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
3173
95
                return false;
3174
95
            }
3175
986
        }
3176
3177
1.37k
        for (int i = 0; i < 3; ++i) {
3178
1.03k
            profile->trc[i].table_entries = 0;
3179
1.03k
            profile->trc[i].parametric = tf[i];
3180
1.03k
        }
3181
344
    }
3182
631
    assert_usable_as_destination(profile);
3183
631
    return true;
3184
948
}
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
}