Coverage Report

Created: 2025-08-03 06:33

/src/skcms/skcms.cc
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2018 Google Inc.
3
 *
4
 * Use of this source code is governed by a BSD-style license that can be
5
 * found in the LICENSE file.
6
 */
7
8
#include "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
23.0M
static float log2f_(float x) {
45
    // The first approximation of log2(x) is its exponent 'e', minus 127.
46
23.0M
    int32_t bits;
47
23.0M
    memcpy(&bits, &x, sizeof(bits));
48
49
23.0M
    float e = (float)bits * (1.0f / (1<<23));
50
51
    // If we use the mantissa too we can refine the error signficantly.
52
23.0M
    int32_t m_bits = (bits & 0x007fffff) | 0x3f000000;
53
23.0M
    float m;
54
23.0M
    memcpy(&m, &m_bits, sizeof(m));
55
56
23.0M
    return (e - 124.225514990f
57
23.0M
              -   1.498030302f*m
58
23.0M
              -   1.725879990f/(0.3520887068f + m));
59
23.0M
}
60
7.46M
static float logf_(float x) {
61
7.46M
    const float ln2 = 0.69314718f;
62
7.46M
    return ln2*log2f_(x);
63
7.46M
}
64
65
15.5M
static float exp2f_(float x) {
66
15.5M
    if (x > 128.0f) {
67
96.1k
        return INFINITY_;
68
15.4M
    } else if (x < -127.0f) {
69
542k
        return 0.0f;
70
542k
    }
71
14.9M
    float fract = x - floorf_(x);
72
73
14.9M
    float fbits = (1.0f * (1<<23)) * (x + 121.274057500f
74
14.9M
                                        -   1.490129070f*fract
75
14.9M
                                        +  27.728023300f/(4.84252568f - fract));
76
77
    // Before we cast fbits to int32_t, check for out of range values to pacify UBSAN.
78
    // INT_MAX is not exactly representable as a float, so exclude it as effectively infinite.
79
    // Negative values are effectively underflow - we'll end up returning a (different) negative
80
    // value, which makes no sense. So clamp to zero.
81
14.9M
    if (fbits >= (float)INT_MAX) {
82
0
        return INFINITY_;
83
14.9M
    } else if (fbits < 0) {
84
68.3k
        return 0;
85
68.3k
    }
86
87
14.8M
    int32_t bits = (int32_t)fbits;
88
14.8M
    memcpy(&x, &bits, sizeof(x));
89
14.8M
    return x;
90
14.9M
}
91
92
// Not static, as it's used by some test tools.
93
32.1M
float powf_(float x, float y) {
94
32.1M
    if (x <= 0.f) {
95
16.5M
        return 0.f;
96
16.5M
    }
97
15.6M
    if (x == 1.f) {
98
92.8k
        return 1.f;
99
92.8k
    }
100
15.5M
    return exp2f_(log2f_(x) * y);
101
15.6M
}
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
19.6M
static float fmaxf_(float x, float y) { return x > y ? x : y; }
109
9.87M
static float fminf_(float x, float y) { return x < y ? x : y; }
110
111
6.27M
static bool isfinitef_(float x) { return 0 == x*0; }
112
113
9.81M
static float minus_1_ulp(float x) {
114
9.81M
    int32_t bits;
115
9.81M
    memcpy(&bits, &x, sizeof(bits));
116
9.81M
    bits = bits - 1;
117
9.81M
    memcpy(&x, &bits, sizeof(bits));
118
9.81M
    return x;
119
9.81M
}
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
423
static float TFKind_marker(skcms_TFType kind) {
131
    // We'd use different NaNs, but those aren't guaranteed to be preserved by WASM.
132
423
    return -(float)kind;
133
423
}
134
135
static skcms_TFType classify(const skcms_TransferFunction& tf, TF_PQish*   pq = nullptr
136
6.13M
                                                             , TF_HLGish* hlg = nullptr) {
137
6.13M
    if (tf.g < 0) {
138
        // Negative "g" is mapped to enum values; large negative are for sure invalid.
139
3.36k
        if (tf.g < -128) {
140
236
            return skcms_TFType_Invalid;
141
236
        }
142
3.12k
        int enum_g = -static_cast<int>(tf.g);
143
        // Non-whole "g" values are invalid as well.
144
3.12k
        if (static_cast<float>(-enum_g) != tf.g) {
145
1.47k
            return skcms_TFType_Invalid;
146
1.47k
        }
147
        // TODO: soundness checks for PQ/HLG like we do for sRGBish?
148
1.65k
        switch (enum_g) {
149
209
            case skcms_TFType_PQish:
150
209
                if (pq) {
151
104
                    memcpy(pq , &tf.a, sizeof(*pq ));
152
104
                }
153
209
                return skcms_TFType_PQish;
154
321
            case skcms_TFType_HLGish:
155
321
                if (hlg) {
156
221
                    memcpy(hlg, &tf.a, sizeof(*hlg));
157
221
                }
158
321
                return skcms_TFType_HLGish;
159
320
            case skcms_TFType_HLGinvish:
160
320
                if (hlg) {
161
98
                    memcpy(hlg, &tf.a, sizeof(*hlg));
162
98
                }
163
320
                return skcms_TFType_HLGinvish;
164
295
            case skcms_TFType_PQ:
165
295
                if (tf.b != 0.f || tf.c != 0.f || tf.d != 0.f || tf.e != 0.f || tf.f != 0.f) {
166
195
                    return skcms_TFType_Invalid;
167
195
                }
168
100
                return skcms_TFType_PQ;
169
329
            case skcms_TFType_HLG:
170
329
                if (tf.d != 0.f || tf.e != 0.f || tf.f != 0.f) {
171
226
                    return skcms_TFType_Invalid;
172
226
                }
173
103
                return skcms_TFType_HLG;
174
1.65k
        }
175
179
        return skcms_TFType_Invalid;
176
1.65k
    }
177
178
    // Basic soundness checks for sRGBish transfer functions.
179
6.12M
    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
6.12M
            && tf.a >= 0
182
6.12M
            && tf.c >= 0
183
6.12M
            && tf.d >= 0
184
6.12M
            && tf.g >= 0
185
            // Raising a negative value to a fractional tf->g produces complex numbers.
186
6.12M
            && tf.a * tf.d + tf.b >= 0) {
187
6.12M
        return skcms_TFType_sRGBish;
188
6.12M
    }
189
190
803
    return skcms_TFType_Invalid;
191
6.12M
}
192
193
0
skcms_TFType skcms_TransferFunction_getType(const skcms_TransferFunction* tf) {
194
0
    return classify(*tf);
195
0
}
196
2.83k
bool skcms_TransferFunction_isSRGBish(const skcms_TransferFunction* tf) {
197
2.83k
    return classify(*tf) == skcms_TFType_sRGBish;
198
2.83k
}
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
0
                                      float D, float E, float F) {
215
0
    *tf = { TFKind_marker(skcms_TFType_PQish), A,B,C,D,E,F };
216
0
    assert(skcms_TransferFunction_isPQish(tf));
217
0
    return true;
218
0
}
219
220
bool skcms_TransferFunction_makeScaledHLGish(skcms_TransferFunction* tf,
221
                                             float K, float R, float G,
222
0
                                             float a, float b, float c) {
223
0
    *tf = { TFKind_marker(skcms_TFType_HLGish), R,G, a,b,c, K-1.0f };
224
0
    assert(skcms_TransferFunction_isHLGish(tf));
225
0
    return true;
226
0
}
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
6.04M
float skcms_TransferFunction_eval(const skcms_TransferFunction* tf, float x) {
251
6.04M
    float sign = x < 0 ? -1.0f : 1.0f;
252
6.04M
    x *= sign;
253
254
6.04M
    TF_PQish  pq;
255
6.04M
    TF_HLGish hlg;
256
6.04M
    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
6.04M
        case skcms_TFType_sRGBish:
281
6.04M
            return sign * (x < tf->d ?       tf->c * x + tf->f
282
6.04M
                                     : 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
6.04M
    }
298
0
    return 0;
299
6.04M
}
300
301
302
9.81M
static float eval_curve(const skcms_Curve* curve, float x) {
303
9.81M
    if (curve->table_entries == 0) {
304
0
        return skcms_TransferFunction_eval(&curve->parametric, x);
305
0
    }
306
307
9.81M
    float ix = fmaxf_(0, fminf_(x, 1)) * static_cast<float>(curve->table_entries - 1);
308
9.81M
    int   lo = (int)                   ix        ,
309
9.81M
          hi = (int)(float)minus_1_ulp(ix + 1.0f);
310
9.81M
    float t = ix - (float)lo;
311
312
9.81M
    float l, h;
313
9.81M
    if (curve->table_8) {
314
7.14k
        l = curve->table_8[lo] * (1/255.0f);
315
7.14k
        h = curve->table_8[hi] * (1/255.0f);
316
9.81M
    } else {
317
9.81M
        uint16_t be_l, be_h;
318
9.81M
        memcpy(&be_l, curve->table_16 + 2*lo, 2);
319
9.81M
        memcpy(&be_h, curve->table_16 + 2*hi, 2);
320
9.81M
        uint16_t le_l = ((be_l << 8) | (be_l >> 8)) & 0xffff;
321
9.81M
        uint16_t le_h = ((be_h << 8) | (be_h >> 8)) & 0xffff;
322
9.81M
        l = le_l * (1/65535.0f);
323
9.81M
        h = le_h * (1/65535.0f);
324
9.81M
    }
325
9.81M
    return l + (h-l)*t;
326
9.81M
}
327
328
7.41k
float skcms_MaxRoundtripError(const skcms_Curve* curve, const skcms_TransferFunction* inv_tf) {
329
7.41k
    uint32_t N = curve->table_entries > 256 ? curve->table_entries : 256;
330
7.41k
    const float dx = 1.0f / static_cast<float>(N - 1);
331
7.41k
    float err = 0;
332
6.03M
    for (uint32_t i = 0; i < N; i++) {
333
6.02M
        float x = static_cast<float>(i) * dx,
334
6.02M
              y = eval_curve(curve, x);
335
6.02M
        err = fmaxf_(err, fabsf_(x - skcms_TransferFunction_eval(inv_tf, y)));
336
6.02M
    }
337
7.41k
    return err;
338
7.41k
}
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
4.42k
static uint16_t read_big_u16(const uint8_t* ptr) {
380
4.42k
    uint16_t be;
381
4.42k
    memcpy(&be, ptr, sizeof(be));
382
#if defined(_MSC_VER)
383
    return _byteswap_ushort(be);
384
#else
385
4.42k
    return __builtin_bswap16(be);
386
4.42k
#endif
387
4.42k
}
388
389
149k
static uint32_t read_big_u32(const uint8_t* ptr) {
390
149k
    uint32_t be;
391
149k
    memcpy(&be, ptr, sizeof(be));
392
#if defined(_MSC_VER)
393
    return _byteswap_ulong(be);
394
#else
395
149k
    return __builtin_bswap32(be);
396
149k
#endif
397
149k
}
398
399
20.9k
static int32_t read_big_i32(const uint8_t* ptr) {
400
20.9k
    return (int32_t)read_big_u32(ptr);
401
20.9k
}
402
403
20.9k
static float read_big_fixed(const uint8_t* ptr) {
404
20.9k
    return static_cast<float>(read_big_i32(ptr)) * (1.0f / 65536.0f);
405
20.9k
}
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
17.9k
static const tag_Layout* get_tag_table(const skcms_ICCProfile* profile) {
440
17.9k
    return (const tag_Layout*)(profile->buffer + SAFE_SIZEOF(header_Layout));
441
17.9k
}
442
443
// s15Fixed16ArrayType is technically variable sized, holding N values. However, the only valid
444
// use of the type is for the CHAD tag that stores exactly nine values.
445
typedef struct {
446
    uint8_t type     [ 4];
447
    uint8_t reserved [ 4];
448
    uint8_t values   [36];
449
} sf32_Layout;
450
451
0
bool skcms_GetCHAD(const skcms_ICCProfile* profile, skcms_Matrix3x3* m) {
452
0
    skcms_ICCTag tag;
453
0
    if (!skcms_GetTagBySignature(profile, skcms_Signature_CHAD, &tag)) {
454
0
        return false;
455
0
    }
456
457
0
    if (tag.type != skcms_Signature_sf32 || tag.size < SAFE_SIZEOF(sf32_Layout)) {
458
0
        return false;
459
0
    }
460
461
0
    const sf32_Layout* sf32Tag = (const sf32_Layout*)tag.buf;
462
0
    const uint8_t* values = sf32Tag->values;
463
0
    for (int r = 0; r < 3; ++r)
464
0
    for (int c = 0; c < 3; ++c, values += 4) {
465
0
        m->vals[r][c] = read_big_fixed(values);
466
0
    }
467
0
    return true;
468
0
}
469
470
// XYZType is technically variable sized, holding N XYZ triples. However, the only valid uses of
471
// the type are for tags/data that store exactly one triple.
472
typedef struct {
473
    uint8_t type     [4];
474
    uint8_t reserved [4];
475
    uint8_t X        [4];
476
    uint8_t Y        [4];
477
    uint8_t Z        [4];
478
} XYZ_Layout;
479
480
411
static bool read_tag_xyz(const skcms_ICCTag* tag, float* x, float* y, float* z) {
481
411
    if (tag->type != skcms_Signature_XYZ || tag->size < SAFE_SIZEOF(XYZ_Layout)) {
482
169
        return false;
483
169
    }
484
485
242
    const XYZ_Layout* xyzTag = (const XYZ_Layout*)tag->buf;
486
487
242
    *x = read_big_fixed(xyzTag->X);
488
242
    *y = read_big_fixed(xyzTag->Y);
489
242
    *z = read_big_fixed(xyzTag->Z);
490
242
    return true;
491
411
}
492
493
0
bool skcms_GetWTPT(const skcms_ICCProfile* profile, float xyz[3]) {
494
0
    skcms_ICCTag tag;
495
0
    return skcms_GetTagBySignature(profile, skcms_Signature_WTPT, &tag) &&
496
0
           read_tag_xyz(&tag, &xyz[0], &xyz[1], &xyz[2]);
497
0
}
498
499
0
static int data_color_space_channel_count(uint32_t data_color_space) {
500
0
    switch (data_color_space) {
501
0
        case skcms_Signature_CMYK:   return 4;
502
0
        case skcms_Signature_Gray:   return 1;
503
0
        case skcms_Signature_RGB:    return 3;
504
0
        case skcms_Signature_Lab:    return 3;
505
0
        case skcms_Signature_XYZ:    return 3;
506
0
        case skcms_Signature_CIELUV: return 3;
507
0
        case skcms_Signature_YCbCr:  return 3;
508
0
        case skcms_Signature_CIEYxy: return 3;
509
0
        case skcms_Signature_HSV:    return 3;
510
0
        case skcms_Signature_HLS:    return 3;
511
0
        case skcms_Signature_CMY:    return 3;
512
0
        case skcms_Signature_2CLR:   return 2;
513
0
        case skcms_Signature_3CLR:   return 3;
514
0
        case skcms_Signature_4CLR:   return 4;
515
0
        case skcms_Signature_5CLR:   return 5;
516
0
        case skcms_Signature_6CLR:   return 6;
517
0
        case skcms_Signature_7CLR:   return 7;
518
0
        case skcms_Signature_8CLR:   return 8;
519
0
        case skcms_Signature_9CLR:   return 9;
520
0
        case skcms_Signature_10CLR:  return 10;
521
0
        case skcms_Signature_11CLR:  return 11;
522
0
        case skcms_Signature_12CLR:  return 12;
523
0
        case skcms_Signature_13CLR:  return 13;
524
0
        case skcms_Signature_14CLR:  return 14;
525
0
        case skcms_Signature_15CLR:  return 15;
526
0
        default:                     return -1;
527
0
    }
528
0
}
529
530
0
int skcms_GetInputChannelCount(const skcms_ICCProfile* profile) {
531
0
    int a2b_count = 0;
532
0
    if (profile->has_A2B) {
533
0
        a2b_count = profile->A2B.input_channels != 0
534
0
                        ? static_cast<int>(profile->A2B.input_channels)
535
0
                        : 3;
536
0
    }
537
538
0
    skcms_ICCTag tag;
539
0
    int trc_count = 0;
540
0
    if (skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &tag)) {
541
0
        trc_count = 1;
542
0
    } else if (profile->has_trc) {
543
0
        trc_count = 3;
544
0
    }
