Coverage Report

Created: 2026-10-27 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libavif/src/colr.c
Line
Count
Source
1
// Copyright 2019 Joe Drago. All rights reserved.
2
// SPDX-License-Identifier: BSD-2-Clause
3
4
#include "avif/internal.h"
5
6
#include <float.h>
7
#include <math.h>
8
#include <string.h>
9
10
struct avifColorPrimariesTable
11
{
12
    avifColorPrimaries colorPrimariesEnum;
13
    const char * name;
14
    float primaries[8]; // rX, rY, gX, gY, bX, bY, wX, wY
15
};
16
static const struct avifColorPrimariesTable avifColorPrimariesTables[] = {
17
    { AVIF_COLOR_PRIMARIES_BT709, "BT.709", { 0.64f, 0.33f, 0.3f, 0.6f, 0.15f, 0.06f, 0.3127f, 0.329f } },
18
    { AVIF_COLOR_PRIMARIES_BT470M, "BT.470-6 System M", { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f, 0.310f, 0.316f } },
19
    { AVIF_COLOR_PRIMARIES_BT470BG, "BT.470-6 System BG", { 0.64f, 0.33f, 0.29f, 0.60f, 0.15f, 0.06f, 0.3127f, 0.3290f } },
20
    { AVIF_COLOR_PRIMARIES_BT601, "BT.601", { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f, 0.3127f, 0.3290f } },
21
    { AVIF_COLOR_PRIMARIES_SMPTE240, "SMPTE 240M", { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f, 0.3127f, 0.3290f } },
22
    { AVIF_COLOR_PRIMARIES_GENERIC_FILM, "Generic film", { 0.681f, 0.319f, 0.243f, 0.692f, 0.145f, 0.049f, 0.310f, 0.316f } },
23
    { AVIF_COLOR_PRIMARIES_BT2020, "BT.2020", { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f, 0.3127f, 0.3290f } },
24
    { AVIF_COLOR_PRIMARIES_XYZ, "XYZ", { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.3333f, 0.3333f } },
25
    { AVIF_COLOR_PRIMARIES_SMPTE431, "SMPTE RP 431-2", { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f, 0.314f, 0.351f } },
26
    { AVIF_COLOR_PRIMARIES_SMPTE432, "SMPTE EG 432-1 (DCI P3)", { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f, 0.3127f, 0.3290f } },
27
    { AVIF_COLOR_PRIMARIES_EBU3213, "EBU Tech. 3213-E", { 0.630f, 0.340f, 0.295f, 0.605f, 0.155f, 0.077f, 0.3127f, 0.3290f } }
28
};
29
static const int avifColorPrimariesTableSize = sizeof(avifColorPrimariesTables) / sizeof(avifColorPrimariesTables[0]);
30
31
void avifColorPrimariesGetValues(avifColorPrimaries acp, float outPrimaries[8])
32
696
{
33
2.81k
    for (int i = 0; i < avifColorPrimariesTableSize; ++i) {
34
2.62k
        if (avifColorPrimariesTables[i].colorPrimariesEnum == acp) {
35
503
            memcpy(outPrimaries, avifColorPrimariesTables[i].primaries, sizeof(avifColorPrimariesTables[i].primaries));
36
503
            return;
37
503
        }
38
2.62k
    }
39
40
    // if we get here, the color primaries are unknown. Just return a reasonable default.
41
193
    memcpy(outPrimaries, avifColorPrimariesTables[0].primaries, sizeof(avifColorPrimariesTables[0].primaries));
42
193
}
43
44
static avifBool matchesTo3RoundedPlaces(float a, float b)
45
506
{
46
506
    return (fabsf(a - b) < 0.001f);
47
506
}
48
49
static avifBool primariesMatch(const float p1[8], const float p2[8])
50
120
{
51
120
    return matchesTo3RoundedPlaces(p1[0], p2[0]) && matchesTo3RoundedPlaces(p1[1], p2[1]) &&
52
32
           matchesTo3RoundedPlaces(p1[2], p2[2]) && matchesTo3RoundedPlaces(p1[3], p2[3]) && matchesTo3RoundedPlaces(p1[4], p2[4]) &&
53
32
           matchesTo3RoundedPlaces(p1[5], p2[5]) && matchesTo3RoundedPlaces(p1[6], p2[6]) && matchesTo3RoundedPlaces(p1[7], p2[7]);
54
120
}
55
56
avifColorPrimaries avifColorPrimariesFind(const float inPrimaries[8], const char ** outName)
57
40
{
58
40
    if (outName) {
59
0
        *outName = NULL;
60
0
    }
61
62
128
    for (int i = 0; i < avifColorPrimariesTableSize; ++i) {
63
120
        if (primariesMatch(inPrimaries, avifColorPrimariesTables[i].primaries)) {
64
32
            if (outName) {
65
0
                *outName = avifColorPrimariesTables[i].name;
66
0
            }
67
32
            return avifColorPrimariesTables[i].colorPrimariesEnum;
68
32
        }
69
120
    }
70
8
    return AVIF_COLOR_PRIMARIES_UNKNOWN;
71
40
}
72
73
avifResult avifTransferCharacteristicsGetGamma(avifTransferCharacteristics atc, float * gamma)
74
0
{
75
0
    if (gamma == NULL) {
76
0
        return AVIF_RESULT_INVALID_ARGUMENT;
77
0
    }
78
79
0
    switch (atc) {
80
0
        case AVIF_TRANSFER_CHARACTERISTICS_BT470M:
81
0
            *gamma = 2.2f;
82
0
            return AVIF_RESULT_OK;
83
84
0
        case AVIF_TRANSFER_CHARACTERISTICS_BT470BG:
85
0
            *gamma = 2.8f;
86
0
            return AVIF_RESULT_OK;
87
88
0
        case AVIF_TRANSFER_CHARACTERISTICS_LINEAR:
89
0
            *gamma = 1.0f;
90
0
            return AVIF_RESULT_OK;
91
92
0
        default:
93
            // Not representable as a single gamma value (e.g. sRGB, BT.709, PQ, HLG, etc.).
94
0
            return AVIF_RESULT_NOT_IMPLEMENTED;
95
0
    }
96
0
}
97
98
avifTransferCharacteristics avifTransferCharacteristicsFindByGamma(float gamma)
99
94
{
100
94
    if (matchesTo3RoundedPlaces(gamma, 2.2f)) {
101
60
        return AVIF_TRANSFER_CHARACTERISTICS_BT470M;
102
60
    } else if (matchesTo3RoundedPlaces(gamma, 1.0f)) {
103
0
        return AVIF_TRANSFER_CHARACTERISTICS_LINEAR;
104
34
    } else if (matchesTo3RoundedPlaces(gamma, 2.8f)) {
105
0
        return AVIF_TRANSFER_CHARACTERISTICS_BT470BG;
106
0
    }
107
108
34
    return AVIF_TRANSFER_CHARACTERISTICS_UNKNOWN;
109
94
}
110
111
struct avifMatrixCoefficientsTable
112
{
113
    avifMatrixCoefficients matrixCoefficientsEnum;
114
    const char * name;
115
    const float kr;
116
    const float kb;
117
};
118
119
// https://www.itu.int/rec/T-REC-H.273-201612-S
120
static const struct avifMatrixCoefficientsTable matrixCoefficientsTables[] = {
121
    //{ AVIF_MATRIX_COEFFICIENTS_IDENTITY, "Identity", 0.0f, 0.0f, }, // Handled elsewhere
122
    { AVIF_MATRIX_COEFFICIENTS_BT709, "BT.709", 0.2126f, 0.0722f },
123
    { AVIF_MATRIX_COEFFICIENTS_FCC, "FCC USFC 73.682", 0.30f, 0.11f },
124
    { AVIF_MATRIX_COEFFICIENTS_BT470BG, "BT.470-6 System BG", 0.299f, 0.114f },
125
    { AVIF_MATRIX_COEFFICIENTS_BT601, "BT.601", 0.299f, 0.114f },
126
    { AVIF_MATRIX_COEFFICIENTS_SMPTE240, "SMPTE ST 240", 0.212f, 0.087f },
127
    //{ AVIF_MATRIX_COEFFICIENTS_YCGCO, "YCgCo", 0.0f, 0.0f, }, // Handled elsewhere
128
    { AVIF_MATRIX_COEFFICIENTS_BT2020_NCL, "BT.2020 (non-constant luminance)", 0.2627f, 0.0593f },
129
    //{ AVIF_MATRIX_COEFFICIENTS_BT2020_CL, "BT.2020 (constant luminance)", 0.2627f, 0.0593f }, // FIXME: It is not an linear transformation.
130
    //{ AVIF_MATRIX_COEFFICIENTS_SMPTE2085, "ST 2085", 0.0f, 0.0f }, // FIXME: ST2085 can't represent using Kr and Kb.
131
    //{ AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL, "Chromaticity-derived constant luminance system", 0.0f, 0.0f } // FIXME: It is not an linear transformation.
132
    //{ AVIF_MATRIX_COEFFICIENTS_ICTCP, "BT.2100-0 ICtCp", 0.0f, 0.0f }, // FIXME: This can't represent using Kr and Kb.
133
};
134
135
static const int avifMatrixCoefficientsTableSize = sizeof(matrixCoefficientsTables) / sizeof(matrixCoefficientsTables[0]);
136
137
static avifBool calcYUVInfoFromCICP(const avifImage * image, float coeffs[3])
138
9.95k
{
139
9.95k
    if (image->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL) {
140
196
        avifColorPrimariesComputeYCoeffs(image->colorPrimaries, coeffs);
141
196
        return AVIF_TRUE;
142
9.75k
    } else {
143
63.4k
        for (int i = 0; i < avifMatrixCoefficientsTableSize; ++i) {
144
55.0k
            const struct avifMatrixCoefficientsTable * const table = &matrixCoefficientsTables[i];
145
55.0k
            if (table->matrixCoefficientsEnum == image->matrixCoefficients) {
146
1.39k
                coeffs[0] = table->kr;
147
1.39k
                coeffs[2] = table->kb;
148
1.39k
                coeffs[1] = 1.0f - coeffs[0] - coeffs[2];
149
1.39k
                return AVIF_TRUE;
150
1.39k
            }
151
55.0k
        }
152
9.75k
    }
153
8.36k
    return AVIF_FALSE;
154
9.95k
}
155
156
void avifCalcYUVCoefficients(const avifImage * image, float * outR, float * outG, float * outB)
157
9.95k
{
158
    // (As of ISO/IEC 23000-22:2019 Amendment 2)
159
    // MIAF Section 7.3.6.4 "Colour information property":
160
    //
161
    // If a coded image has no associated colour property, the default property is defined as having
162
    // colour_type equal to 'nclx' with properties as follows:
163
    // -   colour_primaries equal to 1,
164
    // -   transfer_characteristics equal to 13,
165
    // -   matrix_coefficients equal to 5 or 6 (which are functionally identical), and
166
    // -   full_range_flag equal to 1.
167
    // Only if the colour information property of the image matches these default values, the colour
168
    // property may be omitted; all other images shall have an explicitly declared colour space via
169
    // association with a property of this type.
170
    //
171
    // See here for the discussion: https://github.com/AOMediaCodec/av1-avif/issues/77#issuecomment-676526097
172
173
    // matrix_coefficients of [5,6] == BT.601:
174
9.95k
    float kr = 0.299f;
175
9.95k
    float kb = 0.114f;
176
9.95k
    float kg = 1.0f - kr - kb;
177
178
9.95k
    float coeffs[3];
179
9.95k
    if (calcYUVInfoFromCICP(image, coeffs)) {
180
1.59k
        kr = coeffs[0];
181
1.59k
        kg = coeffs[1];
182
1.59k
        kb = coeffs[2];
183
1.59k
    }
184
185
9.95k
    *outR = kr;
186
9.95k
    *outG = kg;
187
9.95k
    *outB = kb;
188
9.95k
}
189
190
// ---------------------------------------------------------------------------
191
// Transfer characteristics
192
//
193
// Transfer characteristics are defined in ITU-T H.273 https://www.itu.int/rec/T-REC-H.273-201612-S/en
194
// with formulas for linear to gamma conversion in Table 3.
195
// This is based on tongyuantongyu's implementation in https://github.com/AOMediaCodec/libavif/pull/444
196
// with some fixes/changes in the first commit:
197
// - Fixed 5 transfer curves where toLinear and toGamma functions were swapped (470M, 470BG, Log100,
198
//   Log100Sqrt10 and SMPTE428)
199
// - 'avifToLinearLog100' and 'avifToLinearLog100Sqrt10' were modified to return the middle of the
200
//   range of linear values that are gamma-encoded to 0.0 in order to reduce the max round trip error,
201
//   based on vrabaud's change in
202
//   https://chromium.googlesource.com/webm/libwebp/+/25d94f473b10882b8bee9288d00539001b692042
203
// - In this file, PQ and HLG return "extended SDR" linear values in [0.0, 10000/203] and
204
//   [0.0, 1000/203] respectively, where a value of 1.0 means SDR white brightness (203 nits), and any
205
//   value above 1.0 is brighter.
206
// See git history for further changes.
207
208
struct avifTransferCharacteristicsTable
209
{
210
    avifTransferCharacteristics transferCharacteristicsEnum;
211
    const char * name;
212
    avifTransferFunction toLinear;
213
    avifTransferFunction toGamma;
214
};
215
216
static float avifToLinear709(float gamma)
217
0
{
218
0
    if (gamma < 0.0f) {
219
0
        return 0.0f;
220
0
    } else if (gamma < 4.5f * 0.018053968510807f) {
221
0
        return gamma / 4.5f;
222
0
    } else if (gamma < 1.0f) {
223
0
        return powf((gamma + 0.09929682680944f) / 1.09929682680944f, 1.0f / 0.45f);
224
0
    } else {
225
0
        return 1.0f;
226
0
    }
227
0
}
228
229
static float avifToGamma709(float linear)
230
0
{
231
0
    if (linear < 0.0f) {
232
0
        return 0.0f;
233
0
    } else if (linear < 0.018053968510807f) {
234
0
        return linear * 4.5f;
235
0
    } else if (linear < 1.0f) {
236
0
        return 1.09929682680944f * powf(linear, 0.45f) - 0.09929682680944f;
237
0
    } else {
238
0
        return 1.0f;
239
0
    }
240
0
}
241
242
static float avifToLinear470M(float gamma)
243
0
{
244
0
    return powf(AVIF_CLAMP(gamma, 0.0f, 1.0f), 2.2f);
245
0
}
246
247
static float avifToGamma470M(float linear)
248
0
{
249
0
    return powf(AVIF_CLAMP(linear, 0.0f, 1.0f), 1.0f / 2.2f);
250
0
}
251
252
static float avifToLinear470BG(float gamma)
253
0
{
254
0
    return powf(AVIF_CLAMP(gamma, 0.0f, 1.0f), 2.8f);
255
0
}
256
257
static float avifToGamma470BG(float linear)
258
0
{
259
0
    return powf(AVIF_CLAMP(linear, 0.0f, 1.0f), 1.0f / 2.8f);
260
0
}
261
262
static float avifToLinearSMPTE240(float gamma)
263
0
{
264
0
    if (gamma < 0.0f) {
265
0
        return 0.0f;
266
0
    } else if (gamma < 4.0f * 0.022821585529445f) {
267
0
        return gamma / 4.0f;
268
0
    } else if (gamma < 1.0f) {
269
0
        return powf((gamma + 0.111572195921731f) / 1.111572195921731f, 1.0f / 0.45f);
270
0
    } else {
271
0
        return 1.0f;
272
0
    }
273
0
}
274
275
static float avifToGammaSMPTE240(float linear)
276
0
{
277
0
    if (linear < 0.0f) {
278
0
        return 0.0f;
279
0
    } else if (linear < 0.022821585529445f) {
280
0
        return linear * 4.0f;
281
0
    } else if (linear < 1.0f) {
282
0
        return 1.111572195921731f * powf(linear, 0.45f) - 0.111572195921731f;
283
0
    } else {
284
0
        return 1.0f;
285
0
    }
286
0
}
287
288
static float avifToGammaLinear(float gamma)
289
0
{
290
0
    return AVIF_CLAMP(gamma, 0.0f, 1.0f);
291
0
}
292
293
static float avifToLinearLog100(float gamma)
294
0
{
295
    // The function is non-bijective so choose the middle of [0, 0.01].
296
0
    const float mid_interval = 0.01f / 2.f;
297
0
    return (gamma <= 0.0f) ? mid_interval : powf(10.0f, 2.f * (AVIF_MIN(gamma, 1.f) - 1.0f));
298
0
}
299
300
static float avifToGammaLog100(float linear)
301
0
{
302
0
    return linear <= 0.01f ? 0.0f : 1.0f + log10f(AVIF_MIN(linear, 1.0f)) / 2.0f;
303
0
}
304
305
static float avifToLinearLog100Sqrt10(float gamma)
306
0
{
307
    // The function is non-bijective so choose the middle of [0, 0.00316227766f].
308
0
    const float mid_interval = 0.00316227766f / 2.f;
309
0
    return (gamma <= 0.0f) ? mid_interval : powf(10.0f, 2.5f * (AVIF_MIN(gamma, 1.f) - 1.0f));
310
0
}
311
312
static float avifToGammaLog100Sqrt10(float linear)
313
0
{
314
0
    return linear <= 0.00316227766f ? 0.0f : 1.0f + log10f(AVIF_MIN(linear, 1.0f)) / 2.5f;
315
0
}
316
317
static float avifToLinearIEC61966(float gamma)
318
0
{
319
0
    if (gamma < -4.5f * 0.018053968510807f) {
320
0
        return -powf((gamma - 0.09929682680944f) / -1.09929682680944f, 1.0f / 0.45f);
321
0
    } else if (gamma < 4.5f * 0.018053968510807f) {
322
0
        return gamma / 4.5f;
323
0
    } else {
324
0
        return powf((gamma + 0.09929682680944f) / 1.09929682680944f, 1.0f / 0.45f);
325
0
    }
326
0
}
327
328
static float avifToGammaIEC61966(float linear)
329
0
{
330
0
    if (linear < -0.018053968510807f) {
331
0
        return -1.09929682680944f * powf(-linear, 0.45f) + 0.09929682680944f;
332
0
    } else if (linear < 0.018053968510807f) {
333
0
        return linear * 4.5f;
334
0
    } else {
335
0
        return 1.09929682680944f * powf(linear, 0.45f) - 0.09929682680944f;
336
0
    }
337
0
}
338
339
static float avifToLinearBT1361(float gamma)
340
0
{
341
0
    if (gamma < -0.25f) {
342
0
        return -0.25f;
343
0
    } else if (gamma < 0.0f) {
344
0
        return powf((gamma - 0.02482420670236f) / -0.27482420670236f, 1.0f / 0.45f) / -4.0f;
345
0
    } else if (gamma < 4.5f * 0.018053968510807f) {
346
0
        return gamma / 4.5f;
347
0
    } else if (gamma < 1.0f) {
348
0
        return powf((gamma + 0.09929682680944f) / 1.09929682680944f, 1.0f / 0.45f);
349
0
    } else {
350
0
        return 1.0f;
351
0
    }
352
0
}
353
354
static float avifToGammaBT1361(float linear)
355
0
{
356
0
    if (linear < -0.25f) {
357
0
        return -0.25f;
358
0
    } else if (linear < 0.0f) {
359
0
        return -0.27482420670236f * powf(-4.0f * linear, 0.45f) + 0.02482420670236f;
360
0
    } else if (linear < 0.018053968510807f) {
361
0
        return linear * 4.5f;
362
0
    } else if (linear < 1.0f) {
363
0
        return 1.09929682680944f * powf(linear, 0.45f) - 0.09929682680944f;
364
0
    } else {
365
0
        return 1.0f;
366
0
    }
367
0
}
368
369
static float avifToLinearSRGB(float gamma)
370
0
{
371
0
    if (gamma < 0.0f) {
372
0
        return 0.0f;
373
0
    } else if (gamma < 12.92f * 0.0030412825601275209f) {
374
0
        return gamma / 12.92f;
375
0
    } else if (gamma < 1.0f) {
376
0
        return powf((gamma + 0.0550107189475866f) / 1.0550107189475866f, 2.4f);
377
0
    } else {
378
0
        return 1.0f;
379
0
    }
380
0
}
381
382
static float avifToGammaSRGB(float linear)
383
0
{
384
0
    if (linear < 0.0f) {
385
0
        return 0.0f;
386
0
    } else if (linear < 0.0030412825601275209f) {
387
0
        return linear * 12.92f;
388
0
    } else if (linear < 1.0f) {
389
0
        return 1.0550107189475866f * powf(linear, 1.0f / 2.4f) - 0.0550107189475866f;
390
0
    } else {
391
0
        return 1.0f;
392
0
    }
393
0
}
394
395
0
#define PQ_MAX_NITS 10000.0f
396
0
#define HLG_PEAK_LUMINANCE_NITS 1000.0f
397
0
#define SDR_WHITE_NITS 203.0f
398
399
static float avifToLinearPQ(float gamma)
400
0
{
401
0
    if (gamma > 0.0f) {
402
0
        const float powGamma = powf(gamma, 1.0f / 78.84375f);
403
0
        const float num = AVIF_MAX(powGamma - 0.8359375f, 0.0f);
404
0
        const float den = AVIF_MAX(18.8515625f - 18.6875f * powGamma, FLT_MIN);
405
0
        const float linear = powf(num / den, 1.0f / 0.1593017578125f);
406
        // Scale so that SDR white is 1.0 (extended SDR).
407
0
        return linear * PQ_MAX_NITS / SDR_WHITE_NITS;
408
0
    } else {
409
0
        return 0.0f;
410
0
    }
411
0
}
412
413
static float avifToGammaPQ(float linear)
414
0
{
415
0
    if (linear > 0.0f) {
416
        // Scale from extended SDR range to [0.0, 1.0].
417
0
        linear = AVIF_CLAMP(linear * SDR_WHITE_NITS / PQ_MAX_NITS, 0.0f, 1.0f);
418
0
        const float powLinear = powf(linear, 0.1593017578125f);
419
0
        const float num = 0.1640625f * powLinear - 0.1640625f;
420
0
        const float den = 1.0f + 18.6875f * powLinear;
421
0
        return powf(1.0f + num / den, 78.84375f);
422
0
    } else {
423
0
        return 0.0f;
424
0
    }
425
0
}
426
427
static float avifToLinearSMPTE428(float gamma)
428
0
{
429
0
    return powf(AVIF_MAX(gamma, 0.0f), 2.6f) / 0.91655527974030934f;
430
0
}
431
432
static float avifToGammaSMPTE428(float linear)
433
0
{
434
0
    return powf(0.91655527974030934f * AVIF_MAX(linear, 0.0f), 1.0f / 2.6f);
435
0
}
436
437
// Formula from ITU-R BT.2100-2
438
// Assumes Lw=1000 (max display luminance in nits).
439
// For simplicity, approximates Ys (which should be 0.2627*r+0.6780*g+0.0593*b)
440
// to the input value (r, g, or b depending on the current channel).
441
static float avifToLinearHLG(float gamma)
442
0
{
443
    // Inverse OETF followed by the OOTF, see Table 5 in ITU-R BT.2100-2 page 7.
444
    // Note that this differs slightly from  ITU-T H.273 which doesn't use the OOTF.
445
0
    if (gamma < 0.0f) {
446
0
        return 0.0f;
447
0
    }
448
0
    float linear = 0.0f;
449
0
    if (gamma <= 0.5f) {
450
0
        linear = powf((gamma * gamma) * (1.0f / 3.0f), 1.2f);
451
0
    } else {
452
0
        linear = powf((expf((gamma - 0.55991073f) / 0.17883277f) + 0.28466892f) / 12.0f, 1.2f);
453
0
    }
454
    // Scale so that SDR white is 1.0 (extended SDR).
455
0
    return linear * HLG_PEAK_LUMINANCE_NITS / SDR_WHITE_NITS;
456
0
}
457
458
static float avifToGammaHLG(float linear)
459
0
{
460
    // Scale from extended SDR range to [0.0, 1.0].
461
0
    linear = AVIF_CLAMP(linear * SDR_WHITE_NITS / HLG_PEAK_LUMINANCE_NITS, 0.0f, 1.0f);
462
    // Inverse OOTF followed by OETF see Table 5 and Note 5i in ITU-R BT.2100-2 page 7-8.
463
0
    linear = powf(linear, 1.0f / 1.2f);
464
0
    if (linear < 0.0f) {
465
0
        return 0.0f;
466
0
    } else if (linear <= (1.0f / 12.0f)) {
467
0
        return sqrtf(3.0f * linear);
468
0
    } else {
469
0
        return 0.17883277f * logf(12.0f * linear - 0.28466892f) + 0.55991073f;
470
0
    }
471
0
}
472
473
static const struct avifTransferCharacteristicsTable transferCharacteristicsTables[] = {
474
    { AVIF_TRANSFER_CHARACTERISTICS_BT709, "BT.709", avifToLinear709, avifToGamma709 },
475
    { AVIF_TRANSFER_CHARACTERISTICS_BT470M, "BT.470-6 System M", avifToLinear470M, avifToGamma470M },
476
    { AVIF_TRANSFER_CHARACTERISTICS_BT470BG, "BT.470-6 System BG", avifToLinear470BG, avifToGamma470BG },
477
    { AVIF_TRANSFER_CHARACTERISTICS_BT601, "BT.601", avifToLinear709, avifToGamma709 },
478
    { AVIF_TRANSFER_CHARACTERISTICS_SMPTE240, "SMPTE 240M", avifToLinearSMPTE240, avifToGammaSMPTE240 },
479
    { AVIF_TRANSFER_CHARACTERISTICS_LINEAR, "Linear", avifToGammaLinear, avifToGammaLinear },
480
    { AVIF_TRANSFER_CHARACTERISTICS_LOG100, "100:1 Log", avifToLinearLog100, avifToGammaLog100 },
481
    { AVIF_TRANSFER_CHARACTERISTICS_LOG100_SQRT10, "100sqrt(10):1 Log", avifToLinearLog100Sqrt10, avifToGammaLog100Sqrt10 },
482
    { AVIF_TRANSFER_CHARACTERISTICS_IEC61966, "IEC 61966-2-4", avifToLinearIEC61966, avifToGammaIEC61966 },
483
    { AVIF_TRANSFER_CHARACTERISTICS_BT1361, "BT.1361", avifToLinearBT1361, avifToGammaBT1361 },
484
    { AVIF_TRANSFER_CHARACTERISTICS_SRGB, "sRGB", avifToLinearSRGB, avifToGammaSRGB },
485
    { AVIF_TRANSFER_CHARACTERISTICS_BT2020_10BIT, "10bit BT.2020", avifToLinear709, avifToGamma709 },
486
    { AVIF_TRANSFER_CHARACTERISTICS_BT2020_12BIT, "12bit BT.2020", avifToLinear709, avifToGamma709 },
487
    { AVIF_TRANSFER_CHARACTERISTICS_SMPTE2084, "SMPTE ST 2084 (PQ)", avifToLinearPQ, avifToGammaPQ },
488
    { AVIF_TRANSFER_CHARACTERISTICS_SMPTE428, "SMPTE ST 428-1", avifToLinearSMPTE428, avifToGammaSMPTE428 },
489
    { AVIF_TRANSFER_CHARACTERISTICS_HLG, "ARIB STD-B67 (HLG)", avifToLinearHLG, avifToGammaHLG }
490
};
491
492
static const int avifTransferCharacteristicsTableSize =
493
    sizeof(transferCharacteristicsTables) / sizeof(transferCharacteristicsTables[0]);
