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 | } |