Coverage Report

Created: 2025-10-13 06:03

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