545
546
0
    int dcs_count = data_color_space_channel_count(profile->data_color_space);
547
548
0
    if (dcs_count < 0) {
549
0
        return -1;
550
0
    }
551
552
0
    if (a2b_count > 0 && a2b_count != dcs_count) {
553
0
        return -1;
554
0
    }
555
0
    if (trc_count > 0 && trc_count != dcs_count) {
556
0
        return -1;
557
0
    }
558
559
0
    return dcs_count;
560
0
}
561
562
static bool read_to_XYZD50(const skcms_ICCTag* rXYZ, const skcms_ICCTag* gXYZ,
563
193
                           const skcms_ICCTag* bXYZ, skcms_Matrix3x3* toXYZ) {
564
193
    return read_tag_xyz(rXYZ, &toXYZ->vals[0][0], &toXYZ->vals[1][0], &toXYZ->vals[2][0]) &&
565
193
           read_tag_xyz(gXYZ, &toXYZ->vals[0][1], &toXYZ->vals[1][1], &toXYZ->vals[2][1]) &&
566
193
           read_tag_xyz(bXYZ, &toXYZ->vals[0][2], &toXYZ->vals[1][2], &toXYZ->vals[2][2]);
567
193
}
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
2.85k
                            skcms_Curve* curve, uint32_t* curve_size) {
579
2.85k
    if (size < SAFE_FIXED_SIZE(para_Layout)) {
580
3
        return false;
581
3
    }
582
583
2.85k
    const para_Layout* paraTag = (const para_Layout*)buf;
584
585
2.85k
    enum { kG = 0, kGAB = 1, kGABC = 2, kGABCD = 3, kGABCDEF = 4 };
586
2.85k
    uint16_t function_type = read_big_u16(paraTag->function_type);
587
2.85k
    if (function_type > kGABCDEF) {
588
14
        return false;
589
14
    }
590
591
2.84k
    static const uint32_t curve_bytes[] = { 4, 12, 16, 20, 28 };
592
2.84k
    if (size < SAFE_FIXED_SIZE(para_Layout) + curve_bytes[function_type]) {
593
6
        return false;
594
6
    }
595
596
2.83k
    if (curve_size) {
597
2.76k
        *curve_size = SAFE_FIXED_SIZE(para_Layout) + curve_bytes[function_type];
598
2.76k
    }
599
600
2.83k
    curve->table_entries = 0;
601
2.83k
    curve->parametric.a  = 1.0f;
602
2.83k
    curve->parametric.b  = 0.0f;
603
2.83k
    curve->parametric.c  = 0.0f;
604
2.83k
    curve->parametric.d  = 0.0f;
605
2.83k
    curve->parametric.e  = 0.0f;
606
2.83k
    curve->parametric.f  = 0.0f;
607
2.83k
    curve->parametric.g  = read_big_fixed(paraTag->variable);
608
609
2.83k
    switch (function_type) {
610
164
        case kGAB:
611
164
            curve->parametric.a = read_big_fixed(paraTag->variable + 4);
612
164
            curve->parametric.b = read_big_fixed(paraTag->variable + 8);
613
164
            if (curve->parametric.a == 0) {
614
1
                return false;
615
1
            }
616
163
            curve->parametric.d = -curve->parametric.b / curve->parametric.a;
617
163
            break;
618
75
        case kGABC:
619
75
            curve->parametric.a = read_big_fixed(paraTag->variable + 4);
620
75
            curve->parametric.b = read_big_fixed(paraTag->variable + 8);
621
75
            curve->parametric.e = read_big_fixed(paraTag->variable + 12);
622
75
            if (curve->parametric.a == 0) {
623
2
                return false;
624
2
            }
625
73
            curve->parametric.d = -curve->parametric.b / curve->parametric.a;
626
73
            curve->parametric.f = curve->parametric.e;
627
73
            break;
628
127
        case kGABCD:
629
127
            curve->parametric.a = read_big_fixed(paraTag->variable + 4);
630
127
            curve->parametric.b = read_big_fixed(paraTag->variable + 8);
631
127
            curve->parametric.c = read_big_fixed(paraTag->variable + 12);
632
127
            curve->parametric.d = read_big_fixed(paraTag->variable + 16);
633
127
            break;
634
98
        case kGABCDEF:
635
98
            curve->parametric.a = read_big_fixed(paraTag->variable + 4);
636
98
            curve->parametric.b = read_big_fixed(paraTag->variable + 8);
637
98
            curve->parametric.c = read_big_fixed(paraTag->variable + 12);
638
98
            curve->parametric.d = read_big_fixed(paraTag->variable + 16);
639
98
            curve->parametric.e = read_big_fixed(paraTag->variable + 20);
640
98
            curve->parametric.f = read_big_fixed(paraTag->variable + 24);
641
98
            break;
642
2.83k
    }
643
2.83k
    return skcms_TransferFunction_isSRGBish(&curve->parametric);
644
2.83k
}
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
5.33k
                            skcms_Curve* curve, uint32_t* curve_size) {
655
5.33k
    if (size < SAFE_FIXED_SIZE(curv_Layout)) {
656
4
        return false;
657
4
    }
658
659
5.33k
    const curv_Layout* curvTag = (const curv_Layout*)buf;
660
661
5.33k
    uint32_t value_count = read_big_u32(curvTag->value_count);
662
5.33k
    if (size < SAFE_FIXED_SIZE(curv_Layout) + value_count * SAFE_SIZEOF(uint16_t)) {
663
63
        return false;
664
63
    }
665
666
5.26k
    if (curve_size) {
667
4.67k
        *curve_size = SAFE_FIXED_SIZE(curv_Layout) + value_count * SAFE_SIZEOF(uint16_t);
668
4.67k
    }
669
670
5.26k
    if (value_count < 2) {
671
1.85k
        curve->table_entries = 0;
672
1.85k
        curve->parametric.a  = 1.0f;
673
1.85k
        curve->parametric.b  = 0.0f;
674
1.85k
        curve->parametric.c  = 0.0f;
675
1.85k
        curve->parametric.d  = 0.0f;
676
1.85k
        curve->parametric.e  = 0.0f;
677
1.85k
        curve->parametric.f  = 0.0f;
678
1.85k
        if (value_count == 0) {
679
            // Empty tables are a shorthand for an identity curve
680
859
            curve->parametric.g = 1.0f;
681
997
        } else {
682
            // Single entry tables are a shorthand for simple gamma
683
997
            curve->parametric.g = read_big_u16(curvTag->variable) * (1.0f / 256.0f);
684
997
        }
685
3.41k
    } else {
686
3.41k
        curve->table_8       = nullptr;
687
3.41k
        curve->table_16      = curvTag->variable;
688
3.41k
        curve->table_entries = value_count;
689
3.41k
    }
690
691
5.26k
    return true;
692
5.33k
}
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
8.34k
                       skcms_Curve* curve, uint32_t* curve_size) {
698
8.34k
    if (!buf || size < 4 || !curve) {
699
10
        return false;
700
10
    }
701
702
8.33k
    uint32_t type = read_big_u32(buf);
703
8.33k
    if (type == skcms_Signature_para) {
704
2.85k
        return read_curve_para(buf, size, curve, curve_size);
705
5.48k
    } else if (type == skcms_Signature_curv) {
706
5.33k
        return read_curve_curv(buf, size, curve, curve_size);
707
5.33k
    }
708
709
144
    return false;
710
8.33k
}
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
295
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
295
    a2b->matrix_channels = 0;
743
295
    a2b-> input_channels = mftTag-> input_channels[0];
744
295
    a2b->output_channels = mftTag->output_channels[0];
745
746
    // We require exactly three (ie XYZ/Lab/RGB) output channels
747
295
    if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
748
19
        return false;
749
19
    }
750
    // We require at least one, and no more than four (ie CMYK) input channels
751
276
    if (a2b->input_channels < 1 || a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
752
11
        return false;
753
11
    }
754
755
919
    for (uint32_t i = 0; i < a2b->input_channels; ++i) {
756
654
        a2b->grid_points[i] = mftTag->grid_points[0];
757
654
    }
758
    // The grid only makes sense with at least two points along each axis
759
265
    if (a2b->grid_points[0] < 2) {
760
3
        return false;
761
3
    }
762
262
    return true;
763
265
}
764
765
// All as the A2B version above, except where noted.
766
201
static bool read_mft_common(const mft_CommonLayout* mftTag, skcms_B2A* b2a) {
767
    // Same as A2B.
768
201
    b2a->matrix_channels = 0;
769
201
    b2a-> input_channels = mftTag-> input_channels[0];
770
201
    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
201
    if (b2a->input_channels != ARRAY_COUNT(b2a->input_curves)) {
775
26
        return false;
776
26
    }
777
175
    if (b2a->output_channels < 3 || b2a->output_channels > ARRAY_COUNT(b2a->output_curves)) {
778
18
        return false;
779
18
    }
780
781
    // Same as A2B.
782
628
    for (uint32_t i = 0; i < b2a->input_channels; ++i) {
783
471
        b2a->grid_points[i] = mftTag->grid_points[0];
784
471
    }
785
157
    if (b2a->grid_points[0] < 2) {
786
4
        return false;
787
4
    }
788
153
    return true;
789
157
}
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
361
                        A2B_or_B2A* out) {
795
    // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
796
361
    uint32_t byte_len_per_input_table  = input_table_entries * byte_width;
797
361
    uint32_t byte_len_per_output_table = output_table_entries * byte_width;
798
799
    // [input|output]_channels are <= 4, so still no overflow
800
361
    uint32_t byte_len_all_input_tables  = out->input_channels * byte_len_per_input_table;
801
361
    uint32_t byte_len_all_output_tables = out->output_channels * byte_len_per_output_table;
802
803
361
    uint64_t grid_size = out->output_channels * byte_width;
804
1.30k
    for (uint32_t axis = 0; axis < out->input_channels; ++axis) {
805
940
        grid_size *= out->grid_points[axis];
806
940
    }
807
808
361
    if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
809
203
        return false;
810
203
    }
811
812
493
    for (uint32_t i = 0; i < out->input_channels; ++i) {
813
335
        out->input_curves[i].table_entries = input_table_entries;
814
335
        if (byte_width == 1) {
815
81
            out->input_curves[i].table_8  = table_base + i * byte_len_per_input_table;
816
81
            out->input_curves[i].table_16 = nullptr;
817
254
        } else {
818
254
            out->input_curves[i].table_8  = nullptr;
819
254
            out->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
820
254
        }
821
335
    }
822
823
158
    if (byte_width == 1) {
824
33
        out->grid_8  = table_base + byte_len_all_input_tables;
825
33
        out->grid_16 = nullptr;
826
125
    } else {
827
125
        out->grid_8  = nullptr;
828
125
        out->grid_16 = table_base + byte_len_all_input_tables;
829
125
    }
830
831
158
    const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
832
638
    for (uint32_t i = 0; i < out->output_channels; ++i) {
833
480
        out->output_curves[i].table_entries = output_table_entries;
834
480
        if (byte_width == 1) {
835
101
            out->output_curves[i].table_8  = output_table_base + i * byte_len_per_output_table;
836
101
            out->output_curves[i].table_16 = nullptr;
837
379
        } else {
838
379
            out->output_curves[i].table_8  = nullptr;
839
379
            out->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
840
379
        }
841
480
    }
842
843
158
    return true;
844
361
}
skcms.cc:bool init_tables<skcms_A2B>(unsigned char const*, unsigned long, unsigned int, unsigned int, unsigned int, skcms_A2B*)
Line
Count
Source
794
234
                        A2B_or_B2A* out) {
795
    // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
796
234
    uint32_t byte_len_per_input_table  = input_table_entries * byte_width;
797
234
    uint32_t byte_len_per_output_table = output_table_entries * byte_width;
798
799
    // [input|output]_channels are <= 4, so still no overflow
800
234
    uint32_t byte_len_all_input_tables  = out->input_channels * byte_len_per_input_table;
801
234
    uint32_t byte_len_all_output_tables = out->output_channels * byte_len_per_output_table;
802
803
234
    uint64_t grid_size = out->output_channels * byte_width;
804
793
    for (uint32_t axis = 0; axis < out->input_channels; ++axis) {
805
559
        grid_size *= out->grid_points[axis];
806
559
    }
807
808
234
    if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
809
109
        return false;
810
109
    }
811
812
361
    for (uint32_t i = 0; i < out->input_channels; ++i) {
813
236
        out->input_curves[i].table_entries = input_table_entries;
814
236
        if (byte_width == 1) {
815
51
            out->input_curves[i].table_8  = table_base + i * byte_len_per_input_table;
816
51
            out->input_curves[i].table_16 = nullptr;
817
185
        } else {
818
185
            out->input_curves[i].table_8  = nullptr;
819
185
            out->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
820
185
        }
821
236
    }
822
823
125
    if (byte_width == 1) {
824
23
        out->grid_8  = table_base + byte_len_all_input_tables;
825
23
        out->grid_16 = nullptr;
826
102
    } else {
827
102
        out->grid_8  = nullptr;
828
102
        out->grid_16 = table_base + byte_len_all_input_tables;
829
102
    }
830
831
125
    const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
