Coverage Report

Created: 2026-11-12 07:18

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