Coverage Report

Created: 2025-06-22 08:04

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