832
500
    for (uint32_t i = 0; i < out->output_channels; ++i) {
833
375
        out->output_curves[i].table_entries = output_table_entries;
834
375
        if (byte_width == 1) {
835
69
            out->output_curves[i].table_8  = output_table_base + i * byte_len_per_output_table;
836
69
            out->output_curves[i].table_16 = nullptr;
837
306
        } else {
838
306
            out->output_curves[i].table_8  = nullptr;
839
306
            out->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
840
306
        }
841
375
    }
842
843
125
    return true;
844
234
}
skcms.cc:bool init_tables<skcms_B2A>(unsigned char const*, unsigned long, unsigned int, unsigned int, unsigned int, skcms_B2A*)
Line
Count
Source
794
127
                        A2B_or_B2A* out) {
795
    // byte_width is 1 or 2, [input|output]_table_entries are in [2, 4096], so no overflow
796
127
    uint32_t byte_len_per_input_table  = input_table_entries * byte_width;
797
127
    uint32_t byte_len_per_output_table = output_table_entries * byte_width;
798
799
    // [input|output]_channels are <= 4, so still no overflow
800
127
    uint32_t byte_len_all_input_tables  = out->input_channels * byte_len_per_input_table;
801
127
    uint32_t byte_len_all_output_tables = out->output_channels * byte_len_per_output_table;
802
803
127
    uint64_t grid_size = out->output_channels * byte_width;
804
508
    for (uint32_t axis = 0; axis < out->input_channels; ++axis) {
805
381
        grid_size *= out->grid_points[axis];
806
381
    }
807
808
127
    if (max_tables_len < byte_len_all_input_tables + grid_size + byte_len_all_output_tables) {
809
94
        return false;
810
94
    }
811
812
132
    for (uint32_t i = 0; i < out->input_channels; ++i) {
813
99
        out->input_curves[i].table_entries = input_table_entries;
814
99
        if (byte_width == 1) {
815
30
            out->input_curves[i].table_8  = table_base + i * byte_len_per_input_table;
816
30
            out->input_curves[i].table_16 = nullptr;
817
69
        } else {
818
69
            out->input_curves[i].table_8  = nullptr;
819
69
            out->input_curves[i].table_16 = table_base + i * byte_len_per_input_table;
820
69
        }
821
99
    }
822
823
33
    if (byte_width == 1) {
824
10
        out->grid_8  = table_base + byte_len_all_input_tables;
825
10
        out->grid_16 = nullptr;
826
23
    } else {
827
23
        out->grid_8  = nullptr;
828
23
        out->grid_16 = table_base + byte_len_all_input_tables;
829
23
    }
830
831
33
    const uint8_t* output_table_base = table_base + byte_len_all_input_tables + grid_size;
832
138
    for (uint32_t i = 0; i < out->output_channels; ++i) {
833
105
        out->output_curves[i].table_entries = output_table_entries;
834
105
        if (byte_width == 1) {
835
32
            out->output_curves[i].table_8  = output_table_base + i * byte_len_per_output_table;
836
32
            out->output_curves[i].table_16 = nullptr;
837
73
        } else {
838
73
            out->output_curves[i].table_8  = nullptr;
839
73
            out->output_curves[i].table_16 = output_table_base + i * byte_len_per_output_table;
840
73
        }
841
105
    }
842
843
33
    return true;
844
127
}
845
846
template <typename A2B_or_B2A>
847
180
static bool read_tag_mft1(const skcms_ICCTag* tag, A2B_or_B2A* out) {
848
180
    if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
849
11
        return false;
850
11
    }
851
852
169
    const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
853
169
    if (!read_mft_common(mftTag->common, out)) {
854
40
        return false;
855
40
    }
856
857
129
    uint32_t input_table_entries  = 256;
858
129
    uint32_t output_table_entries = 256;
859
860
129
    return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft1_Layout), 1,
861
129
                       input_table_entries, output_table_entries, out);
862
169
}
skcms.cc:bool read_tag_mft1<skcms_A2B>(skcms_ICCTag const*, skcms_A2B*)
Line
Count
Source
847
99
static bool read_tag_mft1(const skcms_ICCTag* tag, A2B_or_B2A* out) {
848
99
    if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
849
6
        return false;
850
6
    }
851
852
93
    const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
853
93
    if (!read_mft_common(mftTag->common, out)) {
854
16
        return false;
855
16
    }
856
857
77
    uint32_t input_table_entries  = 256;
858
77
    uint32_t output_table_entries = 256;
859
860
77
    return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft1_Layout), 1,
861
77
                       input_table_entries, output_table_entries, out);
862
93
}
skcms.cc:bool read_tag_mft1<skcms_B2A>(skcms_ICCTag const*, skcms_B2A*)
Line
Count
Source
847
81
static bool read_tag_mft1(const skcms_ICCTag* tag, A2B_or_B2A* out) {
848
81
    if (tag->size < SAFE_FIXED_SIZE(mft1_Layout)) {
849
5
        return false;
850
5
    }
851
852
76
    const mft1_Layout* mftTag = (const mft1_Layout*)tag->buf;
853
76
    if (!read_mft_common(mftTag->common, out)) {
854
24
        return false;
855
24
    }
856
857
52
    uint32_t input_table_entries  = 256;
858
52
    uint32_t output_table_entries = 256;
859
860
52
    return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft1_Layout), 1,
861
52
                       input_table_entries, output_table_entries, out);
862
76
}
863
864
template <typename A2B_or_B2A>
865
339
static bool read_tag_mft2(const skcms_ICCTag* tag, A2B_or_B2A* out) {
866
339
    if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
867
12
        return false;
868
12
    }
869
870
327
    const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
871
327
    if (!read_mft_common(mftTag->common, out)) {
872
41
        return false;
873
41
    }
874
875
286
    uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
876
286
    uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
877
878
    // ICC spec mandates that 2 <= table_entries <= 4096
879
286
    if (input_table_entries < 2 || input_table_entries > 4096 ||
880
286
        output_table_entries < 2 || output_table_entries > 4096) {
881
54
        return false;
882
54
    }
883
884
232
    return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft2_Layout), 2,
885
232
                       input_table_entries, output_table_entries, out);
886
286
}
skcms.cc:bool read_tag_mft2<skcms_A2B>(skcms_ICCTag const*, skcms_A2B*)
Line
Count
Source
865
208
static bool read_tag_mft2(const skcms_ICCTag* tag, A2B_or_B2A* out) {
866
208
    if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
867
6
        return false;
868
6
    }
869
870
202
    const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
871
202
    if (!read_mft_common(mftTag->common, out)) {
872
17
        return false;
873
17
    }
874
875
185
    uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
876
185
    uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
877
878
    // ICC spec mandates that 2 <= table_entries <= 4096
879
185
    if (input_table_entries < 2 || input_table_entries > 4096 ||
880
185
        output_table_entries < 2 || output_table_entries > 4096) {
881
28
        return false;
882
28
    }
883
884
157
    return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft2_Layout), 2,
885
157
                       input_table_entries, output_table_entries, out);
886
185
}
skcms.cc:bool read_tag_mft2<skcms_B2A>(skcms_ICCTag const*, skcms_B2A*)
Line
Count
Source
865
131
static bool read_tag_mft2(const skcms_ICCTag* tag, A2B_or_B2A* out) {
866
131
    if (tag->size < SAFE_FIXED_SIZE(mft2_Layout)) {
867
6
        return false;
868
6
    }
869
870
125
    const mft2_Layout* mftTag = (const mft2_Layout*)tag->buf;
871
125
    if (!read_mft_common(mftTag->common, out)) {
872
24
        return false;
873
24
    }
874
875
101
    uint32_t input_table_entries = read_big_u16(mftTag->input_table_entries);
876
101
    uint32_t output_table_entries = read_big_u16(mftTag->output_table_entries);
877
878
    // ICC spec mandates that 2 <= table_entries <= 4096
879
101
    if (input_table_entries < 2 || input_table_entries > 4096 ||
880
101
        output_table_entries < 2 || output_table_entries > 4096) {
881
26
        return false;
882
26
    }
883
884
75
    return init_tables(mftTag->variable, tag->size - SAFE_FIXED_SIZE(mft2_Layout), 2,
885
75
                       input_table_entries, output_table_entries, out);
886
101
}
887
888
static bool read_curves(const uint8_t* buf, uint32_t size, uint32_t curve_offset,
889
3.02k
                        uint32_t num_curves, skcms_Curve* curves) {
890
10.4k
    for (uint32_t i = 0; i < num_curves; ++i) {
891
7.93k
        if (curve_offset > size) {
892
298
            return false;
893
298
        }
894
895
7.63k
        uint32_t curve_bytes;
896
7.63k
        if (!read_curve(buf + curve_offset, size - curve_offset, &curves[i], &curve_bytes)) {
897
223
            return false;
898
223
        }
899
900
7.41k
        if (curve_bytes > UINT32_MAX - 3) {
901
0
            return false;
902
0
        }
903
7.41k
        curve_bytes = (curve_bytes + 3) & ~3U;
904
905
7.41k
        uint64_t new_offset_64 = (uint64_t)curve_offset + curve_bytes;
906
7.41k
        curve_offset = (uint32_t)new_offset_64;
907
7.41k
        if (new_offset_64 != curve_offset) {
908
0
            return false;
909
0
        }
910
7.41k
    }
911
912
2.50k
    return true;
913
3.02k
}
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
791
static bool read_tag_mab(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
937
791
    if (tag->size < SAFE_SIZEOF(mAB_or_mBA_Layout)) {
938
5
        return false;
939
5
    }
940
941
786
    const mAB_or_mBA_Layout* mABTag = (const mAB_or_mBA_Layout*)tag->buf;
942
943
786
    a2b->input_channels  = mABTag->input_channels[0];
944
786
    a2b->output_channels = mABTag->output_channels[0];
945
946
    // We require exactly three (ie XYZ/Lab/RGB) output channels
947
786
    if (a2b->output_channels != ARRAY_COUNT(a2b->output_curves)) {
948
9
        return false;
949
9
    }
950
    // We require no more than four (ie CMYK) input channels
951
777
    if (a2b->input_channels > ARRAY_COUNT(a2b->input_curves)) {
952
1
        return false;
953
1
    }
954
955
776
    uint32_t b_curve_offset = read_big_u32(mABTag->b_curve_offset);
956
776
    uint32_t matrix_offset  = read_big_u32(mABTag->matrix_offset);
957
776
    uint32_t m_curve_offset = read_big_u32(mABTag->m_curve_offset);
958
776
    uint32_t clut_offset    = read_big_u32(mABTag->clut_offset);
959
776
    uint32_t a_curve_offset = read_big_u32(mABTag->a_curve_offset);
960
961
    // "B" curves must be present
962
776
    if (0 == b_curve_offset) {
963
1
        return false;
964
1
    }
965
966
775
    if (!read_curves(tag->buf, tag->size, b_curve_offset, a2b->output_channels,
967
775
                     a2b->output_curves)) {
968
131
        return false;
969
131
    }
970
971
    // "M" curves and Matrix must be used together
972
644
    if (0 != m_curve_offset) {
973
391
        if (0 == matrix_offset) {
974
7
            return false;
975
7
        }
976
384
        a2b->matrix_channels = a2b->output_channels;
977
384
        if (!read_curves(tag->buf, tag->size, m_curve_offset, a2b->matrix_channels,
978
384
                         a2b->matrix_curves)) {
979
71
            return false;
980
71
        }
981
982
        // Read matrix, which is stored as a row-major 3x3, followed by the fourth column
983
313
        if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
984
54
            return false;
985
54
        }
986
259
        float encoding_factor = pcs_is_xyz ? (65535 / 32768.0f) : 1.0f;
987
259
        const uint8_t* mtx_buf = tag->buf + matrix_offset;
988
259
        a2b->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf +  0);
989
259
        a2b->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf +  4);
990
259
        a2b->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf +  8);
991
259
        a2b->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
992
259
        a2b->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
993
259
        a2b->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
994
259
        a2b->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
995
259
        a2b->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
996
259
        a2b->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
997
259
        a2b->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
998
259
        a2b->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
999
259
        a2b->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
1000
259
    } else {
1001
253
        if (0 != matrix_offset) {
1002
52
            return false;
1003
52
        }
1004
201
        a2b->matrix_channels = 0;
1005
201
    }
1006
1007
    // "A" curves and CLUT must be used together
1008
460
    if (0 != a_curve_offset) {
1009
395
        if (0 == clut_offset) {
1010
3
            return false;
1011
3
        }
1012
392
        if (!read_curves(tag->buf, tag->size, a_curve_offset, a2b->input_channels,
1013
392
                         a2b->input_curves)) {
1014
37
            return false;
1015
37
        }
1016
1017
355
        if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout)) {
1018
77
            return false;
1019
77
        }
1020
278
        const CLUT_Layout* clut = (const CLUT_Layout*)(tag->buf + clut_offset);
1021
1022
278
        if (clut->grid_byte_width[0] == 1) {
1023
152
            a2b->grid_8  = clut->variable;
1024
152
            a2b->grid_16 = nullptr;
1025
152
        } else if (clut->grid_byte_width[0] == 2) {
1026
113
            a2b->grid_8  = nullptr;
1027
113
            a2b->grid_16 = clut->variable;
1028
113
        } else {
1029
13
            return false;
1030
13
        }
1031
1032
265
        uint64_t grid_size = a2b->output_channels * clut->grid_byte_width[0];  // the payload
1033
895
        for (uint32_t i = 0; i < a2b->input_channels; ++i) {
1034
632
            a2b->grid_points[i] = clut->grid_points[i];
1035
            // The grid only makes sense with at least two points along each axis
1036
632
            if (a2b->grid_points[i] < 2) {
1037
2
                return false;
1038
2
            }
1039
630
            grid_size *= a2b->grid_points[i];
1040
630
        }
1041
263
        if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout) + grid_size) {
1042
55
            return false;
1043
55
        }
1044
263
    } else {
1045
65
        if (0 != clut_offset) {
1046
51
            return false;
1047
51
        }
1048
1049
        // If there is no CLUT, the number of input and output channels must match
1050
14
        if (a2b->input_channels != a2b->output_channels) {
1051
1
            return false;
1052
1
        }
1053
1054
        // Zero out the number of input channels to signal that we're skipping this stage
1055
13
        a2b->input_channels = 0;
1056
13
    }
1057
1058
221
    return true;
