Coverage Report

Created: 2026-06-14 06:57

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