Coverage Report

Created: 2026-04-12 06:57

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