1059
460
}
1060
1061
// Exactly the same as read_tag_mab(), except where there are comments.
1062
// TODO: refactor the two to eliminate common code?
1063
791
static bool read_tag_mba(const skcms_ICCTag* tag, skcms_B2A* b2a, bool pcs_is_xyz) {
1064
791
    if (tag->size < SAFE_SIZEOF(mAB_or_mBA_Layout)) {
1065
5
        return false;
1066
5
    }
1067
1068
786
    const mAB_or_mBA_Layout* mBATag = (const mAB_or_mBA_Layout*)tag->buf;
1069
1070
786
    b2a->input_channels  = mBATag->input_channels[0];
1071
786
    b2a->output_channels = mBATag->output_channels[0];
1072
1073
    // Require exactly 3 inputs (XYZ) and 3 (RGB) or 4 (CMYK) outputs.
1074
786
    if (b2a->input_channels != ARRAY_COUNT(b2a->input_curves)) {
1075
9
        return false;
1076
9
    }
1077
777
    if (b2a->output_channels < 3 || b2a->output_channels > ARRAY_COUNT(b2a->output_curves)) {
1078
11
        return false;
1079
11
    }
1080
1081
766
    uint32_t b_curve_offset = read_big_u32(mBATag->b_curve_offset);
1082
766
    uint32_t matrix_offset  = read_big_u32(mBATag->matrix_offset);
1083
766
    uint32_t m_curve_offset = read_big_u32(mBATag->m_curve_offset);
1084
766
    uint32_t clut_offset    = read_big_u32(mBATag->clut_offset);
1085
766
    uint32_t a_curve_offset = read_big_u32(mBATag->a_curve_offset);
1086
1087
766
    if (0 == b_curve_offset) {
1088
1
        return false;
1089
1
    }
1090
1091
    // "B" curves are our inputs, not outputs.
1092
765
    if (!read_curves(tag->buf, tag->size, b_curve_offset, b2a->input_channels,
1093
765
                     b2a->input_curves)) {
1094
146
        return false;
1095
146
    }
1096
1097
619
    if (0 != m_curve_offset) {
1098
347
        if (0 == matrix_offset) {
1099
5
            return false;
1100
5
        }
1101
        // Matrix channels is tied to input_channels (3), not output_channels.
1102
342
        b2a->matrix_channels = b2a->input_channels;
1103
1104
342
        if (!read_curves(tag->buf, tag->size, m_curve_offset, b2a->matrix_channels,
1105
342
                         b2a->matrix_curves)) {
1106
51
            return false;
1107
51
        }
1108
1109
291
        if (tag->size < matrix_offset + 12 * SAFE_SIZEOF(uint32_t)) {
1110
59
            return false;
1111
59
        }
1112
232
        float encoding_factor = pcs_is_xyz ? (32768 / 65535.0f) : 1.0f;  // TODO: understand
1113
232
        const uint8_t* mtx_buf = tag->buf + matrix_offset;
1114
232
        b2a->matrix.vals[0][0] = encoding_factor * read_big_fixed(mtx_buf +  0);
1115
232
        b2a->matrix.vals[0][1] = encoding_factor * read_big_fixed(mtx_buf +  4);
1116
232
        b2a->matrix.vals[0][2] = encoding_factor * read_big_fixed(mtx_buf +  8);
1117
232
        b2a->matrix.vals[1][0] = encoding_factor * read_big_fixed(mtx_buf + 12);
1118
232
        b2a->matrix.vals[1][1] = encoding_factor * read_big_fixed(mtx_buf + 16);
1119
232
        b2a->matrix.vals[1][2] = encoding_factor * read_big_fixed(mtx_buf + 20);
1120
232
        b2a->matrix.vals[2][0] = encoding_factor * read_big_fixed(mtx_buf + 24);
1121
232
        b2a->matrix.vals[2][1] = encoding_factor * read_big_fixed(mtx_buf + 28);
1122
232
        b2a->matrix.vals[2][2] = encoding_factor * read_big_fixed(mtx_buf + 32);
1123
232
        b2a->matrix.vals[0][3] = encoding_factor * read_big_fixed(mtx_buf + 36);
1124
232
        b2a->matrix.vals[1][3] = encoding_factor * read_big_fixed(mtx_buf + 40);
1125
232
        b2a->matrix.vals[2][3] = encoding_factor * read_big_fixed(mtx_buf + 44);
1126
272
    } else {
1127
272
        if (0 != matrix_offset) {
1128
51
            return false;
1129
51
        }
1130
221
        b2a->matrix_channels = 0;
1131
221
    }
1132
1133
453
    if (0 != a_curve_offset) {
1134
367
        if (0 == clut_offset) {
1135
3
            return false;
1136
3
        }
1137
1138
        // "A" curves are our output, not input.
1139
364
        if (!read_curves(tag->buf, tag->size, a_curve_offset, b2a->output_channels,
1140
364
                         b2a->output_curves)) {
1141
85
            return false;
1142
85
        }
1143
1144
279
        if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout)) {
1145
65
            return false;
1146
65
        }
1147
214
        const CLUT_Layout* clut = (const CLUT_Layout*)(tag->buf + clut_offset);
1148
1149
214
        if (clut->grid_byte_width[0] == 1) {
1150
138
            b2a->grid_8  = clut->variable;
1151
138
            b2a->grid_16 = nullptr;
1152
138
        } else if (clut->grid_byte_width[0] == 2) {
1153
63
            b2a->grid_8  = nullptr;
1154
63
            b2a->grid_16 = clut->variable;
1155
63
        } else {
1156
13
            return false;
1157
13
        }
1158
1159
201
        uint64_t grid_size = b2a->output_channels * clut->grid_byte_width[0];
1160
793
        for (uint32_t i = 0; i < b2a->input_channels; ++i) {
1161
598
            b2a->grid_points[i] = clut->grid_points[i];
1162
598
            if (b2a->grid_points[i] < 2) {
1163
6
                return false;
1164
6
            }
1165
592
            grid_size *= b2a->grid_points[i];
1166
592
        }
1167
195
        if (tag->size < clut_offset + SAFE_FIXED_SIZE(CLUT_Layout) + grid_size) {
1168
45
            return false;
1169
45
        }
1170
195
    } else {
1171
86
        if (0 != clut_offset) {
1172
50
            return false;
1173
50
        }
1174
1175
36
        if (b2a->input_channels != b2a->output_channels) {
1176
4
            return false;
1177
4
        }
1178
1179
        // Zero out *output* channels to skip this stage.
1180
32
        b2a->output_channels = 0;
1181
32
    }
1182
182
    return true;