494
495
avifTransferFunction avifTransferCharacteristicsGetGammaToLinearFunction(avifTransferCharacteristics atc)
496
0
{
497
0
    for (int i = 0; i < avifTransferCharacteristicsTableSize; ++i) {
498
0
        const struct avifTransferCharacteristicsTable * const table = &transferCharacteristicsTables[i];
499
0
        if (table->transferCharacteristicsEnum == atc) {
500
0
            return table->toLinear;
501
0
        }
502
0
    }
503
0
    return avifToLinear709; // Provide a reasonable default.
504
0
}
505
506
avifTransferFunction avifTransferCharacteristicsGetLinearToGammaFunction(avifTransferCharacteristics atc)
507
0
{
508
0
    for (int i = 0; i < avifTransferCharacteristicsTableSize; ++i) {
509
0
        const struct avifTransferCharacteristicsTable * const table = &transferCharacteristicsTables[i];
510
0
        if (table->transferCharacteristicsEnum == atc) {
511
0
            return table->toGamma;
512
0
        }
513
0
    }
514
0
    return avifToGamma709; // Provide a reasonable default.
515
0
}
516
517
void avifColorPrimariesComputeYCoeffs(avifColorPrimaries colorPrimaries, float coeffs[3])
518
196
{
519
196
    float primaries[8];
520
196
    avifColorPrimariesGetValues(colorPrimaries, primaries);
521
196
    float const rX = primaries[0];
522
196
    float const rY = primaries[1];
523
196
    float const gX = primaries[2];
524
196
    float const gY = primaries[3];
525
196
    float const bX = primaries[4];
526
196
    float const bY = primaries[5];
527
196
    float const wX = primaries[6];
528
196
    float const wY = primaries[7];
529
196
    float const rZ = 1.0f - (rX + rY); // (Eq. 34)
530
196
    float const gZ = 1.0f - (gX + gY); // (Eq. 35)
531
196
    float const bZ = 1.0f - (bX + bY); // (Eq. 36)
532
196
    float const wZ = 1.0f - (wX + wY); // (Eq. 37)
533
196
    float const kr = (rY * (wX * (gY * bZ - bY * gZ) + wY * (bX * gZ - gX * bZ) + wZ * (gX * bY - bX * gY))) /
534
196
                     (wY * (rX * (gY * bZ - bY * gZ) + gX * (bY * rZ - rY * bZ) + bX * (rY * gZ - gY * rZ)));
535
    // (Eq. 32)
536
196
    float const kb = (bY * (wX * (rY * gZ - gY * rZ) + wY * (gX * rZ - rX * gZ) + wZ * (rX * gY - gX * rY))) /
537
196
                     (wY * (rX * (gY * bZ - bY * gZ) + gX * (bY * rZ - rY * bZ) + bX * (rY * gZ - gY * rZ)));
538
    // (Eq. 33)
539
196
    coeffs[0] = kr;
540
196
    coeffs[2] = kb;
541
196
    coeffs[1] = 1.0f - coeffs[0] - coeffs[2];
542
196
}