1183
453
}
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
4.75k
                      float* c, float* d, float* f = nullptr) {
1189
4.75k
    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
4.75k
    const float dx = 1.0f / static_cast<float>(N - 1);
1201
1202
4.75k
    int lin_points = 1;
1203
1204
4.75k
    float f_zero = 0.0f;
1205
4.75k
    if (f) {
1206
2.57k
        *f = eval_curve(curve, 0);
1207
2.57k
    } else {
1208
2.18k
        f = &f_zero;
1209
2.18k
    }
1210
1211
1212
4.75k
    float slope_min = -INFINITY_;
1213
4.75k
    float slope_max = +INFINITY_;
1214
60.3k
    for (int i = 1; i < N; ++i) {
1215
59.4k
        float x = static_cast<float>(i) * dx;
1216
59.4k
        float y = eval_curve(curve, x);
1217
1218
59.4k
        float slope_max_i = (y + tol - *f) / x,
1219
59.4k
              slope_min_i = (y - tol - *f) / x;
1220
59.4k
        if (slope_max_i < slope_min || slope_max < slope_min_i) {
1221
            // Slope intervals would no longer overlap.
1222
3.86k
            break;
1223
3.86k
        }
1224
55.5k
        slope_max = fminf_(slope_max, slope_max_i);
1225
55.5k
        slope_min = fmaxf_(slope_min, slope_min_i);
1226
1227
55.5k
        float cur_slope = (y - *f) / x;
1228
55.5k
        if (slope_min <= cur_slope && cur_slope <= slope_max) {
1229
43.2k
            lin_points = i + 1;
1230
43.2k
            *c = cur_slope;
1231
43.2k
        }
1232
55.5k
    }
1233
1234
    // Set D to the last point that met our tolerance.
1235
4.75k
    *d = static_cast<float>(lin_points - 1) * dx;
1236
4.75k
    return lin_points;
1237
4.75k
}
1238
1239
// If this skcms_Curve holds an identity table, rewrite it as an identity skcms_TransferFunction.
1240
4.05k
static void canonicalize_identity(skcms_Curve* curve) {
1241
4.05k
    if (curve->table_entries && curve->table_entries <= (uint32_t)INT_MAX) {
1242
2.57k
        int N = (int)curve->table_entries;
1243
1244
2.57k
        float c = 0.0f, d = 0.0f, f = 0.0f;
1245
2.57k
        if (N == fit_linear(curve, N, 1.0f/static_cast<float>(2*N), &c,&d,&f)
1246
2.57k
            && c == 1.0f
1247
2.57k
            && f == 0.0f) {
1248
138
            curve->table_entries = 0;
1249
138
            curve->table_8       = nullptr;
1250
138
            curve->table_16      = nullptr;
1251
138
            curve->parametric    = skcms_TransferFunction{1,1,0,0,0,0,0};
1252
138
        }
1253
2.57k
    }
1254
4.05k
}
1255
1256
1.19k
static bool read_a2b(const skcms_ICCTag* tag, skcms_A2B* a2b, bool pcs_is_xyz) {
1257
1.19k
    bool ok = false;
1258
1.19k
    if (tag->type == skcms_Signature_mft1) { ok = read_tag_mft1(tag, a2b); }
1259
1.19k
    if (tag->type == skcms_Signature_mft2) { ok = read_tag_mft2(tag, a2b); }
1260
1.19k
    if (tag->type == skcms_Signature_mAB ) { ok = read_tag_mab(tag, a2b, pcs_is_xyz); }
1261
1.19k
    if (!ok) {
1262
848
        return false;
1263
848
    }
1264
1265
346
    if (a2b->input_channels > 0) { canonicalize_identity(a2b->input_curves + 0); }
1266
346
    if (a2b->input_channels > 1) { canonicalize_identity(a2b->input_curves + 1); }
1267
346
    if (a2b->input_channels > 2) { canonicalize_identity(a2b->input_curves + 2); }
1268
346
    if (a2b->input_channels > 3) { canonicalize_identity(a2b->input_curves + 3); }
1269
1270
346
    if (a2b->matrix_channels > 0) { canonicalize_identity(a2b->matrix_curves + 0); }
1271
346
    if (a2b->matrix_channels > 1) { canonicalize_identity(a2b->matrix_curves + 1); }
1272
346
    if (a2b->matrix_channels > 2) { canonicalize_identity(a2b->matrix_curves + 2); }
1273
1274
346
    if (a2b->output_channels > 0) { canonicalize_identity(a2b->output_curves + 0); }
1275
346
    if (a2b->output_channels > 1) { canonicalize_identity(a2b->output_curves + 1); }
1276
346
    if (a2b->output_channels > 2) { canonicalize_identity(a2b->output_curves + 2); }
1277
1278
346
    return true;
1279
1.19k
}
1280
1281
1.10k
static bool read_b2a(const skcms_ICCTag* tag, skcms_B2A* b2a, bool pcs_is_xyz) {
1282
1.10k
    bool ok = false;
1283
1.10k
    if (tag->type == skcms_Signature_mft1) { ok = read_tag_mft1(tag, b2a); }
1284
1.10k
    if (tag->type == skcms_Signature_mft2) { ok = read_tag_mft2(tag, b2a); }
1285
1.10k
    if (tag->type == skcms_Signature_mBA ) { ok = read_tag_mba(tag, b2a, pcs_is_xyz); }
1286
1.10k
    if (!ok) {
1287
888
        return false;
1288
888
    }
1289
1290
215
    if (b2a->input_channels > 0) { canonicalize_identity(b2a->input_curves + 0); }
1291
215
    if (b2a->input_channels > 1) { canonicalize_identity(b2a->input_curves + 1); }
1292
215
    if (b2a->input_channels > 2) { canonicalize_identity(b2a->input_curves + 2); }
1293
1294
215
    if (b2a->matrix_channels > 0) { canonicalize_identity(b2a->matrix_curves + 0); }
1295
215
    if (b2a->matrix_channels > 1) { canonicalize_identity(b2a->matrix_curves + 1); }
1296
215
    if (b2a->matrix_channels > 2) { canonicalize_identity(b2a->matrix_curves + 2); }
1297
1298
215
    if (b2a->output_channels > 0) { canonicalize_identity(b2a->output_curves + 0); }
1299
215
    if (b2a->output_channels > 1) { canonicalize_identity(b2a->output_curves + 1); }
1300
215
    if (b2a->output_channels > 2) { canonicalize_identity(b2a->output_curves + 2); }
1301
215
    if (b2a->output_channels > 3) { canonicalize_identity(b2a->output_curves + 3); }
1302
1303
215
    return true;
1304
1.10k
}
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
128
static bool read_cicp(const skcms_ICCTag* tag, skcms_CICP* cicp) {
1316
128
    if (tag->type != skcms_Signature_CICP || tag->size < SAFE_SIZEOF(CICP_Layout)) {
1317
111
        return false;
1318
111
    }
1319
1320
17
    const CICP_Layout* cicpTag = (const CICP_Layout*)tag->buf;
1321
1322
17
    cicp->color_primaries          = cicpTag->color_primaries[0];
1323
17
    cicp->transfer_characteristics = cicpTag->transfer_characteristics[0];
1324
17
    cicp->matrix_coefficients      = cicpTag->matrix_coefficients[0];
1325
17
    cicp->video_full_range_flag    = cicpTag->video_full_range_flag[0];
1326
17
    return true;
1327
128
}
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
14.8k
bool skcms_GetTagBySignature(const skcms_ICCProfile* profile, uint32_t sig, skcms_ICCTag* tag) {
1340
14.8k
    if (!profile || !profile->buffer || !tag) { return false; }
1341
14.8k
    const tag_Layout* tags = get_tag_table(profile);
1342
41.7k
    for (uint32_t i = 0; i < profile->tag_count; ++i) {
1343
30.6k
        if (read_big_u32(tags[i].signature) == sig) {
1344
3.80k
            tag->signature = sig;
1345
3.80k
            tag->size      = read_big_u32(tags[i].size);
1346
3.80k
            tag->buf       = read_big_u32(tags[i].offset) + profile->buffer;
1347
3.80k
            tag->type      = read_big_u32(tag->buf);
1348
3.80k
            return true;
1349
3.80k
        }
1350
30.6k
    }
1351
11.0k
    return false;
1352
14.8k
}
1353
1354
843
static bool usable_as_src(const skcms_ICCProfile* profile) {
1355
843
    return profile->has_A2B
1356
843
       || (profile->has_trc && profile->has_toXYZD50);
1357
843
}
1358
1359
bool skcms_ParseWithA2BPriority(const void* buf, size_t len,
1360
                                const int priority[], const int priorities,
1361
3.28k
                                skcms_ICCProfile* profile) {
1362
3.28k
    static_assert(SAFE_SIZEOF(header_Layout) == 132, "need to update header code");
1363
1364
3.28k
    if (!profile) {
1365
0
        return false;
1366
0
    }
1367
3.28k
    memset(profile, 0, SAFE_SIZEOF(*profile));
1368
1369
3.28k
    if (len < SAFE_SIZEOF(header_Layout)) {
1370
14
        return false;
1371
14
    }
1372
1373
    // Byte-swap all header fields
1374
3.26k
    const header_Layout* header  = (const header_Layout*)buf;
1375
3.26k
    profile->buffer              = (const uint8_t*)buf;
1376
3.26k
    profile->size                = read_big_u32(header->size);
1377
3.26k
    uint32_t version             = read_big_u32(header->version);
1378
3.26k
    profile->data_color_space    = read_big_u32(header->data_color_space);
1379
3.26k
    profile->pcs                 = read_big_u32(header->pcs);
1380
3.26k
    uint32_t signature           = read_big_u32(header->signature);
1381
3.26k
    float illuminant_X           = read_big_fixed(header->illuminant_X);
1382
3.26k
    float illuminant_Y           = read_big_fixed(header->illuminant_Y);
1383
3.26k
    float illuminant_Z           = read_big_fixed(header->illuminant_Z);
1384
3.26k
    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
3.26k
    uint64_t tag_table_size = profile->tag_count * SAFE_SIZEOF(tag_Layout);
1389
3.26k
    if (signature != skcms_Signature_acsp ||
1390
3.26k
        profile->size > len ||
1391
3.26k
        profile->size < SAFE_SIZEOF(header_Layout) + tag_table_size ||
1392
3.26k
        (version >> 24) > 4) {
1393
176
        return false;
1394
176
    }
1395
1396
    // Validate that illuminant is D50 white
1397
3.09k
    if (fabsf_(illuminant_X - 0.9642f) > 0.0100f ||
1398
3.09k
        fabsf_(illuminant_Y - 1.0000f) > 0.0100f ||
1399
3.09k
        fabsf_(illuminant_Z - 0.8249f) > 0.0100f) {
1400
3
        return false;
1401
3
    }
1402
1403
    // Validate that all tag entries have sane offset + size
1404
3.09k
    const tag_Layout* tags = get_tag_table(profile);
1405
25.8k
    for (uint32_t i = 0; i < profile->tag_count; ++i) {
1406
22.9k
        uint32_t tag_offset = read_big_u32(tags[i].offset);
1407
22.9k
        uint32_t tag_size   = read_big_u32(tags[i].size);
1408
22.9k
        uint64_t tag_end    = (uint64_t)tag_offset + (uint64_t)tag_size;
1409
22.9k
        if (tag_size < 4 || tag_end > profile->size) {
1410
106
            return false;
1411
106
        }
1412
22.9k
    }
1413
1414
2.98k
    if (profile->pcs != skcms_Signature_XYZ && profile->pcs != skcms_Signature_Lab) {
1415
71
        return false;
1416
71
    }
1417
1418
2.91k
    bool pcs_is_xyz = profile->pcs == skcms_Signature_XYZ;
1419
1420
    // Pre-parse commonly used tags.
1421
2.91k
    skcms_ICCTag kTRC;
1422
2.91k
    if (profile->data_color_space == skcms_Signature_Gray &&
1423
2.91k
        skcms_GetTagBySignature(profile, skcms_Signature_kTRC, &kTRC)) {
1424
339
        if (!read_curve(kTRC.buf, kTRC.size, &profile->trc[0], nullptr)) {
1425
            // Malformed tag
1426
49
            return false;
1427
49
        }
1428
290
        profile->trc[1] = profile->trc[0];
1429
290
        profile->trc[2] = profile->trc[0];
1430
290
        profile->has_trc = true;
1431
1432
290
        if (pcs_is_xyz) {
1433
279
            profile->toXYZD50.vals[0][0] = illuminant_X;
1434
279
            profile->toXYZD50.vals[1][1] = illuminant_Y;
1435
279
            profile->toXYZD50.vals[2][2] = illuminant_Z;
1436
279
            profile->has_toXYZD50 = true;
1437
279
        }
1438
2.57k
    } else {
1439
2.57k
        skcms_ICCTag rTRC, gTRC, bTRC;
1440
2.57k
        if (skcms_GetTagBySignature(profile, skcms_Signature_rTRC, &rTRC) &&
1441
2.57k
            skcms_GetTagBySignature(profile, skcms_Signature_gTRC, &gTRC) &&
1442
2.57k
            skcms_GetTagBySignature(profile, skcms_Signature_bTRC, &bTRC)) {
1443
125
            if (!read_curve(rTRC.buf, rTRC.size, &profile->trc[0], nullptr) ||
1444
125
                !read_curve(gTRC.buf, gTRC.size, &profile->trc[1], nullptr) ||
1445
125
                !read_curve(bTRC.buf, bTRC.size, &profile->trc[2], nullptr)) {
1446
                // Malformed TRC tags
1447
5
                return false;
1448
5
            }
1449
120
            profile->has_trc = true;
1450
120
        }
1451
1452
2.56k
        skcms_ICCTag rXYZ, gXYZ, bXYZ;
1453
2.56k
        if (skcms_GetTagBySignature(profile, skcms_Signature_rXYZ, &rXYZ) &&
1454
2.56k
            skcms_GetTagBySignature(profile, skcms_Signature_gXYZ, &gXYZ) &&
1455
2.56k
            skcms_GetTagBySignature(profile, skcms_Signature_bXYZ, &bXYZ)) {
1456
193
            if (!read_to_XYZD50(&rXYZ, &gXYZ, &bXYZ, &profile->toXYZD50)) {
1457
                // Malformed XYZ tags
1458
169
                return false;
1459
169
            }
1460
24
            profile->has_toXYZD50 = true;
1461
24
        }
1462
2.56k
    }
1463
1464
6.13k
    for (int i = 0; i < priorities; i++) {
1465
        // enum { perceptual, relative_colormetric, saturation }
1466
4.64k
        if (priority[i] < 0 || priority[i] > 2) {
1467
0
            return false;
1468
0
        }
1469
4.64k
        uint32_t sig = skcms_Signature_A2B0 + static_cast<uint32_t>(priority[i]);
1470
4.64k
        skcms_ICCTag tag;
1471
4.64k
        if (skcms_GetTagBySignature(profile, sig, &tag)) {
1472
1.19k
            if (!read_a2b(&tag, &profile->A2B, pcs_is_xyz)) {
1473
                // Malformed A2B tag
1474
848
                return false;
1475
848
            }
1476
346
            profile->has_A2B = true;
1477
346
            break;
1478
1.19k
        }
1479
4.64k
    }
1480
1481
3.77k
    for (int i = 0; i < priorities; i++) {
1482
        // enum { perceptual, relative_colormetric, saturation }
1483
3.03k
        if (priority[i] < 0 || priority[i] > 2) {
1484
0
            return false;
1485
0
        }
1486
3.03k
        uint32_t sig = skcms_Signature_B2A0 + static_cast<uint32_t>(priority[i]);
1487
3.03k
        skcms_ICCTag tag;
1488
3.03k
        if (skcms_GetTagBySignature(profile, sig, &tag)) {
1489
1.10k
            if (!read_b2a(&tag, &profile->B2A, pcs_is_xyz)) {
1490
                // Malformed B2A tag
1491
888
                return false;
1492
888
            }
1493
215
            profile->has_B2A = true;
1494
215
            break;
1495
1.10k
        }
1496
3.03k
    }
1497
1498
954
    skcms_ICCTag cicp_tag;
1499
954
    if (skcms_GetTagBySignature(profile, skcms_Signature_CICP, &cicp_tag)) {
1500
128
        if (!read_cicp(&cicp_tag, &profile->CICP)) {
1501
            // Malformed CICP tag
1502
111
            return false;
1503
111
        }
1504
17
        profile->has_CICP = true;
1505
17
    }
1506
1507
843
    return usable_as_src(profile);
1508
954
}
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
0
const skcms_ICCProfile* skcms_XYZD50_profile() {
1610
    // Just like sRGB above, but with identity transfer functions and toXYZD50 matrix.
1611
0
    static const skcms_ICCProfile XYZD50_profile = {
1612
0
        nullptr,               // buffer, moot here
1613
1614
0
        0,                     // size, moot here
1615
0
        skcms_Signature_RGB,   // data_color_space
1616
0
        skcms_Signature_XYZ,   // pcs
1617
0
        0,                     // tag count, moot here
1618
1619
0
        {   // the 3 trc curves
1620
0
            {{0, {1,1, 0,0,0,0,0}}},
1621
0
            {{0, {1,1, 0,0,0,0,0}}},
1622
0
            {{0, {1,1, 0,0,0,0,0}}},
1623
0
        },
1624
1625
0
        {{  // 3x3 toXYZD50 matrix
1626
0
            { 1,0,0 },
1627
0
            { 0,1,0 },
1628
0
            { 0,0,1 },
1629
0
        }},
1630
1631
0
        {   // an empty A2B
1632
0
            {   // input_curves
1633
0
                {{0, {0,0, 0,0,0,0,0}}},
1634
0
                {{0, {0,0, 0,0,0,0,0}}},
1635
0
                {{0, {0,0, 0,0,0,0,0}}},
1636
0
                {{0, {0,0, 0,0,0,0,0}}},
1637
0
            },
1638
0
            nullptr,   // grid_8
1639
0
            nullptr,   // grid_16
1640
0
            0,         // input_channels
1641
0
            {0,0,0,0}, // grid_points
1642
1643
0
            {   // matrix_curves
1644
0
                {{0, {0,0, 0,0,0,0,0}}},
1645
0
                {{0, {0,0, 0,0,0,0,0}}},
1646
0
                {{0, {0,0, 0,0,0,0,0}}},
1647
0
            },
1648
0
            {{  // matrix (3x4)
1649
0
                { 0,0,0,0 },
1650
0
                { 0,0,0,0 },
1651
0
                { 0,0,0,0 },
1652
0
            }},
1653
0
            0,  // matrix_channels
1654
1655
0
            0,  // output_channels
1656
0
            {   // output_curves
1657
0
                {{0, {0,0, 0,0,0,0,0}}},
1658
0
                {{0, {0,0, 0,0,0,0,0}}},
1659
0
                {{0, {0,0, 0,0,0,0,0}}},
1660
0
            },
1661
0
        },
1662
1663
0
        {   // an empty B2A
1664
0
            {   // input_curves
1665
0
                {{0, {0,0, 0,0,0,0,0}}},
1666
0
                {{0, {0,0, 0,0,0,0,0}}},
1667
0
                {{0, {0,0, 0,0,0,0,0}}},
1668
0
            },
1669
0
            0,  // input_channels
1670
1671
0
            0,  // matrix_channels
1672
0
            {   // matrix_curves
1673
0
                {{0, {0,0, 0,0,0,0,0}}},
1674
0
                {{0, {0,0, 0,0,0,0,0}}},
1675
0
                {{0, {0,0, 0,0,0,0,0}}},
1676
0
            },
1677
0
            {{  // matrix (3x4)
1678
0
                { 0,0,0,0 },
1679
0
                { 0,0,0,0 },
1680
0
                { 0,0,0,0 },
1681
0
            }},
1682
1683
0
            {   // output_curves
1684
0
                {{0, {0,0, 0,0,0,0,0}}},
1685
0
                {{0, {0,0, 0,0,0,0,0}}},
1686
0
                {{0, {0,0, 0,0,0,0,0}}},
1687
0
                {{0, {0,0, 0,0,0,0,0}}},
1688
0
            },
1689
0
            nullptr,    // grid_8
1690
0
            nullptr,    // grid_16
1691
0
            {0,0,0,0},  // grid_points
1692
0
            0,          // output_channels
1693
0
        },
1694
1695
0
        { 0, 0, 0, 0 },  // an empty CICP
1696
1697
0
        true,  // has_trc
1698
0
        true,  // has_toXYZD50
1699
0
        false, // has_A2B
1700
0
        false, // has B2A
1701
0
        false, // has_CICP
1702
0
    };
1703
1704
0
    return &XYZD50_profile;
1705
0
}
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
0
bool skcms_ApproximatelyEqualProfiles(const skcms_ICCProfile* A, const skcms_ICCProfile* B) {
1742
    // Test for exactly equal profiles first.
1743
0
    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
0
    const auto CMYK = skcms_Signature_CMYK;
1760
0
    if ((A->data_color_space == CMYK) != (B->data_color_space == CMYK)) {
1761
0
        return false;
1762
0
    }
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
0
    skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
1767
0
    size_t npixels = 84;
1768
0
    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
0
    uint8_t dstA[252],
1776
0
            dstB[252];
1777
0
    if (!skcms_Transform(
1778
0
                skcms_252_random_bytes,     fmt, skcms_AlphaFormat_Unpremul, A,
1779
0
                dstA, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1780
0
                npixels)) {
1781
0
        return false;
1782
0
    }
1783
0
    if (!skcms_Transform(
1784
0
                skcms_252_random_bytes,     fmt, skcms_AlphaFormat_Unpremul, B,
1785
0
                dstB, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
1786
0
                npixels)) {
1787
0
        return false;
1788
0
    }
1789
1790
    // TODO: make sure this final check has reasonable codegen.
1791
0
    for (size_t i = 0; i < 252; i++) {
1792
0
        if (abs((int)dstA[i] - (int)dstB[i]) > 1) {
1793
0
            return false;
1794
0
        }
1795
0
    }
1796
0
    return true;
1797
0
}
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
8.99k
static skcms_Vector3 mv_mul(const skcms_Matrix3x3* m, const skcms_Vector3* v) {
1817
8.99k
    skcms_Vector3 dst = {{0,0,0}};
1818
35.9k
    for (int row = 0; row < 3; ++row) {
1819
26.9k
        dst.vals[row] = m->vals[row][0] * v->vals[0]
1820
26.9k
                      + m->vals[row][1] * v->vals[1]
1821
26.9k
                      + m->vals[row][2] * v->vals[2];
1822
26.9k
    }
1823
8.99k
    return dst;
1824
8.99k
}
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
9.53k
bool skcms_Matrix3x3_invert(const skcms_Matrix3x3* src, skcms_Matrix3x3* dst) {
1913
9.53k
    double a00 = src->vals[0][0],
1914
9.53k
           a01 = src->vals[1][0],
1915
9.53k
           a02 = src->vals[2][0],
1916
9.53k
           a10 = src->vals[0][1],
1917
9.53k
           a11 = src->vals[1][1],
1918
9.53k
           a12 = src->vals[2][1],
1919
9.53k
           a20 = src->vals[0][2],
1920
9.53k
           a21 = src->vals[1][2],
1921
9.53k
           a22 = src->vals[2][2];
1922
1923
9.53k
    double b0 = a00*a11 - a01*a10,
1924
9.53k
           b1 = a00*a12 - a02*a10,
1925
9.53k
           b2 = a01*a12 - a02*a11,
1926
9.53k
           b3 = a20,
1927
9.53k
           b4 = a21,
1928
9.53k
           b5 = a22;
1929
1930
9.53k
    double determinant = b0*b5
1931
9.53k
                       - b1*b4
1932
9.53k
                       + b2*b3;
1933
1934
9.53k
    if (determinant == 0) {
1935
73
        return false;
1936
73
    }
1937
1938
9.45k
    double invdet = 1.0 / determinant;
1939
9.45k
    if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
1940
415
        return false;
1941
415
    }
1942
1943
9.04k
    b0 *= invdet;
1944
9.04k
    b1 *= invdet;
1945
9.04k
    b2 *= invdet;
1946
9.04k
    b3 *= invdet;
1947
9.04k
    b4 *= invdet;
1948
9.04k
    b5 *= invdet;
1949
1950
9.04k
    dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
1951
9.04k
    dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
1952
9.04k
    dst->vals[2][0] = (float)(        +     b2 );
1953
9.04k
    dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
1954
9.04k
    dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
1955
9.04k
    dst->vals[2][1] = (float)(        -     b1 );
1956
9.04k
    dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
1957
9.04k
    dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
1958
9.04k
    dst->vals[2][2] = (float)(        +     b0 );
1959
1960
36.0k
    for (int r = 0; r < 3; ++r)
1961
108k
    for (int c = 0; c < 3; ++c) {
1962
81.0k
        if (!isfinitef_(dst->vals[r][c])) {
1963
52
            return false;
1964
52
        }
1965
81.0k
    }
1966
8.99k
    return true;
1967
9.04k
}
1968
1969
0
skcms_Matrix3x3 skcms_Matrix3x3_concat(const skcms_Matrix3x3* A, const skcms_Matrix3x3* B) {
1970
0
    skcms_Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
1971
0
    for (int r = 0; r < 3; r++)
1972
0
        for (int c = 0; c < 3; c++) {
1973
0
            m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
1974
0
                         + A->vals[r][1] * B->vals[1][c]
1975
0
                         + A->vals[r][2] * B->vals[2][c];
1976
0
        }
1977
0
    return m;
1978
0
}
1979
1980
#if defined(__clang__)
1981
    [[clang::no_sanitize("float-divide-by-zero")]]  // Checked for by classify() on the way out.
1982
#endif
1983
22.3k
bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
1984
22.3k
    TF_PQish  pq;
1985
22.3k
    TF_HLGish hlg;
1986
22.3k
    switch (classify(*src, &pq, &hlg)) {
1987
2.29k
        case skcms_TFType_Invalid: return false;
1988
99
        case skcms_TFType_PQ:      return false;
1989
102
        case skcms_TFType_HLG:     return false;
1990
19.3k
        case skcms_TFType_sRGBish: break;  // handled below
1991
1992
104
        case skcms_TFType_PQish:
1993
104
            *dst = { TFKind_marker(skcms_TFType_PQish), -pq.A,  pq.D, 1.0f/pq.F
1994
104
                                                      ,  pq.B, -pq.E, 1.0f/pq.C};
1995
104
            return true;
1996
1997
221
        case skcms_TFType_HLGish:
1998
221
            *dst = { TFKind_marker(skcms_TFType_HLGinvish), 1.0f/hlg.R, 1.0f/hlg.G
1999
221
                                                          , 1.0f/hlg.a, hlg.b, hlg.c
2000
221
                                                          , hlg.K_minus_1 };
2001
221
            return true;
2002
2003
98
        case skcms_TFType_HLGinvish:
2004
98
            *dst = { TFKind_marker(skcms_TFType_HLGish), 1.0f/hlg.R, 1.0f/hlg.G
2005
98
                                                       , 1.0f/hlg.a, hlg.b, hlg.c
2006
98
                                                       , hlg.K_minus_1 };
2007
98
            return true;
2008
22.3k
    }
2009
2010
19.3k
    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
19.3k
    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
19.3k
    float d_l =       src->c * src->d + src->f,
2022
19.3k
          d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
2023
19.3k
    if (fabsf_(d_l - d_r) > 1/512.0f) {
2024
844
        return false;
2025
844
    }
2026
18.5k
    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
18.5k
    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
13.6k
        inv.c =    1.0f/src->c;
2035
13.6k
        inv.f = -src->f/src->c;
2036
13.6k
    }
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
18.5k
    float k = powf_(src->a, -src->g);  // (1/a)^g == a^-g
2051
18.5k
    inv.g = 1.0f / src->g;
2052
18.5k
    inv.a = k;
2053
18.5k
    inv.b = -k * src->e;
2054
18.5k
    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
18.5k
    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
18.5k
    if (inv.a * inv.d + inv.b < 0) {
2066
1.02k
        inv.b = -inv.a * inv.d;
2067
1.02k
    }
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
18.5k
    if (classify(inv) != skcms_TFType_sRGBish) {
2072
518
        return false;
2073
518
    }
2074
2075
18.0k
    assert (inv.a >= 0);
2076
18.0k
    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
18.0k
    float s = skcms_TransferFunction_eval(src, 1.0f);
2082
18.0k
    if (!isfinitef_(s)) {
2083
230
        return false;
2084
230
    }
2085
2086
17.7k
    float sign = s < 0 ? -1.0f : 1.0f;
2087
17.7k
    s *= sign;
2088
17.7k
    if (s < inv.d) {
2089
778
        inv.f = 1.0f - sign * inv.c * s;
2090
17.0k
    } else {
2091
17.0k
        inv.e = 1.0f - sign * powf_(inv.a * s + inv.b, inv.g);
2092
17.0k
    }
2093
2094
17.7k
    *dst = inv;
2095
17.7k
    return classify(*dst) == skcms_TFType_sRGBish;
2096
18.0k
}
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
3.73M
                          float dfdP[3]) {
2141
3.73M
    const float y = eval_curve(curve, x);
2142
2143
3.73M
    const float g = tf->g, a = tf->a, b = tf->b,
2144
3.73M
                c = tf->c, d = tf->d, f = tf->f;
2145
2146
3.73M
    const float Y = fmaxf_(a*y + b, 0.0f),
2147
3.73M
                D =        a*d + b;
2148
3.73M
    assert (D >= 0);
2149
2150
    // The gradient.
2151
3.73M
    dfdP[0] = logf_(Y)*powf_(Y, g)
2152
3.73M
            - logf_(D)*powf_(D, g);
2153
3.73M
    dfdP[1] = y*g*powf_(Y, g-1)
2154
3.73M
            - d*g*powf_(D, g-1);
2155
3.73M
    dfdP[2] =   g*powf_(Y, g-1)
2156
3.73M
            -   g*powf_(D, g-1);
2157
2158
    // The residual.
2159
3.73M
    const float f_inv = powf_(Y, g)
2160
3.73M
                      - powf_(D, g)
2161
3.73M
                      + c*d + f;
2162
3.73M
    return x - f_inv;
2163
3.73M
}
2164
2165
static bool gauss_newton_step(const skcms_Curve* curve,
2166
                                    skcms_TransferFunction* tf,
2167
9.53k
                              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
9.53k
    skcms_Matrix3x3 lhs = {{ {0,0,0}, {0,0,0}, {0,0,0} }};
2204
9.53k
    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
3.74M
    for (int i = 0; i < N; i++) {
2210
3.73M
        float x = x0 + static_cast<float>(i)*dx;
2211
2212
3.73M
        float dfdP[3] = {0,0,0};
2213
3.73M
        float resid = rg_nonlinear(x,curve,tf, dfdP);
2214
2215
14.9M
        for (int r = 0; r < 3; r++) {
2216
44.7M
            for (int c = 0; c < 3; c++) {
2217
33.5M
                lhs.vals[r][c] += dfdP[r] * dfdP[c];
2218
33.5M
            }
2219
11.1M
            rhs.vals[r] += dfdP[r] * resid;
2220
11.1M
        }
2221
3.73M
    }
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
38.1k
    for (int k = 0; k < 3; k++) {
2226
28.5k
        if (lhs.vals[0][k]==0 && lhs.vals[1][k]==0 && lhs.vals[2][k]==0 &&
2227
28.5k
            lhs.vals[k][0]==0 && lhs.vals[k][1]==0 && lhs.vals[k][2]==0) {
2228
4.79k
            lhs.vals[k][k] = 1;
2229
4.79k
        }
2230
28.5k
    }
2231
2232
    // 3) invert lhs
2233
9.53k
    skcms_Matrix3x3 lhs_inv;
2234
9.53k
    if (!skcms_Matrix3x3_invert(&lhs, &lhs_inv)) {
2235
540
        return false;
2236
540
    }
2237
2238
    // 4) multiply inverse lhs by rhs
2239
8.99k
    skcms_Vector3 dP = mv_mul(&lhs_inv, &rhs);
2240
8.99k
    tf->g += dP.vals[0];
2241
8.99k
    tf->a += dP.vals[1];
2242
8.99k
    tf->b += dP.vals[2];
2243
8.99k
    return isfinitef_(tf->g) && isfinitef_(tf->a) && isfinitef_(tf->b);
2244
9.53k
}
2245
2246
static float max_roundtrip_error_checked(const skcms_Curve* curve,
2247
10.2k
                                         const skcms_TransferFunction* tf_inv) {
2248
10.2k
    skcms_TransferFunction tf;
2249
10.2k
    if (!skcms_TransferFunction_invert(tf_inv, &tf) || skcms_TFType_sRGBish != classify(tf)) {
2250
3.79k
        return INFINITY_;
2251
3.79k
    }
2252
2253
6.46k
    skcms_TransferFunction tf_inv_again;
2254
6.46k
    if (!skcms_TransferFunction_invert(&tf, &tf_inv_again)) {
2255
891
        return INFINITY_;
2256
891
    }
2257
2258
5.57k
    return skcms_MaxRoundtripError(curve, &tf_inv_again);
2259
6.46k
}
2260
2261
// Fit the points in [L,N) to the non-linear piece of tf, or return false if we can't.
2262
1.97k
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
10.9k
    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
10.9k
        if (tf->a < 0) {
2268
617
            return false;
2269
617
        }
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
10.3k
        if (tf->a * tf->d + tf->b < 0) {
2273
1.00k
            tf->b = -tf->a * tf->d;
2274
1.00k
        }
2275
10.3k
        assert (tf->a >= 0 &&
2276
10.3k
                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
10.3k
        tf->e =   tf->c*tf->d + tf->f
2281
10.3k
          - powf_(tf->a*tf->d + tf->b, tf->g);
2282
2283
10.3k
        return isfinitef_(tf->e);
2284
10.3k
    };
2285
2286
1.97k
    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
1.97k
    const float dx = 1.0f / static_cast<float>(N-1);
2292
2293
1.97k
    skcms_TransferFunction best_tf = *tf;
2294
1.97k
    float best_max_error = INFINITY_;
2295
2296
    // Need this or several curves get worse... *sigh*
2297
1.97k
    float init_error = max_roundtrip_error_checked(curve, tf);
2298
1.97k
    if (init_error < best_max_error) {
2299
1.72k
        best_max_error = init_error;
2300
1.72k
        best_tf = *tf;
2301
1.72k
    }
2302
2303
    // As far as we can tell, 1 Gauss-Newton step won't converge, and 3 steps is no better than 2.
2304
10.2k
    for (int j = 0; j < 8; j++) {
2305
9.53k
        if (!gauss_newton_step(curve, tf, static_cast<float>(L)*dx, dx, N-L) || !fixup_tf()) {
2306
1.24k
            *tf = best_tf;
2307
1.24k
            return isfinitef_(best_max_error);
2308
1.24k
        }
2309
2310
8.28k
        float max_error = max_roundtrip_error_checked(curve, tf);
2311
8.28k
        if (max_error < best_max_error) {
2312
1.87k
            best_max_error = max_error;
2313
1.87k
            best_tf = *tf;
2314
1.87k
        }
2315
8.28k
    }
2316
2317
728
    *tf = best_tf;
2318
728
    return isfinitef_(best_max_error);
2319
1.97k
}
2320
2321
bool skcms_ApproximateCurve(const skcms_Curve* curve,
2322
                            skcms_TransferFunction* approx,
2323
1.75k
                            float* max_error) {
2324
1.75k
    if (!curve || !approx || !max_error) {
2325
0
        return false;
2326
0
    }
2327
2328
1.75k
    if (curve->table_entries == 0) {
2329
        // No point approximating an skcms_TransferFunction with an skcms_TransferFunction!
2330
668
        return false;
2331
668
    }
2332
2333
1.09k
    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
1.09k
    int N = (int)curve->table_entries;
2339
1.09k
    const float dx = 1.0f / static_cast<float>(N - 1);
2340
2341
1.09k
    *max_error = INFINITY_;
2342
1.09k
    const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
2343
3.27k
    for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
2344
2.18k
        skcms_TransferFunction tf,
2345
2.18k
                               tf_inv;
2346
2347
        // It's problematic to fit curves with non-zero f, so always force it to zero explicitly.
2348
2.18k
        tf.f = 0.0f;
2349
2.18k
        int L = fit_linear(curve, N, kTolerances[t], &tf.c, &tf.d);
2350
2351
2.18k
        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
89
            tf.g = 1;
2355
89
            tf.a = tf.c;
2356
89
            tf.b = tf.f;
2357
89
            tf.c = tf.d = tf.e = tf.f = 0;
2358
2.09k
        } else if (L == N - 1) {
2359
            // Degenerate case with only two points in the nonlinear segment. Solve directly.
2360
119
            tf.g = 1;
2361
119
            tf.a = (eval_curve(curve, static_cast<float>(N-1)*dx) -
2362
119
                    eval_curve(curve, static_cast<float>(N-2)*dx))
2363
119
                 / dx;
2364
119
            tf.b = eval_curve(curve, static_cast<float>(N-2)*dx)
2365
119
                 - tf.a * static_cast<float>(N-2)*dx;
2366
119
            tf.e = 0;
2367
1.97k
        } else {
2368
            // Start by guessing a gamma-only curve through the midpoint.
2369
1.97k
            int mid = (L + N) / 2;
2370
1.97k
            float mid_x = static_cast<float>(mid) / static_cast<float>(N - 1);
2371
1.97k
            float mid_y = eval_curve(curve, mid_x);
2372
1.97k
            tf.g = log2f_(mid_y) / log2f_(mid_x);
2373
1.97k
            tf.a = 1;
2374
1.97k
            tf.b = 0;
2375
1.97k
            tf.e =    tf.c*tf.d + tf.f
2376
1.97k
              - powf_(tf.a*tf.d + tf.b, tf.g);
2377
2378
2379
1.97k
            if (!skcms_TransferFunction_invert(&tf, &tf_inv) ||
2380
1.97k
                !fit_nonlinear(curve, L,N, &tf_inv)) {
2381
232
                continue;
2382
232
            }
2383
2384
            // We fit tf_inv, so calculate tf to keep in sync.
2385
            // fit_nonlinear() should guarantee invertibility.
2386
1.74k
            if (!skcms_TransferFunction_invert(&tf_inv, &tf)) {
2387
0
                assert(false);
2388
0
                continue;
2389
0
            }
2390
1.74k
        }
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
1.94k
        if (skcms_TFType_sRGBish != classify(tf)) {
2397
73
            continue;
2398
73
        }
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
1.87k
        if (!skcms_TransferFunction_invert(&tf, &tf_inv)) {
2410
30
            continue;
2411
30
        }
2412
2413
1.84k
        float err = skcms_MaxRoundtripError(curve, &tf_inv);
2414
1.84k
        if (*max_error > err) {
2415
1.18k
            *max_error = err;
2416
1.18k
            *approx    = tf;
2417
1.18k
        }
2418
1.84k
    }
2419
1.09k
    return isfinitef_(*max_error);
2420
1.09k
}
2421
2422
enum class CpuType { Baseline, HSW, SKX };
2423
2424
0
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
0
        static const CpuType type = []{
2433
0
            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
0
            uint32_t eax, ebx, ecx, edx;
2440
0
            __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2441
0
                                         : "0"(1), "2"(0));
2442
0
            if ((edx & (1u<<25)) &&  // SSE
2443
0
                (edx & (1u<<26)) &&  // SSE2
2444
0
                (ecx & (1u<< 0)) &&  // SSE3
2445
0
                (ecx & (1u<< 9)) &&  // SSSE3
2446
0
                (ecx & (1u<<12)) &&  // FMA (N.B. not used, avoided even)
2447
0
                (ecx & (1u<<19)) &&  // SSE4.1
2448
0
                (ecx & (1u<<20)) &&  // SSE4.2
2449
0
                (ecx & (1u<<26)) &&  // XSAVE
2450
0
                (ecx & (1u<<27)) &&  // OSXSAVE
2451
0
                (ecx & (1u<<28)) &&  // AVX
2452
0
                (ecx & (1u<<29))) {  // F16C
2453
2454
                // Call cpuid(7) to check for AVX2 and AVX-512 bits.
2455
0
                __asm__ __volatile__("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
2456
0
                                             : "0"(7), "2"(0));
2457
                // eax from xgetbv(0) will tell us whether XMM, YMM, and ZMM state is saved.
2458
0
                uint32_t xcr0, dont_need_edx;
2459
0
                __asm__ __volatile__("xgetbv" : "=a"(xcr0), "=d"(dont_need_edx) : "c"(0));
2460
2461
0
                if ((xcr0 & (1u<<1)) &&  // XMM register state saved?
2462
0
                    (xcr0 & (1u<<2)) &&  // YMM register state saved?
2463
0
                    (ebx  & (1u<<5))) {  // AVX2
2464
                    // At this point we're at least HSW.  Continue checking for SKX.
2465
0
                    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
0
                    return CpuType::HSW;
2476
0
                }
2477
0
            }
2478
0
            return CpuType::Baseline;
2479
0
        }();
2480
0
        return type;
2481
0
    #endif
2482
0
}
2483
2484
0
static bool tf_is_gamma(const skcms_TransferFunction& tf) {
2485
0
    return tf.g > 0 && tf.a == 1 &&
2486
0
           tf.b == 0 && tf.c == 0 && tf.d == 0 && tf.e == 0 && tf.f == 0;
2487
0
}
2488
2489
struct OpAndArg {
2490
    Op          op;
2491
    const void* arg;
2492
};
2493
2494
0
static OpAndArg select_curve_op(const skcms_Curve* curve, int channel) {
2495
0
    struct OpType {
2496
0
        Op sGamma, sRGBish, PQish, HLGish, HLGinvish, table;
2497
0
    };
2498
0
    static constexpr OpType kOps[] = {
2499
0
        { Op::gamma_r, Op::tf_r, Op::pq_r, Op::hlg_r, Op::hlginv_r, Op::table_r },
2500
0
        { Op::gamma_g, Op::tf_g, Op::pq_g, Op::hlg_g, Op::hlginv_g, Op::table_g },
2501
0
        { Op::gamma_b, Op::tf_b, Op::pq_b, Op::hlg_b, Op::hlginv_b, Op::table_b },
2502
0
        { Op::gamma_a, Op::tf_a, Op::pq_a, Op::hlg_a, Op::hlginv_a, Op::table_a },
2503
0
    };
2504
0
    const auto& op = kOps[channel];
2505
2506
0
    if (curve->table_entries == 0) {
2507
0
        const OpAndArg noop = { Op::load_a8/*doesn't matter*/, nullptr };
2508
2509
0
        const skcms_TransferFunction& tf = curve->parametric;
2510
2511
0
        if (tf_is_gamma(tf)) {
2512
0
            return tf.g != 1 ? OpAndArg{op.sGamma, &tf}
2513
0
                             : noop;
2514
0
        }
2515
2516
0
        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
0
            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
0
        }
2528
0
    }
2529
0
    return OpAndArg{op.table, curve};
2530
0
}
2531
2532
0
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
0
    int cursor = 0;
2536
0
    for (int index = numChannels; index-- > 0; ) {
2537
0
        ops[cursor] = select_curve_op(&curves[index], index);
2538
0
        if (ops[cursor].arg) {
2539
0
            ++cursor;
2540
0
        }
2541
0
    }
2542
2543
    // Identify separate B+G+R ops and fuse them into a single RGB op.
2544
0
    if (cursor >= 3) {
2545
0
        struct FusableOps {
2546
0
            Op r, g, b, rgb;
2547
0
        };
2548
0
        static constexpr FusableOps kFusableOps[] = {
2549
0
            {Op::gamma_r,  Op::gamma_g,  Op::gamma_b,  Op::gamma_rgb},
2550
0
            {Op::tf_r,     Op::tf_g,     Op::tf_b,     Op::tf_rgb},
2551
0
            {Op::pq_r,     Op::pq_g,     Op::pq_b,     Op::pq_rgb},
2552
0
            {Op::hlg_r,    Op::hlg_g,    Op::hlg_b,    Op::hlg_rgb},
2553
0
            {Op::hlginv_r, Op::hlginv_g, Op::hlginv_b, Op::hlginv_rgb},
2554
0
        };
2555
2556
0
        int posR = cursor - 1;
2557
0
        int posG = cursor - 2;
2558
0
        int posB = cursor - 3;
2559
0
        for (const FusableOps& fusableOp : kFusableOps) {
2560
0
            if (ops[posR].op == fusableOp.r &&
2561
0
                ops[posG].op == fusableOp.g &&
2562
0
                ops[posB].op == fusableOp.b &&
2563
0
                (0 == memcmp(ops[posR].arg, ops[posG].arg, sizeof(skcms_TransferFunction))) &&
2564
0
                (0 == memcmp(ops[posR].arg, ops[posB].arg, sizeof(skcms_TransferFunction)))) {
2565
                // Fuse the three matching ops into one.
2566
0
                ops[posB].op = fusableOp.rgb;
2567
0
                cursor -= 2;
2568
0
                break;
2569
0
            }
2570
0
        }
2571
0
    }
2572
2573
0
    return cursor;
2574
0
}
2575
2576
0
static size_t bytes_per_pixel(skcms_PixelFormat fmt) {
2577
0
    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
0
        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
0
        case skcms_PixelFormat_RGB_fff          >> 1: return 12;
2598
0
        case skcms_PixelFormat_RGBA_ffff        >> 1: return 16;
2599
0
    }
2600
0
    assert(false);
2601
0
    return 0;
2602
0
}
2603
2604
static bool prep_for_destination(const skcms_ICCProfile* profile,
2605
                                 skcms_Matrix3x3* fromXYZD50,
2606
                                 skcms_TransferFunction* invR,
2607
                                 skcms_TransferFunction* invG,
2608
0
                                 skcms_TransferFunction* invB) {
2609
    // skcms_Transform() supports B2A destinations...
2610
0
    if (profile->has_B2A) { return true; }
2611
    // ...and destinations with parametric transfer functions and an XYZD50 gamut matrix.
2612
0
    return profile->has_trc
2613
0
        && profile->has_toXYZD50
2614
0
        && profile->trc[0].table_entries == 0
2615
0
        && profile->trc[1].table_entries == 0
2616
0
        && profile->trc[2].table_entries == 0
2617
0
        && skcms_TransferFunction_invert(&profile->trc[0].parametric, invR)
2618
0
        && skcms_TransferFunction_invert(&profile->trc[1].parametric, invG)
2619
0
        && skcms_TransferFunction_invert(&profile->trc[2].parametric, invB)
2620
0
        && skcms_Matrix3x3_invert(&profile->toXYZD50, fromXYZD50);
2621
0
}
2622
2623
bool skcms_Transform(const void*             src,
2624
                     skcms_PixelFormat       srcFmt,
2625
                     skcms_AlphaFormat       srcAlpha,
2626
                     const skcms_ICCProfile* srcProfile,
2627
                     void*                   dst,
2628
                     skcms_PixelFormat       dstFmt,
2629
                     skcms_AlphaFormat       dstAlpha,
2630
                     const skcms_ICCProfile* dstProfile,
2631
0
                     size_t                  nz) {
2632
0
    const size_t dst_bpp = bytes_per_pixel(dstFmt),
2633
0
                 src_bpp = bytes_per_pixel(srcFmt);
2634
    // Let's just refuse if the request is absurdly big.
2635
0
    if (nz * dst_bpp > INT_MAX || nz * src_bpp > INT_MAX) {
2636
0
        return false;
2637
0
    }
2638
0
    int n = (int)nz;
2639
2640
    // Null profiles default to sRGB. Passing null for both is handy when doing format conversion.
2641
0
    if (!srcProfile) {
2642
0
        srcProfile = skcms_sRGB_profile();
2643
0
    }
2644
0
    if (!dstProfile) {
2645
0
        dstProfile = skcms_sRGB_profile();
2646
0
    }
2647
2648
    // We can't transform in place unless the PixelFormats are the same size.
2649
0
    if (dst == src && dst_bpp != src_bpp) {
2650
0
        return false;
2651
0
    }
2652
    // TODO: more careful alias rejection (like, dst == src + 1)?
2653
2654
0
    Op          program[32];
2655
0
    const void* context[32];
2656
2657
0
    Op*          ops      = program;
2658
0
    const void** contexts = context;
2659
2660
0
    auto add_op = [&](Op o) {
2661
0
        *ops++ = o;
2662
0
        *contexts++ = nullptr;
2663
0
    };
2664
2665
0
    auto add_op_ctx = [&](Op o, const void* c) {
2666
0
        *ops++ = o;
2667
0
        *contexts++ = c;
2668
0
    };
2669
2670
0
    auto add_curve_ops = [&](const skcms_Curve* curves, int numChannels) {
2671
0
        OpAndArg oa[4];
2672
0
        assert(numChannels <= ARRAY_COUNT(oa));
2673
2674
0
        int numOps = select_curve_ops(curves, numChannels, oa);
2675
2676
0
        for (int i = 0; i < numOps; ++i) {
2677
0
            add_op_ctx(oa[i].op, oa[i].arg);
2678
0
        }
2679
0
    };
2680
2681
    // These are always parametric curves of some sort.
2682
0
    skcms_Curve dst_curves[3];
2683
0
    dst_curves[0].table_entries =
2684
0
    dst_curves[1].table_entries =
2685
0
    dst_curves[2].table_entries = 0;
2686
2687
0
    skcms_Matrix3x3        from_xyz;
2688
2689
0
    switch (srcFmt >> 1) {
2690
0
        default: return false;
2691
0
        case skcms_PixelFormat_A_8              >> 1: add_op(Op::load_a8);          break;
2692
0
        case skcms_PixelFormat_G_8              >> 1: add_op(Op::load_g8);          break;
2693
0
        case skcms_PixelFormat_GA_88            >> 1: add_op(Op::load_ga88);        break;
2694
0
        case skcms_PixelFormat_ABGR_4444        >> 1: add_op(Op::load_4444);        break;
2695
0
        case skcms_PixelFormat_RGB_565          >> 1: add_op(Op::load_565);         break;
2696
0
        case skcms_PixelFormat_RGB_888          >> 1: add_op(Op::load_888);         break;
2697
0
        case skcms_PixelFormat_RGBA_8888        >> 1: add_op(Op::load_8888);        break;
2698
0
        case skcms_PixelFormat_RGBA_1010102     >> 1: add_op(Op::load_1010102);     break;
2699
0
        case skcms_PixelFormat_RGB_101010x_XR   >> 1: add_op(Op::load_101010x_XR);  break;
2700
0
        case skcms_PixelFormat_RGBA_10101010_XR >> 1: add_op(Op::load_10101010_XR); break;
2701
0
        case skcms_PixelFormat_RGB_161616LE     >> 1: add_op(Op::load_161616LE);    break;
2702
0
        case skcms_PixelFormat_RGBA_16161616LE  >> 1: add_op(Op::load_16161616LE);  break;
2703
0
        case skcms_PixelFormat_RGB_161616BE     >> 1: add_op(Op::load_161616BE);    break;
2704
0
        case skcms_PixelFormat_RGBA_16161616BE  >> 1: add_op(Op::load_16161616BE);  break;
2705
0
        case skcms_PixelFormat_RGB_hhh_Norm     >> 1: add_op(Op::load_hhh);         break;
2706
0
        case skcms_PixelFormat_RGBA_hhhh_Norm   >> 1: add_op(Op::load_hhhh);        break;
2707
0
        case skcms_PixelFormat_RGB_hhh          >> 1: add_op(Op::load_hhh);         break;
2708
0
        case skcms_PixelFormat_RGBA_hhhh        >> 1: add_op(Op::load_hhhh);        break;
2709
0
        case skcms_PixelFormat_RGB_fff          >> 1: add_op(Op::load_fff);         break;
2710
0
        case skcms_PixelFormat_RGBA_ffff        >> 1: add_op(Op::load_ffff);        break;
2711
2712
0
        case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
2713
0
            add_op(Op::load_8888);
2714
0
            add_op_ctx(Op::tf_rgb, skcms_sRGB_TransferFunction());
2715
0
            break;
2716
0
    }
2717
0
    if (srcFmt == skcms_PixelFormat_RGB_hhh_Norm ||
2718
0
        srcFmt == skcms_PixelFormat_RGBA_hhhh_Norm) {
2719
0
        add_op(Op::clamp);
2720
0
    }
2721
0
    if (srcFmt & 1) {
2722
0
        add_op(Op::swap_rb);
2723
0
    }
2724
0
    skcms_ICCProfile gray_dst_profile;
2725
0
    switch (dstFmt >> 1) {
2726
0
        case skcms_PixelFormat_G_8:
2727
0
        case skcms_PixelFormat_GA_88:
2728
            // When transforming to gray, stop at XYZ (by setting toXYZ to identity), then transform
2729
            // luminance (Y) by the destination transfer function.
2730
0
            gray_dst_profile = *dstProfile;
2731
0
            skcms_SetXYZD50(&gray_dst_profile, &skcms_XYZD50_profile()->toXYZD50);
2732
0
            dstProfile = &gray_dst_profile;
2733
0
            break;
2734
0
        default:
2735
0
            break;
2736
0
    }
2737
2738
0
    if (srcProfile->data_color_space == skcms_Signature_CMYK) {
2739
        // Photoshop creates CMYK images as inverse CMYK.
2740
        // These happen to be the only ones we've _ever_ seen.
2741
0
        add_op(Op::invert);
2742
        // With CMYK, ignore the alpha type, to avoid changing K or conflating CMY with K.
2743
0
        srcAlpha = skcms_AlphaFormat_Unpremul;
2744
0
    }
2745
2746
0
    if (srcAlpha == skcms_AlphaFormat_Opaque) {
2747
0
        add_op(Op::force_opaque);
2748
0
    } else if (srcAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2749
0
        add_op(Op::unpremul);
2750
0
    }
2751
2752
0
    if (dstProfile != srcProfile) {
2753
2754
0
        if (!prep_for_destination(dstProfile,
2755
0
                                  &from_xyz,
2756
0
                                  &dst_curves[0].parametric,
2757
0
                                  &dst_curves[1].parametric,
2758
0
                                  &dst_curves[2].parametric)) {
2759
0
            return false;
2760
0
        }
2761
2762
0
        if (srcProfile->has_A2B) {
2763
0
            if (srcProfile->A2B.input_channels) {
2764
0
                add_curve_ops(srcProfile->A2B.input_curves,
2765
0
                              (int)srcProfile->A2B.input_channels);
2766
0
                add_op(Op::clamp);
2767
0
                add_op_ctx(Op::clut_A2B, &srcProfile->A2B);
2768
0
            }
2769
2770
0
            if (srcProfile->A2B.matrix_channels == 3) {
2771
0
                add_curve_ops(srcProfile->A2B.matrix_curves, /*numChannels=*/3);
2772
2773
0
                static const skcms_Matrix3x4 I = {{
2774
0
                    {1,0,0,0},
2775
0
                    {0,1,0,0},
2776
0
                    {0,0,1,0},
2777
0
                }};
2778
0
                if (0 != memcmp(&I, &srcProfile->A2B.matrix, sizeof(I))) {
2779
0
                    add_op_ctx(Op::matrix_3x4, &srcProfile->A2B.matrix);
2780
0
                }
2781
0
            }
2782
2783
0
            if (srcProfile->A2B.output_channels == 3) {
2784
0
                add_curve_ops(srcProfile->A2B.output_curves, /*numChannels=*/3);
2785
0
            }
2786
2787
0
            if (srcProfile->pcs == skcms_Signature_Lab) {
2788
0
                add_op(Op::lab_to_xyz);
2789
0
            }
2790
2791
0
        } else if (srcProfile->has_trc && srcProfile->has_toXYZD50) {
2792
0
            add_curve_ops(srcProfile->trc, /*numChannels=*/3);
2793
0
        } else {
2794
0
            return false;
2795
0
        }
2796
2797
        // A2B sources are in XYZD50 by now, but TRC sources are still in their original gamut.
2798
0
        assert (srcProfile->has_A2B || srcProfile->has_toXYZD50);
2799
2800
0
        if (dstProfile->has_B2A) {
2801
            // B2A needs its input in XYZD50, so transform TRC sources now.
2802
0
            if (!srcProfile->has_A2B) {
2803
0
                add_op_ctx(Op::matrix_3x3, &srcProfile->toXYZD50);
2804
0
            }
2805
2806
0
            if (dstProfile->pcs == skcms_Signature_Lab) {
2807
0
                add_op(Op::xyz_to_lab);
2808
0
            }
2809
2810
0
            if (dstProfile->B2A.input_channels == 3) {
2811
0
                add_curve_ops(dstProfile->B2A.input_curves, /*numChannels=*/3);
2812
0
            }
2813
2814
0
            if (dstProfile->B2A.matrix_channels == 3) {
2815
0
                static const skcms_Matrix3x4 I = {{
2816
0
                    {1,0,0,0},
2817
0
                    {0,1,0,0},
2818
0
                    {0,0,1,0},
2819
0
                }};
2820
0
                if (0 != memcmp(&I, &dstProfile->B2A.matrix, sizeof(I))) {
2821
0
                    add_op_ctx(Op::matrix_3x4, &dstProfile->B2A.matrix);
2822
0
                }
2823
2824
0
                add_curve_ops(dstProfile->B2A.matrix_curves, /*numChannels=*/3);
2825
0
            }
2826
2827
0
            if (dstProfile->B2A.output_channels) {
2828
0
                add_op(Op::clamp);
2829
0
                add_op_ctx(Op::clut_B2A, &dstProfile->B2A);
2830
2831
0
                add_curve_ops(dstProfile->B2A.output_curves,
2832
0
                              (int)dstProfile->B2A.output_channels);
2833
0
            }
2834
0
        } else {
2835
            // This is a TRC destination.
2836
            // We'll concat any src->xyz matrix with our xyz->dst matrix into one src->dst matrix.
2837
            // (A2B sources are already in XYZD50, making that src->xyz matrix I.)
2838
0
            static const skcms_Matrix3x3 I = {{
2839
0
                { 1.0f, 0.0f, 0.0f },
2840
0
                { 0.0f, 1.0f, 0.0f },
2841
0
                { 0.0f, 0.0f, 1.0f },
2842
0
            }};
2843
0
            const skcms_Matrix3x3* to_xyz = srcProfile->has_A2B ? &I : &srcProfile->toXYZD50;
2844
2845
            // There's a chance the source and destination gamuts are identical,
2846
            // in which case we can skip the gamut transform.
2847
0
            if (0 != memcmp(&dstProfile->toXYZD50, to_xyz, sizeof(skcms_Matrix3x3))) {
2848
                // Concat the entire gamut transform into from_xyz,
2849
                // now slightly misnamed but it's a handy spot to stash the result.
2850
0
                from_xyz = skcms_Matrix3x3_concat(&from_xyz, to_xyz);
2851
0
                add_op_ctx(Op::matrix_3x3, &from_xyz);
2852
0
            }
2853
2854
            // Encode back to dst RGB using its parametric transfer functions.
2855
0
            OpAndArg oa[3];
2856
0
            int numOps = select_curve_ops(dst_curves, /*numChannels=*/3, oa);
2857
0
            for (int index = 0; index < numOps; ++index) {
2858
0
                assert(oa[index].op != Op::table_r &&
2859
0
                       oa[index].op != Op::table_g &&
2860
0
                       oa[index].op != Op::table_b &&
2861
0
                       oa[index].op != Op::table_a);
2862
0
                add_op_ctx(oa[index].op, oa[index].arg);
2863
0
            }
2864
0
        }
2865
0
    }
2866
2867
    // Clamp here before premul to make sure we're clamping to normalized values _and_ gamut,
2868
    // not just to values that fit in [0,1].
2869
    //
2870
    // E.g. r = 1.1, a = 0.5 would fit fine in fixed point after premul (ra=0.55,a=0.5),
2871
    // but would be carrying r > 1, which is really unexpected for downstream consumers.
2872
0
    if (dstFmt < skcms_PixelFormat_RGB_hhh) {
2873
0
        add_op(Op::clamp);
2874
0
    }
2875
2876
0
    if (dstProfile->data_color_space == skcms_Signature_CMYK) {
2877
        // Photoshop creates CMYK images as inverse CMYK.
2878
        // These happen to be the only ones we've _ever_ seen.
2879
0
        add_op(Op::invert);
2880
2881
        // CMYK has no alpha channel, so make sure dstAlpha is a no-op.
2882
0
        dstAlpha = skcms_AlphaFormat_Unpremul;
2883
0
    }
2884
2885
0
    if (dstAlpha == skcms_AlphaFormat_Opaque) {
2886
0
        add_op(Op::force_opaque);
2887
0
    } else if (dstAlpha == skcms_AlphaFormat_PremulAsEncoded) {
2888
0
        add_op(Op::premul);
2889
0
    }
2890
0
    if (dstFmt & 1) {
2891
0
        add_op(Op::swap_rb);
2892
0
    }
2893
0
    switch (dstFmt >> 1) {
2894
0
        default: return false;
2895
0
        case skcms_PixelFormat_A_8              >> 1: add_op(Op::store_a8);          break;
2896
0
        case skcms_PixelFormat_G_8              >> 1: add_op(Op::store_g8);          break;
2897
0
        case skcms_PixelFormat_GA_88            >> 1: add_op(Op::store_ga88);        break;
2898
0
        case skcms_PixelFormat_ABGR_4444        >> 1: add_op(Op::store_4444);        break;
2899
0
        case skcms_PixelFormat_RGB_565          >> 1: add_op(Op::store_565);         break;
2900
0
        case skcms_PixelFormat_RGB_888          >> 1: add_op(Op::store_888);         break;
2901
0
        case skcms_PixelFormat_RGBA_8888        >> 1: add_op(Op::store_8888);        break;
2902
0
        case skcms_PixelFormat_RGBA_1010102     >> 1: add_op(Op::store_1010102);     break;
2903
0
        case skcms_PixelFormat_RGB_161616LE     >> 1: add_op(Op::store_161616LE);    break;
2904
0
        case skcms_PixelFormat_RGBA_16161616LE  >> 1: add_op(Op::store_16161616LE);  break;
2905
0
        case skcms_PixelFormat_RGB_161616BE     >> 1: add_op(Op::store_161616BE);    break;
2906
0
        case skcms_PixelFormat_RGBA_16161616BE  >> 1: add_op(Op::store_16161616BE);  break;
2907
0
        case skcms_PixelFormat_RGB_hhh_Norm     >> 1: add_op(Op::store_hhh);         break;
2908
0
        case skcms_PixelFormat_RGBA_hhhh_Norm   >> 1: add_op(Op::store_hhhh);        break;
2909
0
        case skcms_PixelFormat_RGB_101010x_XR   >> 1: add_op(Op::store_101010x_XR);  break;
2910
0
        case skcms_PixelFormat_RGBA_10101010_XR >> 1: add_op(Op::store_10101010_XR); break;
2911
0
        case skcms_PixelFormat_RGB_hhh          >> 1: add_op(Op::store_hhh);         break;
2912
0
        case skcms_PixelFormat_RGBA_hhhh        >> 1: add_op(Op::store_hhhh);        break;
2913
0
        case skcms_PixelFormat_RGB_fff          >> 1: add_op(Op::store_fff);         break;
2914
0
        case skcms_PixelFormat_RGBA_ffff        >> 1: add_op(Op::store_ffff);        break;
2915
2916
0
        case skcms_PixelFormat_RGBA_8888_sRGB >> 1:
2917
0
            add_op_ctx(Op::tf_rgb, skcms_sRGB_Inverse_TransferFunction());
2918
0
            add_op(Op::store_8888);
2919
0
            break;
2920
0
    }
2921
2922
0
    assert(ops      <= program + ARRAY_COUNT(program));
2923
0
    assert(contexts <= context + ARRAY_COUNT(context));
2924
2925
0
    auto run = baseline::run_program;
2926
0
    switch (cpu_type()) {
2927
0
        case CpuType::SKX:
2928
            #if !defined(SKCMS_DISABLE_SKX)
2929
                run = skx::run_program;
2930
                break;
2931
            #endif
2932
2933
0
        case CpuType::HSW:
2934
            #if !defined(SKCMS_DISABLE_HSW)
2935
                run = hsw::run_program;
2936
                break;
2937
            #endif
2938
2939
0
        case CpuType::Baseline:
2940
0
            break;
2941
0
    }
2942
2943
0
    run(program, context, ops - program, (const char*)src, (char*)dst, n, src_bpp,dst_bpp);
2944
0
    return true;
2945
0
}
2946
2947
0
static void assert_usable_as_destination(const skcms_ICCProfile* profile) {
2948
#if defined(NDEBUG)
2949
    (void)profile;
2950
#else
2951
0
    skcms_Matrix3x3 fromXYZD50;
2952
0
    skcms_TransferFunction invR, invG, invB;
2953
0
    assert(prep_for_destination(profile, &fromXYZD50, &invR, &invG, &invB));
2954
0
#endif
2955
0
}
2956
2957
0
bool skcms_MakeUsableAsDestination(skcms_ICCProfile* profile) {
2958
0
    if (!profile->has_B2A) {
2959
0
        skcms_Matrix3x3 fromXYZD50;
2960
0
        if (!profile->has_trc || !profile->has_toXYZD50
2961
0
            || !skcms_Matrix3x3_invert(&profile->toXYZD50, &fromXYZD50)) {
2962
0
            return false;
2963
0
        }
2964
2965
0
        skcms_TransferFunction tf[3];
2966
0
        for (int i = 0; i < 3; i++) {
2967
0
            skcms_TransferFunction inv;
2968
0
            if (profile->trc[i].table_entries == 0
2969
0
                && skcms_TransferFunction_invert(&profile->trc[i].parametric, &inv)) {
2970
0
                tf[i] = profile->trc[i].parametric;
2971
0
                continue;
2972
0
            }
2973
2974
0
            float max_error;
2975
            // Parametric curves from skcms_ApproximateCurve() are guaranteed to be invertible.
2976
0
            if (!skcms_ApproximateCurve(&profile->trc[i], &tf[i], &max_error)) {
2977
0
                return false;
2978
0
            }
2979
0
        }
2980
2981
0
        for (int i = 0; i < 3; ++i) {
2982
0
            profile->trc[i].table_entries = 0;
2983
0
            profile->trc[i].parametric = tf[i];
2984
0
        }
2985
0
    }
2986
0
    assert_usable_as_destination(profile);
2987
0
    return true;
2988
0
}
2989
2990
0
bool skcms_MakeUsableAsDestinationWithSingleCurve(skcms_ICCProfile* profile) {
2991
    // Call skcms_MakeUsableAsDestination() with B2A disabled;
2992
    // on success that'll return a TRC/XYZ profile with three skcms_TransferFunctions.
2993
0
    skcms_ICCProfile result = *profile;
2994
0
    result.has_B2A = false;
2995
0
    if (!skcms_MakeUsableAsDestination(&result)) {
2996
0
        return false;
2997
0
    }
2998
2999
    // Of the three, pick the transfer function that best fits the other two.
3000
0
    int best_tf = 0;
3001
0
    float min_max_error = INFINITY_;
3002
0
    for (int i = 0; i < 3; i++) {
3003
0
        skcms_TransferFunction inv;
3004
0
        if (!skcms_TransferFunction_invert(&result.trc[i].parametric, &inv)) {
3005
0
            return false;
3006
0
        }
3007
3008
0
        float err = 0;
3009
0
        for (int j = 0; j < 3; ++j) {
3010
0
            err = fmaxf_(err, skcms_MaxRoundtripError(&profile->trc[j], &inv));
3011
0
        }
3012
0
        if (min_max_error > err) {
3013
0
            min_max_error = err;
3014
0
            best_tf = i;
3015
0
        }
3016
0
    }
3017
3018
0
    for (int i = 0; i < 3; i++) {
3019
0
        result.trc[i].parametric = result.trc[best_tf].parametric;
3020
0
    }
3021
3022
0
    *profile = result;
3023
0
    assert_usable_as_destination(profile);
3024
0
    return true;
3025
0
}