Branch data Line data Source code
1 : : /*
2 : : * This file is part of libplacebo.
3 : : *
4 : : * libplacebo is free software; you can redistribute it and/or
5 : : * modify it under the terms of the GNU Lesser General Public
6 : : * License as published by the Free Software Foundation; either
7 : : * version 2.1 of the License, or (at your option) any later version.
8 : : *
9 : : * libplacebo is distributed in the hope that it will be useful,
10 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : : * GNU Lesser General Public License for more details.
13 : : *
14 : : * You should have received a copy of the GNU Lesser General Public
15 : : * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
16 : : */
17 : :
18 : : #include <math.h>
19 : :
20 : : #include "common.h"
21 : : #include "colorspace.h"
22 : : #include "hash.h"
23 : :
24 : : #include <libplacebo/colorspace.h>
25 : : #include <libplacebo/tone_mapping.h>
26 : :
27 : 10466 : bool pl_color_system_is_ycbcr_like(enum pl_color_system sys)
28 : : {
29 [ + - + ]: 10466 : switch (sys) {
30 : : case PL_COLOR_SYSTEM_UNKNOWN:
31 : : case PL_COLOR_SYSTEM_RGB:
32 : : case PL_COLOR_SYSTEM_XYZ:
33 : : return false;
34 : 6208 : case PL_COLOR_SYSTEM_BT_601:
35 : : case PL_COLOR_SYSTEM_BT_709:
36 : : case PL_COLOR_SYSTEM_SMPTE_240M:
37 : : case PL_COLOR_SYSTEM_BT_2020_NC:
38 : : case PL_COLOR_SYSTEM_BT_2020_C:
39 : : case PL_COLOR_SYSTEM_BT_2100_PQ:
40 : : case PL_COLOR_SYSTEM_BT_2100_HLG:
41 : : case PL_COLOR_SYSTEM_DOLBYVISION:
42 : : case PL_COLOR_SYSTEM_YCGCO:
43 : 6208 : return true;
44 : : case PL_COLOR_SYSTEM_COUNT: break;
45 : : };
46 : :
47 : 0 : pl_unreachable();
48 : : }
49 : :
50 : 12 : bool pl_color_system_is_linear(enum pl_color_system sys)
51 : : {
52 [ + - + ]: 12 : switch (sys) {
53 : : case PL_COLOR_SYSTEM_UNKNOWN:
54 : : case PL_COLOR_SYSTEM_RGB:
55 : : case PL_COLOR_SYSTEM_BT_601:
56 : : case PL_COLOR_SYSTEM_BT_709:
57 : : case PL_COLOR_SYSTEM_SMPTE_240M:
58 : : case PL_COLOR_SYSTEM_BT_2020_NC:
59 : : case PL_COLOR_SYSTEM_YCGCO:
60 : : return true;
61 : 5 : case PL_COLOR_SYSTEM_BT_2020_C:
62 : : case PL_COLOR_SYSTEM_BT_2100_PQ:
63 : : case PL_COLOR_SYSTEM_BT_2100_HLG:
64 : : case PL_COLOR_SYSTEM_DOLBYVISION:
65 : : case PL_COLOR_SYSTEM_XYZ:
66 : 5 : return false;
67 : : case PL_COLOR_SYSTEM_COUNT: break;
68 : : };
69 : :
70 : 0 : pl_unreachable();
71 : : }
72 : :
73 : : const char *const pl_color_system_names[PL_COLOR_SYSTEM_COUNT] = {
74 : : [PL_COLOR_SYSTEM_UNKNOWN] = "Auto (unknown)",
75 : : [PL_COLOR_SYSTEM_BT_601] = "ITU-R Rec. BT.601 (SD)",
76 : : [PL_COLOR_SYSTEM_BT_709] = "ITU-R Rec. BT.709 (HD)",
77 : : [PL_COLOR_SYSTEM_SMPTE_240M] = "SMPTE-240M",
78 : : [PL_COLOR_SYSTEM_BT_2020_NC] = "ITU-R Rec. BT.2020 (non-constant luminance)",
79 : : [PL_COLOR_SYSTEM_BT_2020_C] = "ITU-R Rec. BT.2020 (constant luminance)",
80 : : [PL_COLOR_SYSTEM_BT_2100_PQ] = "ITU-R Rec. BT.2100 ICtCp PQ variant",
81 : : [PL_COLOR_SYSTEM_BT_2100_HLG] = "ITU-R Rec. BT.2100 ICtCp HLG variant",
82 : : [PL_COLOR_SYSTEM_DOLBYVISION] = "Dolby Vision (invalid for output)",
83 : : [PL_COLOR_SYSTEM_YCGCO] = "YCgCo (derived from RGB)",
84 : : [PL_COLOR_SYSTEM_RGB] = "Red, Green and Blue",
85 : : [PL_COLOR_SYSTEM_XYZ] = "Digital Cinema Distribution Master (XYZ)",
86 : : };
87 : :
88 : 25 : const char *pl_color_system_name(enum pl_color_system sys)
89 : : {
90 [ - + ]: 25 : pl_assert(sys >= 0 && sys < PL_COLOR_SYSTEM_COUNT);
91 : 25 : return pl_color_system_names[sys];
92 : : }
93 : :
94 : 5 : enum pl_color_system pl_color_system_guess_ycbcr(int width, int height)
95 : : {
96 [ + + ]: 5 : if (width >= 1280 || height > 576) {
97 : : // Typical HD content
98 : : return PL_COLOR_SYSTEM_BT_709;
99 : : } else {
100 : : // Typical SD content
101 : 3 : return PL_COLOR_SYSTEM_BT_601;
102 : : }
103 : : }
104 : :
105 : 1033 : bool pl_bit_encoding_equal(const struct pl_bit_encoding *b1,
106 : : const struct pl_bit_encoding *b2)
107 : : {
108 : 2066 : return b1->sample_depth == b2->sample_depth &&
109 [ + - + - ]: 1033 : b1->color_depth == b2->color_depth &&
110 [ - + ]: 1033 : b1->bit_shift == b2->bit_shift;
111 : : }
112 : :
113 : : const struct pl_color_repr pl_color_repr_unknown = {0};
114 : :
115 : : const struct pl_color_repr pl_color_repr_rgb = {
116 : : .sys = PL_COLOR_SYSTEM_RGB,
117 : : .levels = PL_COLOR_LEVELS_FULL,
118 : : };
119 : :
120 : : const struct pl_color_repr pl_color_repr_sdtv = {
121 : : .sys = PL_COLOR_SYSTEM_BT_601,
122 : : .levels = PL_COLOR_LEVELS_LIMITED,
123 : : };
124 : :
125 : : const struct pl_color_repr pl_color_repr_hdtv = {
126 : : .sys = PL_COLOR_SYSTEM_BT_709,
127 : : .levels = PL_COLOR_LEVELS_LIMITED,
128 : : };
129 : :
130 : : const struct pl_color_repr pl_color_repr_uhdtv = {
131 : : .sys = PL_COLOR_SYSTEM_BT_2020_NC,
132 : : .levels = PL_COLOR_LEVELS_LIMITED,
133 : : };
134 : :
135 : : const struct pl_color_repr pl_color_repr_jpeg = {
136 : : .sys = PL_COLOR_SYSTEM_BT_601,
137 : : .levels = PL_COLOR_LEVELS_FULL,
138 : : };
139 : :
140 : 805 : bool pl_color_repr_equal(const struct pl_color_repr *c1,
141 : : const struct pl_color_repr *c2)
142 : : {
143 : 805 : return c1->sys == c2->sys &&
144 : 804 : c1->levels == c2->levels &&
145 [ - + ]: 804 : c1->alpha == c2->alpha &&
146 [ + + - + : 2413 : c1->dovi == c2->dovi &&
- + ]
147 : 804 : pl_bit_encoding_equal(&c1->bits, &c2->bits);
148 : : }
149 : :
150 : : static struct pl_bit_encoding pl_bit_encoding_merge(const struct pl_bit_encoding *orig,
151 : : const struct pl_bit_encoding *new)
152 : : {
153 : : return (struct pl_bit_encoding) {
154 [ + - ]: 1 : .sample_depth = PL_DEF(orig->sample_depth, new->sample_depth),
155 [ + - ]: 1 : .color_depth = PL_DEF(orig->color_depth, new->color_depth),
156 [ + - ]: 1 : .bit_shift = PL_DEF(orig->bit_shift, new->bit_shift),
157 : : };
158 : : }
159 : :
160 : 1 : void pl_color_repr_merge(struct pl_color_repr *orig, const struct pl_color_repr *new)
161 : : {
162 : 1 : *orig = (struct pl_color_repr) {
163 [ + - ]: 1 : .sys = PL_DEF(orig->sys, new->sys),
164 [ + - ]: 1 : .levels = PL_DEF(orig->levels, new->levels),
165 [ + - ]: 1 : .alpha = PL_DEF(orig->alpha, new->alpha),
166 [ + - ]: 1 : .dovi = PL_DEF(orig->dovi, new->dovi),
167 : : .bits = pl_bit_encoding_merge(&orig->bits, &new->bits),
168 : : };
169 : 1 : }
170 : :
171 : 4799 : enum pl_color_levels pl_color_levels_guess(const struct pl_color_repr *repr)
172 : : {
173 [ + + ]: 4799 : if (repr->sys == PL_COLOR_SYSTEM_DOLBYVISION)
174 : : return PL_COLOR_LEVELS_FULL;
175 : :
176 [ + + ]: 4793 : if (repr->levels)
177 : : return repr->levels;
178 : :
179 : 117 : return pl_color_system_is_ycbcr_like(repr->sys)
180 : : ? PL_COLOR_LEVELS_LIMITED
181 [ + + ]: 117 : : PL_COLOR_LEVELS_FULL;
182 : : }
183 : :
184 : 3014 : float pl_color_repr_normalize(struct pl_color_repr *repr)
185 : : {
186 : : float scale = 1.0;
187 : : struct pl_bit_encoding *bits = &repr->bits;
188 : :
189 [ + + ]: 3014 : if (bits->bit_shift) {
190 : 1 : scale /= (1LL << bits->bit_shift);
191 : 1 : bits->bit_shift = 0;
192 : : }
193 : :
194 : : // If one of these is set but not the other, use the set one
195 [ + + ]: 3014 : int tex_bits = PL_DEF(bits->sample_depth, 8);
196 [ + + ]: 3014 : int col_bits = PL_DEF(bits->color_depth, tex_bits);
197 : : tex_bits = PL_DEF(tex_bits, col_bits);
198 : :
199 [ + + ]: 3014 : if (pl_color_levels_guess(repr) == PL_COLOR_LEVELS_LIMITED) {
200 : : // Limit range is always shifted directly
201 : 112 : scale *= (float) (1LL << tex_bits) / (1LL << col_bits);
202 : : } else {
203 : : // Full range always uses the full range available
204 : 2902 : scale *= ((1LL << tex_bits) - 1.) / ((1LL << col_bits) - 1.);
205 : : }
206 : :
207 : 3014 : bits->color_depth = bits->sample_depth;
208 : 3014 : return scale;
209 : : }
210 : :
211 : 17 : bool pl_color_primaries_is_wide_gamut(enum pl_color_primaries prim)
212 : : {
213 [ + - + ]: 17 : switch (prim) {
214 : : case PL_COLOR_PRIM_UNKNOWN:
215 : : case PL_COLOR_PRIM_BT_601_525:
216 : : case PL_COLOR_PRIM_BT_601_625:
217 : : case PL_COLOR_PRIM_BT_709:
218 : : case PL_COLOR_PRIM_BT_470M:
219 : : case PL_COLOR_PRIM_EBU_3213:
220 : : return false;
221 : 9 : case PL_COLOR_PRIM_BT_2020:
222 : : case PL_COLOR_PRIM_APPLE:
223 : : case PL_COLOR_PRIM_ADOBE:
224 : : case PL_COLOR_PRIM_PRO_PHOTO:
225 : : case PL_COLOR_PRIM_CIE_1931:
226 : : case PL_COLOR_PRIM_DCI_P3:
227 : : case PL_COLOR_PRIM_DISPLAY_P3:
228 : : case PL_COLOR_PRIM_V_GAMUT:
229 : : case PL_COLOR_PRIM_S_GAMUT:
230 : : case PL_COLOR_PRIM_FILM_C:
231 : : case PL_COLOR_PRIM_ACES_AP0:
232 : : case PL_COLOR_PRIM_ACES_AP1:
233 : 9 : return true;
234 : : case PL_COLOR_PRIM_COUNT: break;
235 : : }
236 : :
237 : 0 : pl_unreachable();
238 : : }
239 : :
240 : : const char *const pl_color_primaries_names[PL_COLOR_PRIM_COUNT] = {
241 : : [PL_COLOR_PRIM_UNKNOWN] = "Auto (unknown)",
242 : : [PL_COLOR_PRIM_BT_601_525] = "ITU-R Rec. BT.601 (525-line = NTSC, SMPTE-C)",
243 : : [PL_COLOR_PRIM_BT_601_625] = "ITU-R Rec. BT.601 (625-line = PAL, SECAM)",
244 : : [PL_COLOR_PRIM_BT_709] = "ITU-R Rec. BT.709 (HD), also sRGB",
245 : : [PL_COLOR_PRIM_BT_470M] = "ITU-R Rec. BT.470 M",
246 : : [PL_COLOR_PRIM_EBU_3213] = "EBU Tech. 3213-E / JEDEC P22 phosphors",
247 : : [PL_COLOR_PRIM_BT_2020] = "ITU-R Rec. BT.2020 (Ultra HD)",
248 : : [PL_COLOR_PRIM_APPLE] = "Apple RGB",
249 : : [PL_COLOR_PRIM_ADOBE] = "Adobe RGB (1998)",
250 : : [PL_COLOR_PRIM_PRO_PHOTO] = "ProPhoto RGB (ROMM)",
251 : : [PL_COLOR_PRIM_CIE_1931] = "CIE 1931 RGB primaries",
252 : : [PL_COLOR_PRIM_DCI_P3] = "DCI-P3 (Digital Cinema)",
253 : : [PL_COLOR_PRIM_DISPLAY_P3] = "DCI-P3 (Digital Cinema) with D65 white point",
254 : : [PL_COLOR_PRIM_V_GAMUT] = "Panasonic V-Gamut (VARICAM)",
255 : : [PL_COLOR_PRIM_S_GAMUT] = "Sony S-Gamut",
256 : : [PL_COLOR_PRIM_FILM_C] = "Traditional film primaries with Illuminant C",
257 : : [PL_COLOR_PRIM_ACES_AP0] = "ACES Primaries #0",
258 : : [PL_COLOR_PRIM_ACES_AP1] = "ACES Primaries #1",
259 : : };
260 : :
261 : 11 : const char *pl_color_primaries_name(enum pl_color_primaries prim)
262 : : {
263 [ - + ]: 11 : pl_assert(prim >= 0 && prim < PL_COLOR_PRIM_COUNT);
264 : 11 : return pl_color_primaries_names[prim];
265 : : }
266 : :
267 : 7 : enum pl_color_primaries pl_color_primaries_guess(int width, int height)
268 : : {
269 : : // HD content
270 [ + + ]: 7 : if (width >= 1280 || height > 576)
271 : : return PL_COLOR_PRIM_BT_709;
272 : :
273 [ + + + ]: 4 : switch (height) {
274 : : case 576: // Typical PAL content, including anamorphic/squared
275 : : return PL_COLOR_PRIM_BT_601_625;
276 : :
277 : 1 : case 480: // Typical NTSC content, including squared
278 : : case 486: // NTSC Pro or anamorphic NTSC
279 : 1 : return PL_COLOR_PRIM_BT_601_525;
280 : :
281 : : default: // No good metric, just pick BT.709 to minimize damage
282 : : return PL_COLOR_PRIM_BT_709;
283 : : }
284 : : }
285 : :
286 : : const char *const pl_color_transfer_names[PL_COLOR_TRC_COUNT] = {
287 : : [PL_COLOR_TRC_UNKNOWN] = "Auto (unknown SDR)",
288 : : [PL_COLOR_TRC_BT_1886] = "ITU-R Rec. BT.1886 (CRT emulation + OOTF)",
289 : : [PL_COLOR_TRC_SRGB] = "IEC 61966-2-4 sRGB (CRT emulation)",
290 : : [PL_COLOR_TRC_LINEAR] = "Linear light content",
291 : : [PL_COLOR_TRC_GAMMA18] = "Pure power gamma 1.8",
292 : : [PL_COLOR_TRC_GAMMA20] = "Pure power gamma 2.0",
293 : : [PL_COLOR_TRC_GAMMA22] = "Pure power gamma 2.2",
294 : : [PL_COLOR_TRC_GAMMA24] = "Pure power gamma 2.4",
295 : : [PL_COLOR_TRC_GAMMA26] = "Pure power gamma 2.6",
296 : : [PL_COLOR_TRC_GAMMA28] = "Pure power gamma 2.8",
297 : : [PL_COLOR_TRC_PRO_PHOTO] = "ProPhoto RGB (ROMM)",
298 : : [PL_COLOR_TRC_ST428] = "Digital Cinema Distribution Master (XYZ)",
299 : : [PL_COLOR_TRC_PQ] = "ITU-R BT.2100 PQ (perceptual quantizer), aka SMPTE ST2048",
300 : : [PL_COLOR_TRC_HLG] = "ITU-R BT.2100 HLG (hybrid log-gamma), aka ARIB STD-B67",
301 : : [PL_COLOR_TRC_V_LOG] = "Panasonic V-Log (VARICAM)",
302 : : [PL_COLOR_TRC_S_LOG1] = "Sony S-Log1",
303 : : [PL_COLOR_TRC_S_LOG2] = "Sony S-Log2",
304 : : };
305 : :
306 : 61 : const char *pl_color_transfer_name(enum pl_color_transfer trc)
307 : : {
308 [ - + ]: 61 : pl_assert(trc >= 0 && trc < PL_COLOR_TRC_COUNT);
309 : 61 : return pl_color_transfer_names[trc];
310 : : }
311 : :
312 : : // HLG 75% value (scene-referred)
313 : : #define HLG_75 3.17955
314 : :
315 [ + - ]: 96900 : float pl_color_transfer_nominal_peak(enum pl_color_transfer trc)
316 : : {
317 : : switch (trc) {
318 : : case PL_COLOR_TRC_UNKNOWN:
319 : : case PL_COLOR_TRC_BT_1886:
320 : : case PL_COLOR_TRC_SRGB:
321 : : case PL_COLOR_TRC_LINEAR:
322 : : case PL_COLOR_TRC_GAMMA18:
323 : : case PL_COLOR_TRC_GAMMA20:
324 : : case PL_COLOR_TRC_GAMMA22:
325 : : case PL_COLOR_TRC_GAMMA24:
326 : : case PL_COLOR_TRC_GAMMA26:
327 : : case PL_COLOR_TRC_GAMMA28:
328 : : case PL_COLOR_TRC_PRO_PHOTO:
329 : : case PL_COLOR_TRC_ST428:
330 : : return 1.0;
331 : : case PL_COLOR_TRC_PQ: return 10000.0 / PL_COLOR_SDR_WHITE;
332 : : case PL_COLOR_TRC_HLG: return 12.0 / HLG_75;
333 : : case PL_COLOR_TRC_V_LOG: return 46.0855;
334 : : case PL_COLOR_TRC_S_LOG1: return 6.52;
335 : : case PL_COLOR_TRC_S_LOG2: return 9.212;
336 : : case PL_COLOR_TRC_COUNT: break;
337 : : }
338 : :
339 : 0 : pl_unreachable();
340 : : }
341 : :
342 : : const struct pl_hdr_metadata pl_hdr_metadata_empty = {0};
343 : : const struct pl_hdr_metadata pl_hdr_metadata_hdr10 ={
344 : : .prim = {
345 : : .red = {0.708, 0.292},
346 : : .green = {0.170, 0.797},
347 : : .blue = {0.131, 0.046},
348 : : .white = {0.31271, 0.32902},
349 : : },
350 : : .min_luma = 0,
351 : : .max_luma = 10000,
352 : : .max_cll = 10000,
353 : : .max_fall = 0, // unknown
354 : : };
355 : :
356 : 333488 : float pl_hdr_rescale(enum pl_hdr_scaling from, enum pl_hdr_scaling to, float x)
357 : : {
358 [ + + ]: 333488 : if (from == to)
359 : : return x;
360 [ + + ]: 257570 : if (!x) // micro-optimization for common value
361 : : return x;
362 : :
363 : 257452 : x = fmaxf(x, 0.0f);
364 : :
365 : : // Convert input to PL_SCALE_RELATIVE
366 [ + + + - : 257452 : switch (from) {
- ]
367 : 54316 : case PL_HDR_PQ:
368 : 54316 : x = powf(x, 1.0f / PQ_M2);
369 : 54316 : x = fmaxf(x - PQ_C1, 0.0f) / (PQ_C2 - PQ_C3 * x);
370 : 54316 : x = powf(x, 1.0f / PQ_M1);
371 : 54316 : x *= 10000.0f;
372 : : // fall through
373 : 222969 : case PL_HDR_NITS:
374 : 222969 : x /= PL_COLOR_SDR_WHITE;
375 : : // fall through
376 : 257452 : case PL_HDR_NORM:
377 : 257452 : goto output;
378 : 0 : case PL_HDR_SQRT:
379 : 0 : x *= x;
380 : 0 : goto output;
381 : : case PL_HDR_SCALING_COUNT:
382 : : break;
383 : : }
384 : :
385 : 0 : pl_unreachable();
386 : :
387 : 257452 : output:
388 : : // Convert PL_SCALE_RELATIVE to output
389 [ - + + - : 257452 : switch (to) {
+ ]
390 : : case PL_HDR_NORM:
391 : : return x;
392 : 0 : case PL_HDR_SQRT:
393 : 0 : return sqrtf(x);
394 : 40368 : case PL_HDR_NITS:
395 : 40368 : return x * PL_COLOR_SDR_WHITE;
396 : 3261 : case PL_HDR_PQ:
397 : 3261 : x *= PL_COLOR_SDR_WHITE / 10000.0f;
398 : 3261 : x = powf(x, PQ_M1);
399 : 3261 : x = (PQ_C1 + PQ_C2 * x) / (1.0f + PQ_C3 * x);
400 : 3261 : x = powf(x, PQ_M2);
401 : 3261 : return x;
402 : : case PL_HDR_SCALING_COUNT:
403 : : break;
404 : : }
405 : :
406 : 0 : pl_unreachable();
407 : : }
408 : :
409 : 2538 : static inline bool pl_hdr_bezier_equal(const struct pl_hdr_bezier *a,
410 : : const struct pl_hdr_bezier *b)
411 : : {
412 : 5076 : return a->target_luma == b->target_luma &&
413 [ + - ]: 2538 : a->knee_x == b->knee_x &&
414 [ + - ]: 2538 : a->knee_y == b->knee_y &&
415 [ + - + - ]: 5076 : a->num_anchors == b->num_anchors &&
416 [ - + ]: 2538 : !memcmp(a->anchors, b->anchors, sizeof(a->anchors[0]) * a->num_anchors);
417 : : }
418 : :
419 : 2542 : bool pl_hdr_metadata_equal(const struct pl_hdr_metadata *a,
420 : : const struct pl_hdr_metadata *b)
421 : : {
422 : 2542 : return pl_raw_primaries_equal(&a->prim, &b->prim) &&
423 [ - + ]: 2538 : a->min_luma == b->min_luma &&
424 [ - + ]: 2538 : a->max_luma == b->max_luma &&
425 [ - + ]: 2538 : a->max_cll == b->max_cll &&
426 [ - + ]: 2538 : a->max_fall == b->max_fall &&
427 [ - + ]: 2538 : a->scene_max[0] == b->scene_max[0] &&
428 [ - + ]: 2538 : a->scene_max[1] == b->scene_max[1] &&
429 [ - + ]: 2538 : a->scene_max[2] == b->scene_max[2] &&
430 [ - + ]: 2538 : a->scene_avg == b->scene_avg &&
431 [ - + ]: 2538 : pl_hdr_bezier_equal(&a->ootf, &b->ootf) &&
432 [ + + - + ]: 5080 : a->max_pq_y == b->max_pq_y &&
433 [ - + ]: 2538 : a->avg_pq_y == b->avg_pq_y;
434 : : }
435 : :
436 : 57 : void pl_hdr_metadata_merge(struct pl_hdr_metadata *orig,
437 : : const struct pl_hdr_metadata *update)
438 : : {
439 : 57 : pl_raw_primaries_merge(&orig->prim, &update->prim);
440 [ + - ]: 57 : if (!orig->min_luma)
441 : 57 : orig->min_luma = update->min_luma;
442 [ + - ]: 57 : if (!orig->max_luma)
443 : 57 : orig->max_luma = update->max_luma;
444 [ + - ]: 57 : if (!orig->max_cll)
445 : 57 : orig->max_cll = update->max_cll;
446 [ + - ]: 57 : if (!orig->max_fall)
447 : 57 : orig->max_fall = update->max_fall;
448 [ + - ]: 57 : if (!orig->scene_max[1])
449 : 57 : memcpy(orig->scene_max, update->scene_max, sizeof(orig->scene_max));
450 [ + - ]: 57 : if (!orig->scene_avg)
451 : 57 : orig->scene_avg = update->scene_avg;
452 [ + - ]: 57 : if (!orig->ootf.target_luma)
453 : 57 : orig->ootf = update->ootf;
454 [ + - ]: 57 : if (!orig->max_pq_y)
455 : 57 : orig->max_pq_y = update->max_pq_y;
456 [ + - ]: 57 : if (!orig->avg_pq_y)
457 : 57 : orig->avg_pq_y = update->avg_pq_y;
458 : 57 : }
459 : :
460 : 155 : bool pl_hdr_metadata_contains(const struct pl_hdr_metadata *data,
461 : : enum pl_hdr_metadata_type type)
462 : : {
463 : 155 : bool has_hdr10 = data->max_luma;
464 [ + + - + ]: 155 : bool has_hdr10plus = data->scene_avg && (data->scene_max[0] ||
465 [ # # ]: 0 : data->scene_max[1] ||
466 [ # # ]: 0 : data->scene_max[2]);
467 [ + + - + ]: 155 : bool has_cie_y = data->max_pq_y && data->avg_pq_y;
468 : :
469 [ + + + + : 155 : switch (type) {
- + ]
470 : : case PL_HDR_METADATA_NONE: return true;
471 : 5 : case PL_HDR_METADATA_ANY: return has_hdr10 || has_hdr10plus || has_cie_y;
472 : 5 : case PL_HDR_METADATA_HDR10: return has_hdr10;
473 : 70 : case PL_HDR_METADATA_HDR10PLUS: return has_hdr10plus;
474 : 70 : case PL_HDR_METADATA_CIE_Y: return has_cie_y;
475 : : case PL_HDR_METADATA_TYPE_COUNT: break;
476 : : }
477 : :
478 : 0 : pl_unreachable();
479 : : }
480 : :
481 : : const struct pl_color_space pl_color_space_unknown = {0};
482 : :
483 : : const struct pl_color_space pl_color_space_srgb = {
484 : : .primaries = PL_COLOR_PRIM_BT_709,
485 : : .transfer = PL_COLOR_TRC_SRGB,
486 : : };
487 : :
488 : : const struct pl_color_space pl_color_space_bt709 = {
489 : : .primaries = PL_COLOR_PRIM_BT_709,
490 : : .transfer = PL_COLOR_TRC_BT_1886,
491 : : };
492 : :
493 : : const struct pl_color_space pl_color_space_hdr10 = {
494 : : .primaries = PL_COLOR_PRIM_BT_2020,
495 : : .transfer = PL_COLOR_TRC_PQ,
496 : : };
497 : :
498 : : const struct pl_color_space pl_color_space_bt2020_hlg = {
499 : : .primaries = PL_COLOR_PRIM_BT_2020,
500 : : .transfer = PL_COLOR_TRC_HLG,
501 : : };
502 : :
503 : : const struct pl_color_space pl_color_space_monitor = {
504 : : .primaries = PL_COLOR_PRIM_BT_709, // sRGB primaries
505 : : .transfer = PL_COLOR_TRC_UNKNOWN, // unknown SDR response
506 : : };
507 : :
508 : 8864 : bool pl_color_space_is_hdr(const struct pl_color_space *csp)
509 : : {
510 [ + + + + ]: 17646 : return csp->hdr.max_luma > PL_COLOR_SDR_WHITE ||
511 : 8782 : pl_color_transfer_is_hdr(csp->transfer);
512 : : }
513 : :
514 : 47160 : bool pl_color_space_is_black_scaled(const struct pl_color_space *csp)
515 : : {
516 [ + - + ]: 47160 : switch (csp->transfer) {
517 : : case PL_COLOR_TRC_UNKNOWN:
518 : : case PL_COLOR_TRC_SRGB:
519 : : case PL_COLOR_TRC_LINEAR:
520 : : case PL_COLOR_TRC_GAMMA18:
521 : : case PL_COLOR_TRC_GAMMA20:
522 : : case PL_COLOR_TRC_GAMMA22:
523 : : case PL_COLOR_TRC_GAMMA24:
524 : : case PL_COLOR_TRC_GAMMA26:
525 : : case PL_COLOR_TRC_GAMMA28:
526 : : case PL_COLOR_TRC_PRO_PHOTO:
527 : : case PL_COLOR_TRC_ST428:
528 : : case PL_COLOR_TRC_HLG:
529 : : return true;
530 : :
531 : 12367 : case PL_COLOR_TRC_BT_1886:
532 : : case PL_COLOR_TRC_PQ:
533 : : case PL_COLOR_TRC_V_LOG:
534 : : case PL_COLOR_TRC_S_LOG1:
535 : : case PL_COLOR_TRC_S_LOG2:
536 : 12367 : return false;
537 : :
538 : : case PL_COLOR_TRC_COUNT: break;
539 : : }
540 : :
541 : 0 : pl_unreachable();
542 : : }
543 : :
544 : : #define MAP3(...) \
545 : : do { \
546 : : float X; \
547 : : for (int _i = 0; _i < 3; _i++) { \
548 : : X = color[_i]; \
549 : : color[_i] = __VA_ARGS__; \
550 : : } \
551 : : } while (0)
552 : :
553 : 46320 : void pl_color_linearize(const struct pl_color_space *csp, float color[3])
554 : : {
555 [ + + ]: 46320 : if (csp->transfer == PL_COLOR_TRC_LINEAR)
556 : 5689 : return;
557 : :
558 : : float csp_min, csp_max;
559 : 43526 : pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
560 : : .color = csp,
561 : : .metadata = PL_HDR_METADATA_HDR10,
562 : : .scaling = PL_HDR_NORM,
563 : : .out_min = &csp_min,
564 : : .out_max = &csp_max,
565 : : ));
566 : :
567 [ + + ]: 174104 : MAP3(fmaxf(X, 0));
568 : :
569 [ + + + + : 43526 : switch (csp->transfer) {
+ + + + +
+ + + + +
+ - ]
570 : : case PL_COLOR_TRC_SRGB:
571 [ + + + + ]: 11580 : MAP3(X > 0.04045f ? powf((X + 0.055f) / 1.055f, 2.4f)
572 : : : X / 12.92f);
573 : 2895 : goto scale_out;
574 : 2895 : case PL_COLOR_TRC_BT_1886: {
575 : 2895 : const float lb = powf(csp_min, 1/2.4f);
576 : 2895 : const float lw = powf(csp_max, 1/2.4f);
577 : 2895 : const float a = powf(lw - lb, 2.4f);
578 : 2895 : const float b = lb / (lw - lb);
579 [ + + ]: 11580 : MAP3(a * powf(X + b, 2.4f));
580 : : return;
581 : : }
582 [ + + ]: 11580 : case PL_COLOR_TRC_GAMMA18: MAP3(powf(X, 1.8f)); goto scale_out;
583 [ + + ]: 11580 : case PL_COLOR_TRC_GAMMA20: MAP3(powf(X, 2.0f)); goto scale_out;
584 : : case PL_COLOR_TRC_UNKNOWN:
585 [ + + ]: 11984 : case PL_COLOR_TRC_GAMMA22: MAP3(powf(X, 2.2f)); goto scale_out;
586 [ + + ]: 11580 : case PL_COLOR_TRC_GAMMA24: MAP3(powf(X, 2.4f)); goto scale_out;
587 [ + + ]: 11580 : case PL_COLOR_TRC_GAMMA26: MAP3(powf(X, 2.6f)); goto scale_out;
588 [ + + ]: 11580 : case PL_COLOR_TRC_GAMMA28: MAP3(powf(X, 2.8f)); goto scale_out;
589 : : case PL_COLOR_TRC_PRO_PHOTO:
590 [ + + + + ]: 11580 : MAP3(X > 0.03125f ? powf(X, 1.8f) : X / 16);
591 : 2895 : goto scale_out;
592 : : case PL_COLOR_TRC_ST428:
593 [ + + ]: 11580 : MAP3(52.37f/48 * powf(X, 2.6f));
594 : 2895 : goto scale_out;
595 : : case PL_COLOR_TRC_PQ:
596 [ + + ]: 11580 : MAP3(powf(X, 1 / PQ_M2));
597 [ + + ]: 11580 : MAP3(fmaxf(X - PQ_C1, 0) / (PQ_C2 - PQ_C3 * X));
598 [ + + ]: 11580 : MAP3(10000 / PL_COLOR_SDR_WHITE * powf(X, 1 / PQ_M1));
599 : 2895 : goto scale_out;
600 : 2895 : case PL_COLOR_TRC_HLG: {
601 : 2895 : const float y = fmaxf(1.2f + 0.42f * log10f(csp_max / HLG_REF), 1);
602 : 2895 : const float b = sqrtf(3 * powf(csp_min / csp_max, 1 / y));
603 : : const pl_matrix3x3 rgb2xyz =
604 : 2895 : pl_get_rgb2xyz_matrix(pl_raw_primaries_get(csp->primaries));
605 : : const float *coef = rgb2xyz.m[1];
606 : : // OETF^-1
607 [ + + ]: 11580 : MAP3((1 - b) * X + b);
608 [ + + + + ]: 11580 : MAP3(X > 0.5f ? expf((X - HLG_C) / HLG_A) + HLG_B
609 : : : 4 * X * X);
610 : : // OOTF
611 : 2895 : float luma = coef[0] * color[0] + coef[1] * color[1] + coef[2] * color[2];
612 : 2895 : luma = powf(fmaxf(luma / 12, 0), y - 1);
613 [ + + ]: 11580 : MAP3(luma * X / 12);
614 : 2895 : goto scale_out;
615 : : }
616 : : case PL_COLOR_TRC_V_LOG:
617 [ + + + + ]: 11580 : MAP3(X >= 0.181f ? powf(10, (X - VLOG_D) / VLOG_C) - VLOG_B
618 : : : (X - 0.125f) / 5.6f);
619 : 2895 : goto scale_out;
620 : : case PL_COLOR_TRC_S_LOG1:
621 [ + + ]: 11580 : MAP3(powf(10, (X - SLOG_C) / SLOG_A) - SLOG_B);
622 : 2895 : goto scale_out;
623 : : case PL_COLOR_TRC_S_LOG2:
624 [ + + + + ]: 11580 : MAP3(X >= SLOG_Q ? (powf(10, (X - SLOG_C) / SLOG_A) - SLOG_B) / SLOG_K2
625 : : : (X - SLOG_Q) / SLOG_P);
626 : 2895 : goto scale_out;
627 : : case PL_COLOR_TRC_LINEAR:
628 : : case PL_COLOR_TRC_COUNT:
629 : : break;
630 : : }
631 : :
632 : 0 : pl_unreachable();
633 : :
634 : 40631 : scale_out:
635 [ + + + + ]: 40631 : if (pl_color_space_is_black_scaled(csp) && csp->transfer != PL_COLOR_TRC_HLG)
636 [ + + ]: 104624 : MAP3((csp_max - csp_min) * X + csp_min);
637 : : }
638 : :
639 : 1313 : void pl_color_delinearize(const struct pl_color_space *csp, float color[3])
640 : : {
641 [ + - ]: 1313 : if (csp->transfer == PL_COLOR_TRC_LINEAR)
642 : : return;
643 : :
644 : : float csp_min, csp_max;
645 : 1313 : pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
646 : : .color = csp,
647 : : .metadata = PL_HDR_METADATA_HDR10,
648 : : .scaling = PL_HDR_NORM,
649 : : .out_min = &csp_min,
650 : : .out_max = &csp_max,
651 : : ));
652 : :
653 [ + + + + ]: 1313 : if (pl_color_space_is_black_scaled(csp) && csp->transfer != PL_COLOR_TRC_HLG)
654 [ + + ]: 4040 : MAP3((X - csp_min) / (csp_max - csp_min));
655 : :
656 [ + + ]: 5252 : MAP3(fmaxf(X, 0));
657 : :
658 [ + + + + : 1313 : switch (csp->transfer) {
+ + + + +
+ + + - -
- - ]
659 : : case PL_COLOR_TRC_SRGB:
660 [ + + + + ]: 404 : MAP3(X >= 0.0031308f ? 1.055f * powf(X, 1/2.4f) - 0.055f
661 : : : 12.92f * X);
662 : : return;
663 : 101 : case PL_COLOR_TRC_BT_1886: {
664 : 101 : const float lb = powf(csp_min, 1/2.4f);
665 : 101 : const float lw = powf(csp_max, 1/2.4f);
666 : 101 : const float a = powf(lw - lb, 2.4f);
667 : 101 : const float b = lb / (lw - lb);
668 [ + + ]: 404 : MAP3(powf(X / a, 1/2.4f) - b);
669 : : return;
670 : : }
671 [ + + ]: 404 : case PL_COLOR_TRC_GAMMA18: MAP3(powf(X, 1/1.8f)); return;
672 [ + + ]: 404 : case PL_COLOR_TRC_GAMMA20: MAP3(powf(X, 1/2.0f)); return;
673 : : case PL_COLOR_TRC_UNKNOWN:
674 [ + + ]: 808 : case PL_COLOR_TRC_GAMMA22: MAP3(powf(X, 1/2.2f)); return;
675 [ + + ]: 404 : case PL_COLOR_TRC_GAMMA24: MAP3(powf(X, 1/2.4f)); return;
676 [ + + ]: 404 : case PL_COLOR_TRC_GAMMA26: MAP3(powf(X, 1/2.6f)); return;
677 [ + + ]: 404 : case PL_COLOR_TRC_GAMMA28: MAP3(powf(X, 1/2.8f)); return;
678 : : case PL_COLOR_TRC_ST428:
679 [ + + ]: 404 : MAP3(powf(X * 48/52.37f, 1/2.6f));
680 : : return;
681 : : case PL_COLOR_TRC_PRO_PHOTO:
682 [ + + + + ]: 404 : MAP3(X >= 0.001953f ? powf(X, 1/1.8f) : 16 * X);
683 : : return;
684 : : case PL_COLOR_TRC_PQ:
685 [ + + ]: 404 : MAP3(powf(X * PL_COLOR_SDR_WHITE / 10000, PQ_M1));
686 [ + + ]: 404 : MAP3(powf((PQ_C1 + PQ_C2 * X) / (1 + PQ_C3 * X), PQ_M2));
687 : : return;
688 : 101 : case PL_COLOR_TRC_HLG: {
689 : 101 : const float y = fmaxf(1.2f + 0.42f * log10f(csp_max / HLG_REF), 1);
690 : 101 : const float b = sqrtf(3 * powf(csp_min / csp_max, 1 / y));
691 : : const pl_matrix3x3 rgb2xyz =
692 : 101 : pl_get_rgb2xyz_matrix(pl_raw_primaries_get(csp->primaries));
693 : : const float *coef = rgb2xyz.m[1];
694 : : // OOTF^-1
695 : 101 : float luma = coef[0] * color[0] + coef[1] * color[1] + coef[2] * color[2];
696 : 101 : luma = fmaxf(1e-6f, powf(luma / csp_max, (1 - y) / y));
697 [ + + ]: 404 : MAP3(12 / csp_max * luma * X);
698 : : // OETF
699 [ + + + + ]: 404 : MAP3(X > 1 ? HLG_A * logf(X - HLG_B) + HLG_C : 0.5f * sqrtf(X));
700 [ + + ]: 404 : MAP3((X - b) / (1 - b));
701 : : return;
702 : : }
703 : : case PL_COLOR_TRC_V_LOG:
704 [ # # # # ]: 0 : MAP3(X >= 0.01f ? VLOG_C * log10f(X + VLOG_B) + VLOG_D
705 : : : 5.6f * X + 0.125f);
706 : : return;
707 : : case PL_COLOR_TRC_S_LOG1:
708 [ # # ]: 0 : MAP3(SLOG_A * log10f(X + SLOG_B) + SLOG_C);
709 : : return;
710 : : case PL_COLOR_TRC_S_LOG2:
711 [ # # # # ]: 0 : MAP3(X >= 0 ? SLOG_A * log10f(SLOG_B * X + SLOG_C)
712 : : : SLOG_P * X + SLOG_Q);
713 : : return;
714 : : case PL_COLOR_TRC_LINEAR:
715 : : case PL_COLOR_TRC_COUNT:
716 : : break;
717 : : }
718 : :
719 : 0 : pl_unreachable();
720 : : }
721 : :
722 : 57 : void pl_color_space_merge(struct pl_color_space *orig,
723 : : const struct pl_color_space *new)
724 : : {
725 [ + - ]: 57 : if (!orig->primaries)
726 : 57 : orig->primaries = new->primaries;
727 [ + - ]: 57 : if (!orig->transfer)
728 : 57 : orig->transfer = new->transfer;
729 : 57 : pl_hdr_metadata_merge(&orig->hdr, &new->hdr);
730 : 57 : }
731 : :
732 : 2584 : bool pl_color_space_equal(const struct pl_color_space *c1,
733 : : const struct pl_color_space *c2)
734 : : {
735 : 5146 : return c1->primaries == c2->primaries &&
736 [ + + + + : 5114 : c1->transfer == c2->transfer &&
+ + ]
737 : 2530 : pl_hdr_metadata_equal(&c1->hdr, &c2->hdr);
738 : : }
739 : :
740 : : // Estimates luminance from maxRGB by looking at how monochromatic MaxSCL is
741 : 2 : static void luma_from_maxrgb(const struct pl_color_space *csp,
742 : : enum pl_hdr_scaling scaling,
743 : : float *out_max, float *out_avg)
744 : : {
745 [ + - + - ]: 4 : const float maxscl = PL_MAX3(csp->hdr.scene_max[0],
746 : : csp->hdr.scene_max[1],
747 : : csp->hdr.scene_max[2]);
748 [ - + ]: 2 : if (!maxscl)
749 : 0 : return;
750 : :
751 : 2 : struct pl_raw_primaries prim = csp->hdr.prim;
752 : 2 : pl_raw_primaries_merge(&prim, pl_raw_primaries_get(csp->primaries));
753 : 2 : const pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(&prim);
754 : :
755 : 2 : const float max_luma = rgb2xyz.m[1][0] * csp->hdr.scene_max[0] +
756 : 2 : rgb2xyz.m[1][1] * csp->hdr.scene_max[1] +
757 : 2 : rgb2xyz.m[1][2] * csp->hdr.scene_max[2];
758 : :
759 : 2 : const float coef = max_luma / maxscl;
760 : 2 : *out_max = pl_hdr_rescale(PL_HDR_NITS, scaling, max_luma);
761 : 2 : *out_avg = pl_hdr_rescale(PL_HDR_NITS, scaling, coef * csp->hdr.scene_avg);
762 : : }
763 : :
764 : : static inline bool metadata_compat(enum pl_hdr_metadata_type metadata,
765 : : enum pl_hdr_metadata_type compat)
766 : : {
767 : 104680 : return metadata == PL_HDR_METADATA_ANY || metadata == compat;
768 : : }
769 : :
770 : 52340 : void pl_color_space_nominal_luma_ex(const struct pl_nominal_luma_params *params)
771 : : {
772 [ + - + + : 52340 : if (!params || (!params->out_min && !params->out_max && !params->out_avg))
- + - - ]
773 : 0 : return;
774 : :
775 : 52340 : const struct pl_color_space *csp = params->color;
776 : 52340 : const enum pl_hdr_scaling scaling = params->scaling;
777 : :
778 : 52340 : float min_luma = 0, max_luma = 0, avg_luma = 0;
779 [ + + ]: 52340 : if (params->metadata != PL_HDR_METADATA_NONE) {
780 : : // Initialize from static HDR10 metadata, in all cases
781 : 52334 : min_luma = pl_hdr_rescale(PL_HDR_NITS, scaling, csp->hdr.min_luma);
782 : 52334 : max_luma = pl_hdr_rescale(PL_HDR_NITS, scaling, csp->hdr.max_luma);
783 : : }
784 : :
785 [ + + + + ]: 52406 : if (metadata_compat(params->metadata, PL_HDR_METADATA_HDR10PLUS) &&
786 : 66 : pl_hdr_metadata_contains(&csp->hdr, PL_HDR_METADATA_HDR10PLUS))
787 : : {
788 : 2 : luma_from_maxrgb(csp, scaling, &max_luma, &avg_luma);
789 : : }
790 : :
791 [ + + + + ]: 52406 : if (metadata_compat(params->metadata, PL_HDR_METADATA_CIE_Y) &&
792 : 66 : pl_hdr_metadata_contains(&csp->hdr, PL_HDR_METADATA_CIE_Y))
793 : : {
794 : 14 : max_luma = pl_hdr_rescale(PL_HDR_PQ, scaling, csp->hdr.max_pq_y);
795 : 14 : avg_luma = pl_hdr_rescale(PL_HDR_PQ, scaling, csp->hdr.avg_pq_y);
796 : : }
797 : :
798 : : // Clamp to sane value range
799 : 52340 : const float hdr_min = pl_hdr_rescale(PL_HDR_NITS, scaling, PL_COLOR_HDR_BLACK);
800 : 52340 : const float hdr_max = pl_hdr_rescale(PL_HDR_PQ, scaling, 1.0f);
801 [ + + + + : 52340 : max_luma = max_luma ? PL_CLAMP(max_luma, hdr_min, hdr_max) : 0;
+ + ]
802 [ + + + + : 52340 : min_luma = min_luma ? PL_CLAMP(min_luma, hdr_min, hdr_max) : 0;
+ + ]
803 [ + + + + : 52340 : if ((max_luma && min_luma >= max_luma) || min_luma >= hdr_max)
- + ]
804 : 41912 : min_luma = max_luma = 0; // sanity
805 : :
806 : : // PQ is always scaled down to absolute black, ignoring HDR metadata
807 [ + + ]: 52340 : if (csp->transfer == PL_COLOR_TRC_PQ)
808 : : min_luma = hdr_min;
809 : :
810 : : // Baseline/fallback metadata, inferred entirely from the colorspace
811 : : // description and built-in default assumptions
812 [ + + ]: 52340 : if (!max_luma) {
813 [ + + ]: 46845 : if (csp->transfer == PL_COLOR_TRC_HLG) {
814 : 2801 : max_luma = pl_hdr_rescale(PL_HDR_NITS, scaling, PL_COLOR_HLG_PEAK);
815 : : } else {
816 : 44044 : const float peak = pl_color_transfer_nominal_peak(csp->transfer);
817 : 44044 : max_luma = pl_hdr_rescale(PL_HDR_NORM, scaling, peak);
818 : : }
819 : : }
820 : :
821 [ + + ]: 52340 : if (!min_luma) {
822 [ + + ]: 43970 : if (pl_color_transfer_is_hdr(csp->transfer)) {
823 : : min_luma = hdr_min;
824 : : } else {
825 : 32791 : const float peak = pl_hdr_rescale(scaling, PL_HDR_NITS, max_luma);
826 : 32791 : min_luma = pl_hdr_rescale(PL_HDR_NITS, scaling,
827 : : peak / PL_COLOR_SDR_CONTRAST);
828 : : }
829 : : }
830 : :
831 [ + + ]: 52340 : if (avg_luma)
832 [ + - + - ]: 32 : avg_luma = PL_CLAMP(avg_luma, min_luma, max_luma); // sanity
833 : :
834 [ + + ]: 52340 : if (params->out_min)
835 : 50527 : *params->out_min = min_luma;
836 [ + - ]: 52340 : if (params->out_max)
837 : 52340 : *params->out_max = max_luma;
838 [ + + ]: 52340 : if (params->out_avg)
839 : 82 : *params->out_avg = avg_luma;
840 : : }
841 : :
842 : 6619 : void pl_color_space_infer(struct pl_color_space *space)
843 : : {
844 [ + + ]: 6619 : if (!space->primaries)
845 : 20 : space->primaries = PL_COLOR_PRIM_BT_709;
846 [ + + ]: 6619 : if (!space->transfer)
847 : 18 : space->transfer = PL_COLOR_TRC_BT_1886;
848 : :
849 : : // Sanitize the static HDR metadata
850 [ + + ]: 6619 : pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
851 : : .color = space,
852 : : .metadata = PL_HDR_METADATA_HDR10,
853 : : .scaling = PL_HDR_NITS,
854 : : .out_max = &space->hdr.max_luma,
855 : : // Preserve tagged minimum
856 : : .out_min = space->hdr.min_luma ? NULL : &space->hdr.min_luma,
857 : : ));
858 : :
859 : : // Default the signal color space based on the nominal raw primaries
860 [ + + ]: 6619 : if (!pl_primaries_valid(&space->hdr.prim))
861 : 4872 : space->hdr.prim = *pl_raw_primaries_get(space->primaries);
862 : 6619 : }
863 : :
864 : 3294 : static void infer_both_ref(struct pl_color_space *space,
865 : : struct pl_color_space *ref)
866 : : {
867 : 3294 : pl_color_space_infer(ref);
868 : :
869 [ - + ]: 3294 : if (!space->primaries) {
870 [ # # ]: 0 : if (pl_color_primaries_is_wide_gamut(ref->primaries)) {
871 : 0 : space->primaries = PL_COLOR_PRIM_BT_709;
872 : : } else {
873 : 0 : space->primaries = ref->primaries;
874 : : }
875 : : }
876 : :
877 [ - + ]: 3294 : if (!space->transfer) {
878 [ # # # # : 0 : switch (ref->transfer) {
# # ]
879 : : case PL_COLOR_TRC_UNKNOWN:
880 : : case PL_COLOR_TRC_COUNT:
881 : 0 : pl_unreachable();
882 : 0 : case PL_COLOR_TRC_BT_1886:
883 : : case PL_COLOR_TRC_SRGB:
884 : : case PL_COLOR_TRC_GAMMA22:
885 : : // Re-use input transfer curve to avoid small adaptations
886 : 0 : space->transfer = ref->transfer;
887 : 0 : break;
888 : 0 : case PL_COLOR_TRC_PQ:
889 : : case PL_COLOR_TRC_HLG:
890 : : case PL_COLOR_TRC_V_LOG:
891 : : case PL_COLOR_TRC_S_LOG1:
892 : : case PL_COLOR_TRC_S_LOG2:
893 : : // Pick BT.1886 model because it models SDR contrast accurately,
894 : : // and we need contrast information for tone mapping
895 : 0 : space->transfer = PL_COLOR_TRC_BT_1886;
896 : 0 : break;
897 : 0 : case PL_COLOR_TRC_PRO_PHOTO:
898 : : // ProPhotoRGB and sRGB are both piecewise with linear slope
899 : 0 : space->transfer = PL_COLOR_TRC_SRGB;
900 : 0 : break;
901 : 0 : case PL_COLOR_TRC_LINEAR:
902 : : case PL_COLOR_TRC_GAMMA18:
903 : : case PL_COLOR_TRC_GAMMA20:
904 : : case PL_COLOR_TRC_GAMMA24:
905 : : case PL_COLOR_TRC_GAMMA26:
906 : : case PL_COLOR_TRC_GAMMA28:
907 : : case PL_COLOR_TRC_ST428:
908 : : // Pick pure power output curve to avoid introducing black crush
909 : 0 : space->transfer = PL_COLOR_TRC_GAMMA22;
910 : 0 : break;
911 : : }
912 : : }
913 : :
914 : : // Infer the remaining fields after making the above choices
915 : 3294 : pl_color_space_infer(space);
916 : 3294 : }
917 : :
918 : 0 : void pl_color_space_infer_ref(struct pl_color_space *space,
919 : : const struct pl_color_space *refp)
920 : : {
921 : : // Make a copy of `refp` to infer missing values first
922 : 0 : struct pl_color_space ref = *refp;
923 : 0 : infer_both_ref(space, &ref);
924 : 0 : }
925 : :
926 : 3294 : void pl_color_space_infer_map(struct pl_color_space *src,
927 : : struct pl_color_space *dst)
928 : : {
929 : 3294 : bool unknown_src_contrast = !src->hdr.min_luma;
930 : 3294 : bool unknown_dst_contrast = !dst->hdr.min_luma;
931 : :
932 : 3294 : infer_both_ref(dst, src);
933 : :
934 : : // If the src has an unspecified gamma curve with dynamic black scaling,
935 : : // default it to match the dst colorspace contrast. This does not matter in
936 : : // most cases, but ensures that BT.1886 is tuned to the appropriate black
937 : : // point by default.
938 [ + + ]: 3294 : bool dynamic_src_contrast = pl_color_space_is_black_scaled(src) ||
939 [ + + ]: 70 : src->transfer == PL_COLOR_TRC_BT_1886;
940 [ + + ]: 3294 : if (unknown_src_contrast && dynamic_src_contrast)
941 : 2415 : src->hdr.min_luma = dst->hdr.min_luma;
942 : :
943 : : // Do the same in reverse if both src and dst are SDR curves
944 : 3294 : bool src_is_sdr = !pl_color_space_is_hdr(src);
945 : 3294 : bool dst_is_sdr = !pl_color_space_is_hdr(dst);
946 [ + + + - ]: 3294 : if (unknown_dst_contrast && src_is_sdr && dst_is_sdr)
947 : 2399 : dst->hdr.min_luma = src->hdr.min_luma;
948 : :
949 : : // If the src is HLG and the output is HDR, tune the HLG peak to the output
950 [ - + - - ]: 3294 : if (src->transfer == PL_COLOR_TRC_HLG && pl_color_space_is_hdr(dst))
951 : 0 : src->hdr.max_luma = dst->hdr.max_luma;
952 : 3294 : }
953 : :
954 : : const struct pl_color_adjustment pl_color_adjustment_neutral = {
955 : : PL_COLOR_ADJUSTMENT_NEUTRAL
956 : : };
957 : :
958 : 14 : void pl_chroma_location_offset(enum pl_chroma_location loc, float *x, float *y)
959 : : {
960 : 14 : *x = *y = 0;
961 : :
962 : : // This is the majority of subsampled chroma content out there
963 [ + - ]: 14 : loc = PL_DEF(loc, PL_CHROMA_LEFT);
964 : :
965 [ + + ]: 14 : switch (loc) {
966 : 12 : case PL_CHROMA_LEFT:
967 : : case PL_CHROMA_TOP_LEFT:
968 : : case PL_CHROMA_BOTTOM_LEFT:
969 : 12 : *x = -0.5;
970 : 12 : break;
971 : : default: break;
972 : : }
973 : :
974 [ + + ]: 14 : switch (loc) {
975 : 11 : case PL_CHROMA_TOP_LEFT:
976 : : case PL_CHROMA_TOP_CENTER:
977 : 11 : *y = -0.5;
978 : 11 : break;
979 : : default: break;
980 : : }
981 : :
982 [ + + ]: 14 : switch (loc) {
983 : 1 : case PL_CHROMA_BOTTOM_LEFT:
984 : : case PL_CHROMA_BOTTOM_CENTER:
985 : 1 : *y = 0.5;
986 : 1 : break;
987 : : default: break;
988 : : }
989 : 14 : }
990 : :
991 : 10 : struct pl_cie_xy pl_white_from_temp(float temp)
992 : : {
993 [ + - + - ]: 10 : temp = PL_CLAMP(temp, 2500, 25000);
994 : :
995 : 10 : double ti = 1000.0 / temp, ti2 = ti * ti, ti3 = ti2 * ti, x;
996 [ + + ]: 10 : if (temp <= 7000) {
997 : 6 : x = -4.6070 * ti3 + 2.9678 * ti2 + 0.09911 * ti + 0.244063;
998 : : } else {
999 : 4 : x = -2.0064 * ti3 + 1.9018 * ti2 + 0.24748 * ti + 0.237040;
1000 : : }
1001 : :
1002 : 10 : return (struct pl_cie_xy) {
1003 : : .x = x,
1004 : 10 : .y = -3 * (x*x) + 2.87 * x - 0.275,
1005 : : };
1006 : : }
1007 : :
1008 [ + + ]: 2600 : bool pl_raw_primaries_equal(const struct pl_raw_primaries *a,
1009 : : const struct pl_raw_primaries *b)
1010 : : {
1011 : : return pl_cie_xy_equal(&a->red, &b->red) &&
1012 : : pl_cie_xy_equal(&a->green, &b->green) &&
1013 : 2600 : pl_cie_xy_equal(&a->blue, &b->blue) &&
1014 : : pl_cie_xy_equal(&a->white, &b->white);
1015 : : }
1016 : :
1017 : 47 : bool pl_raw_primaries_similar(const struct pl_raw_primaries *a,
1018 : : const struct pl_raw_primaries *b)
1019 : : {
1020 : 47 : float delta = fabsf(a->red.x - b->red.x) +
1021 : 47 : fabsf(a->red.y - b->red.y) +
1022 : 47 : fabsf(a->green.x - b->green.x) +
1023 : 47 : fabsf(a->green.y - b->green.y) +
1024 : 47 : fabsf(a->blue.x - b->blue.x) +
1025 : 47 : fabsf(a->blue.y - b->blue.y) +
1026 : 47 : fabsf(a->white.x - b->white.x) +
1027 : 47 : fabsf(a->white.y - b->white.y);
1028 : :
1029 : 47 : return delta < 0.001;
1030 : : }
1031 : :
1032 : 59 : void pl_raw_primaries_merge(struct pl_raw_primaries *orig,
1033 : : const struct pl_raw_primaries *update)
1034 : : {
1035 : : union {
1036 : : struct pl_raw_primaries prim;
1037 : : float raw[8];
1038 : : } *pa = (void *) orig,
1039 : : *pb = (void *) update;
1040 : :
1041 : : pl_static_assert(sizeof(*pa) == sizeof(*orig));
1042 [ + + ]: 531 : for (int i = 0; i < PL_ARRAY_SIZE(pa->raw); i++)
1043 [ + - ]: 472 : pa->raw[i] = PL_DEF(pa->raw[i], pb->raw[i]);
1044 : 59 : }
1045 : :
1046 : 8102 : const struct pl_raw_primaries *pl_raw_primaries_get(enum pl_color_primaries prim)
1047 : : {
1048 : : /*
1049 : : Values from: ITU-R Recommendations BT.470-6, BT.601-7, BT.709-5, BT.2020-0
1050 : :
1051 : : https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.470-6-199811-S!!PDF-E.pdf
1052 : : https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf
1053 : : https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-5-200204-I!!PDF-E.pdf
1054 : : https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2020-0-201208-I!!PDF-E.pdf
1055 : :
1056 : : Other colorspaces from https://en.wikipedia.org/wiki/RGB_color_space#Specifications
1057 : : */
1058 : :
1059 : : // CIE standard illuminant series
1060 : : #define CIE_D50 {0.3457, 0.3585}
1061 : : #define CIE_D65 {0.3127, 0.3290}
1062 : : #define CIE_C {0.3100, 0.3160}
1063 : : #define CIE_E {1.0/3.0, 1.0/3.0}
1064 : : #define DCI {0.3140, 0.3510}
1065 : :
1066 : : static const struct pl_raw_primaries primaries[] = {
1067 : : [PL_COLOR_PRIM_BT_470M] = {
1068 : : .red = {0.670, 0.330},
1069 : : .green = {0.210, 0.710},
1070 : : .blue = {0.140, 0.080},
1071 : : .white = CIE_C,
1072 : : },
1073 : :
1074 : : [PL_COLOR_PRIM_BT_601_525] = {
1075 : : .red = {0.630, 0.340},
1076 : : .green = {0.310, 0.595},
1077 : : .blue = {0.155, 0.070},
1078 : : .white = CIE_D65,
1079 : : },
1080 : : [PL_COLOR_PRIM_BT_601_625] = {
1081 : : .red = {0.640, 0.330},
1082 : : .green = {0.290, 0.600},
1083 : : .blue = {0.150, 0.060},
1084 : : .white = CIE_D65,
1085 : : },
1086 : : [PL_COLOR_PRIM_BT_709] = {
1087 : : .red = {0.640, 0.330},
1088 : : .green = {0.300, 0.600},
1089 : : .blue = {0.150, 0.060},
1090 : : .white = CIE_D65,
1091 : : },
1092 : : [PL_COLOR_PRIM_BT_2020] = {
1093 : : .red = {0.708, 0.292},
1094 : : .green = {0.170, 0.797},
1095 : : .blue = {0.131, 0.046},
1096 : : .white = CIE_D65,
1097 : : },
1098 : : [PL_COLOR_PRIM_APPLE] = {
1099 : : .red = {0.625, 0.340},
1100 : : .green = {0.280, 0.595},
1101 : : .blue = {0.115, 0.070},
1102 : : .white = CIE_D65,
1103 : : },
1104 : : [PL_COLOR_PRIM_ADOBE] = {
1105 : : .red = {0.640, 0.330},
1106 : : .green = {0.210, 0.710},
1107 : : .blue = {0.150, 0.060},
1108 : : .white = CIE_D65,
1109 : : },
1110 : : [PL_COLOR_PRIM_PRO_PHOTO] = {
1111 : : .red = {0.7347, 0.2653},
1112 : : .green = {0.1596, 0.8404},
1113 : : .blue = {0.0366, 0.0001},
1114 : : .white = CIE_D50,
1115 : : },
1116 : : [PL_COLOR_PRIM_CIE_1931] = {
1117 : : .red = {0.7347, 0.2653},
1118 : : .green = {0.2738, 0.7174},
1119 : : .blue = {0.1666, 0.0089},
1120 : : .white = CIE_E,
1121 : : },
1122 : : // From SMPTE RP 431-2
1123 : : [PL_COLOR_PRIM_DCI_P3] = {
1124 : : .red = {0.680, 0.320},
1125 : : .green = {0.265, 0.690},
1126 : : .blue = {0.150, 0.060},
1127 : : .white = DCI,
1128 : : },
1129 : : [PL_COLOR_PRIM_DISPLAY_P3] = {
1130 : : .red = {0.680, 0.320},
1131 : : .green = {0.265, 0.690},
1132 : : .blue = {0.150, 0.060},
1133 : : .white = CIE_D65,
1134 : : },
1135 : : // From Panasonic VARICAM reference manual
1136 : : [PL_COLOR_PRIM_V_GAMUT] = {
1137 : : .red = {0.730, 0.280},
1138 : : .green = {0.165, 0.840},
1139 : : .blue = {0.100, -0.03},
1140 : : .white = CIE_D65,
1141 : : },
1142 : : // From Sony S-Log reference manual
1143 : : [PL_COLOR_PRIM_S_GAMUT] = {
1144 : : .red = {0.730, 0.280},
1145 : : .green = {0.140, 0.855},
1146 : : .blue = {0.100, -0.05},
1147 : : .white = CIE_D65,
1148 : : },
1149 : : // From FFmpeg source code
1150 : : [PL_COLOR_PRIM_FILM_C] = {
1151 : : .red = {0.681, 0.319},
1152 : : .green = {0.243, 0.692},
1153 : : .blue = {0.145, 0.049},
1154 : : .white = CIE_C,
1155 : : },
1156 : : [PL_COLOR_PRIM_EBU_3213] = {
1157 : : .red = {0.630, 0.340},
1158 : : .green = {0.295, 0.605},
1159 : : .blue = {0.155, 0.077},
1160 : : .white = CIE_D65,
1161 : : },
1162 : : // From Wikipedia
1163 : : [PL_COLOR_PRIM_ACES_AP0] = {
1164 : : .red = {0.7347, 0.2653},
1165 : : .green = {0.0000, 1.0000},
1166 : : .blue = {0.0001, -0.0770},
1167 : : .white = {0.32168, 0.33767},
1168 : : },
1169 : : [PL_COLOR_PRIM_ACES_AP1] = {
1170 : : .red = {0.713, 0.293},
1171 : : .green = {0.165, 0.830},
1172 : : .blue = {0.128, 0.044},
1173 : : .white = {0.32168, 0.33767},
1174 : : },
1175 : : };
1176 : :
1177 : : // This is the default assumption if no colorspace information could
1178 : : // be determined, eg. for files which have no video channel.
1179 [ + + ]: 8102 : if (!prim)
1180 : : prim = PL_COLOR_PRIM_BT_709;
1181 : :
1182 [ - + ]: 7895 : pl_assert(prim < PL_ARRAY_SIZE(primaries));
1183 : 8102 : return &primaries[prim];
1184 : : }
1185 : :
1186 : : // Compute the RGB/XYZ matrix as described here:
1187 : : // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
1188 : 4292 : pl_matrix3x3 pl_get_rgb2xyz_matrix(const struct pl_raw_primaries *prim)
1189 : : {
1190 : 4292 : pl_matrix3x3 out = {{{0}}};
1191 : : float S[3], X[4], Z[4];
1192 : :
1193 : 4292 : X[0] = pl_cie_X(prim->red);
1194 : 4292 : X[1] = pl_cie_X(prim->green);
1195 : 4292 : X[2] = pl_cie_X(prim->blue);
1196 : 4292 : X[3] = pl_cie_X(prim->white);
1197 : :
1198 : 4292 : Z[0] = pl_cie_Z(prim->red);
1199 : 4292 : Z[1] = pl_cie_Z(prim->green);
1200 : 4292 : Z[2] = pl_cie_Z(prim->blue);
1201 : 4292 : Z[3] = pl_cie_Z(prim->white);
1202 : :
1203 : : // S = XYZ^-1 * W
1204 [ + + ]: 17168 : for (int i = 0; i < 3; i++) {
1205 : 12876 : out.m[0][i] = X[i];
1206 : 12876 : out.m[1][i] = 1;
1207 : 12876 : out.m[2][i] = Z[i];
1208 : : }
1209 : :
1210 : 4292 : pl_matrix3x3_invert(&out);
1211 : :
1212 [ + + ]: 17168 : for (int i = 0; i < 3; i++)
1213 : 12876 : S[i] = out.m[i][0] * X[3] + out.m[i][1] * 1 + out.m[i][2] * Z[3];
1214 : :
1215 : : // M = [Sc * XYZc]
1216 [ + + ]: 17168 : for (int i = 0; i < 3; i++) {
1217 : 12876 : out.m[0][i] = S[i] * X[i];
1218 : 12876 : out.m[1][i] = S[i] * 1;
1219 : 12876 : out.m[2][i] = S[i] * Z[i];
1220 : : }
1221 : :
1222 : 4292 : return out;
1223 : : }
1224 : :
1225 : 7 : pl_matrix3x3 pl_get_xyz2rgb_matrix(const struct pl_raw_primaries *prim)
1226 : : {
1227 : : // For simplicity, just invert the rgb2xyz matrix
1228 : 7 : pl_matrix3x3 out = pl_get_rgb2xyz_matrix(prim);
1229 : 7 : pl_matrix3x3_invert(&out);
1230 : 7 : return out;
1231 : : }
1232 : :
1233 : : // Matrix used in CAT16, a revised one-step linear transform method
1234 : : static const pl_matrix3x3 m_cat16 = {{
1235 : : { 0.401288, 0.650173, -0.051461 },
1236 : : { -0.250268, 1.204414, 0.045854 },
1237 : : { -0.002079, 0.048952, 0.953127 },
1238 : : }};
1239 : :
1240 : : // M := M * XYZd<-XYZs
1241 : 1248 : static void apply_chromatic_adaptation(struct pl_cie_xy src,
1242 : : struct pl_cie_xy dest,
1243 : : pl_matrix3x3 *mat)
1244 : : {
1245 : : // If the white points are nearly identical, this is a wasteful identity
1246 : : // operation.
1247 [ + + - + ]: 1248 : if (fabs(src.x - dest.x) < 1e-6 && fabs(src.y - dest.y) < 1e-6)
1248 : 987 : return;
1249 : :
1250 : : // Linear "von Kries" method, adapted from CIECAM16
1251 : : // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
1252 : : float C[3][2];
1253 : :
1254 [ + + ]: 1044 : for (int i = 0; i < 3; i++) {
1255 : : // source cone
1256 : 783 : C[i][0] = m_cat16.m[i][0] * pl_cie_X(src)
1257 : 783 : + m_cat16.m[i][1] * 1
1258 : 783 : + m_cat16.m[i][2] * pl_cie_Z(src);
1259 : :
1260 : : // dest cone
1261 : 783 : C[i][1] = m_cat16.m[i][0] * pl_cie_X(dest)
1262 : 783 : + m_cat16.m[i][1] * 1
1263 : 783 : + m_cat16.m[i][2] * pl_cie_Z(dest);
1264 : : }
1265 : :
1266 : : // tmp := I * [Cd/Cs] * Ma
1267 : 261 : pl_matrix3x3 tmp = {0};
1268 [ + + ]: 1044 : for (int i = 0; i < 3; i++)
1269 : 783 : tmp.m[i][i] = C[i][1] / C[i][0];
1270 : :
1271 : 261 : pl_matrix3x3_mul(&tmp, &m_cat16);
1272 : :
1273 : : // M := M * Ma^-1 * tmp
1274 : 261 : pl_matrix3x3 ma_inv = m_cat16;
1275 : 261 : pl_matrix3x3_invert(&ma_inv);
1276 : 261 : pl_matrix3x3_mul(mat, &ma_inv);
1277 : 261 : pl_matrix3x3_mul(mat, &tmp);
1278 : : }
1279 : :
1280 : 6 : pl_matrix3x3 pl_get_adaptation_matrix(struct pl_cie_xy src, struct pl_cie_xy dst)
1281 : : {
1282 : : // Use BT.709 primaries (with chosen white point) as an XYZ reference
1283 : 6 : struct pl_raw_primaries csp = *pl_raw_primaries_get(PL_COLOR_PRIM_BT_709);
1284 : 6 : csp.white = src;
1285 : :
1286 : 6 : pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(&csp);
1287 : 6 : pl_matrix3x3 xyz2rgb = rgb2xyz;
1288 : 6 : pl_matrix3x3_invert(&xyz2rgb);
1289 : :
1290 : 6 : apply_chromatic_adaptation(src, dst, &xyz2rgb);
1291 : 6 : pl_matrix3x3_mul(&xyz2rgb, &rgb2xyz);
1292 : 6 : return xyz2rgb;
1293 : : }
1294 : :
1295 : 1239 : pl_matrix3x3 pl_ipt_rgb2lms(const struct pl_raw_primaries *prim)
1296 : : {
1297 : : static const pl_matrix3x3 hpe = {{ // HPE XYZ->LMS (D65) method
1298 : : { 0.40024f, 0.70760f, -0.08081f },
1299 : : { -0.22630f, 1.16532f, 0.04570f },
1300 : : { 0.00000f, 0.00000f, 0.91822f },
1301 : : }};
1302 : :
1303 : : const float c = 0.04; // 4% crosstalk
1304 : 1239 : pl_matrix3x3 m = {{
1305 : : { 1 - 2*c, c, c },
1306 : : { c, 1 - 2*c, c },
1307 : : { c, c, 1 - 2*c },
1308 : : }};
1309 : :
1310 : 1239 : pl_matrix3x3_mul(&m, &hpe);
1311 : :
1312 : : // Apply chromatic adaptation to D65 if the input white point differs
1313 : : static const struct pl_cie_xy d65 = CIE_D65;
1314 : 1239 : apply_chromatic_adaptation(prim->white, d65, &m);
1315 : :
1316 : 1239 : const pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(prim);
1317 : 1239 : pl_matrix3x3_mul(&m, &rgb2xyz);
1318 : 1239 : return m;
1319 : : }
1320 : :
1321 : 67 : pl_matrix3x3 pl_ipt_lms2rgb(const struct pl_raw_primaries *prim)
1322 : : {
1323 : 67 : pl_matrix3x3 m = pl_ipt_rgb2lms(prim);
1324 : 67 : pl_matrix3x3_invert(&m);
1325 : 67 : return m;
1326 : : }
1327 : :
1328 : : // As standardized in Ebner & Fairchild IPT (1998)
1329 : : const pl_matrix3x3 pl_ipt_lms2ipt = {{
1330 : : { 0.4000, 0.4000, 0.2000 },
1331 : : { 4.4550, -4.8510, 0.3960 },
1332 : : { 0.8056, 0.3572, -1.1628 },
1333 : : }};
1334 : :
1335 : : // Numerically inverted from the matrix above
1336 : : const pl_matrix3x3 pl_ipt_ipt2lms = {{
1337 : : { 1.0, 0.0975689, 0.205226 },
1338 : : { 1.0, -0.1138760, 0.133217 },
1339 : : { 1.0, 0.0326151, -0.676887 },
1340 : : }};
1341 : :
1342 : : const struct pl_cone_params pl_vision_normal = {PL_CONE_NONE, 1.0};
1343 : : const struct pl_cone_params pl_vision_protanomaly = {PL_CONE_L, 0.5};
1344 : : const struct pl_cone_params pl_vision_protanopia = {PL_CONE_L, 0.0};
1345 : : const struct pl_cone_params pl_vision_deuteranomaly = {PL_CONE_M, 0.5};
1346 : : const struct pl_cone_params pl_vision_deuteranopia = {PL_CONE_M, 0.0};
1347 : : const struct pl_cone_params pl_vision_tritanomaly = {PL_CONE_S, 0.5};
1348 : : const struct pl_cone_params pl_vision_tritanopia = {PL_CONE_S, 0.0};
1349 : : const struct pl_cone_params pl_vision_monochromacy = {PL_CONE_LM, 0.0};
1350 : : const struct pl_cone_params pl_vision_achromatopsia = {PL_CONE_LMS, 0.0};
1351 : :
1352 : 20 : pl_matrix3x3 pl_get_cone_matrix(const struct pl_cone_params *params,
1353 : : const struct pl_raw_primaries *prim)
1354 : : {
1355 : : // LMS<-RGB := LMS<-XYZ * XYZ<-RGB
1356 : 20 : pl_matrix3x3 rgb2lms = m_cat16;
1357 : 20 : pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(prim);
1358 : 20 : pl_matrix3x3_mul(&rgb2lms, &rgb2xyz);
1359 : :
1360 : : // LMS versions of the two opposing primaries, plus neutral
1361 : 20 : float lms_r[3] = {1.0, 0.0, 0.0},
1362 : 20 : lms_b[3] = {0.0, 0.0, 1.0},
1363 : 20 : lms_w[3] = {1.0, 1.0, 1.0};
1364 : :
1365 : 20 : pl_matrix3x3_apply(&rgb2lms, lms_r);
1366 : 20 : pl_matrix3x3_apply(&rgb2lms, lms_b);
1367 : 20 : pl_matrix3x3_apply(&rgb2lms, lms_w);
1368 : :
1369 : 20 : float a, b, c = params->strength;
1370 : : pl_matrix3x3 distort;
1371 : :
1372 [ + + + + : 20 : switch (params->cones) {
+ + + +
- ]
1373 : 4 : case PL_CONE_NONE:
1374 : 4 : return pl_matrix3x3_identity;
1375 : :
1376 : 3 : case PL_CONE_L:
1377 : : // Solve to preserve neutral and blue
1378 : 3 : a = (lms_b[0] - lms_b[2] * lms_w[0] / lms_w[2]) /
1379 : 3 : (lms_b[1] - lms_b[2] * lms_w[1] / lms_w[2]);
1380 : 3 : b = (lms_b[0] - lms_b[1] * lms_w[0] / lms_w[1]) /
1381 : 3 : (lms_b[2] - lms_b[1] * lms_w[2] / lms_w[1]);
1382 [ - + ]: 3 : assert(fabs(a * lms_w[1] + b * lms_w[2] - lms_w[0]) < 1e-6);
1383 : :
1384 : 3 : distort = (pl_matrix3x3) {{
1385 : 3 : { c, (1.0 - c) * a, (1.0 - c) * b},
1386 : : { 0.0, 1.0, 0.0},
1387 : : { 0.0, 0.0, 1.0},
1388 : : }};
1389 : 3 : break;
1390 : :
1391 : 6 : case PL_CONE_M:
1392 : : // Solve to preserve neutral and blue
1393 : 6 : a = (lms_b[1] - lms_b[2] * lms_w[1] / lms_w[2]) /
1394 : 6 : (lms_b[0] - lms_b[2] * lms_w[0] / lms_w[2]);
1395 : 6 : b = (lms_b[1] - lms_b[0] * lms_w[1] / lms_w[0]) /
1396 : 6 : (lms_b[2] - lms_b[0] * lms_w[2] / lms_w[0]);
1397 [ - + ]: 6 : assert(fabs(a * lms_w[0] + b * lms_w[2] - lms_w[1]) < 1e-6);
1398 : :
1399 : 6 : distort = (pl_matrix3x3) {{
1400 : : { 1.0, 0.0, 0.0},
1401 : 6 : {(1.0 - c) * a, c, (1.0 - c) * b},
1402 : : { 0.0, 0.0, 1.0},
1403 : : }};
1404 : 6 : break;
1405 : :
1406 : 3 : case PL_CONE_S:
1407 : : // Solve to preserve neutral and red
1408 : 3 : a = (lms_r[2] - lms_r[1] * lms_w[2] / lms_w[1]) /
1409 : 3 : (lms_r[0] - lms_r[1] * lms_w[0] / lms_w[1]);
1410 : 3 : b = (lms_r[2] - lms_r[0] * lms_w[2] / lms_w[0]) /
1411 : 3 : (lms_r[1] - lms_r[0] * lms_w[1] / lms_w[0]);
1412 [ - + ]: 3 : assert(fabs(a * lms_w[0] + b * lms_w[1] - lms_w[2]) < 1e-6);
1413 : :
1414 : 3 : distort = (pl_matrix3x3) {{
1415 : : { 1.0, 0.0, 0.0},
1416 : : { 0.0, 1.0, 0.0},
1417 : 3 : {(1.0 - c) * a, (1.0 - c) * b, c},
1418 : : }};
1419 : 3 : break;
1420 : :
1421 : 1 : case PL_CONE_LM:
1422 : : // Solve to preserve neutral
1423 : 1 : a = lms_w[0] / lms_w[2];
1424 : 1 : b = lms_w[1] / lms_w[2];
1425 : :
1426 : 1 : distort = (pl_matrix3x3) {{
1427 : 1 : { c, 0.0, (1.0 - c) * a},
1428 : 1 : { 0.0, c, (1.0 - c) * b},
1429 : : { 0.0, 0.0, 1.0},
1430 : : }};
1431 : 1 : break;
1432 : :
1433 : 1 : case PL_CONE_MS:
1434 : : // Solve to preserve neutral
1435 : 1 : a = lms_w[1] / lms_w[0];
1436 : 1 : b = lms_w[2] / lms_w[0];
1437 : :
1438 : 1 : distort = (pl_matrix3x3) {{
1439 : : { 1.0, 0.0, 0.0},
1440 : 1 : {(1.0 - c) * a, c, 0.0},
1441 : 1 : {(1.0 - c) * b, 0.0, c},
1442 : : }};
1443 : 1 : break;
1444 : :
1445 : 1 : case PL_CONE_LS:
1446 : : // Solve to preserve neutral
1447 : 1 : a = lms_w[0] / lms_w[1];
1448 : 1 : b = lms_w[2] / lms_w[1];
1449 : :
1450 : 1 : distort = (pl_matrix3x3) {{
1451 : 1 : { c, (1.0 - c) * a, 0.0},
1452 : : { 0.0, 1.0, 0.0},
1453 : 1 : { 0.0, (1.0 - c) * b, c},
1454 : : }};
1455 : 1 : break;
1456 : :
1457 : 1 : case PL_CONE_LMS: {
1458 : : // Rod cells only, which can be modelled somewhat as a combination of
1459 : : // L and M cones. Either way, this is pushing the limits of the our
1460 : : // color model, so this is only a rough approximation.
1461 : 1 : const float w[3] = {0.3605, 0.6415, -0.002};
1462 : : assert(fabs(w[0] + w[1] + w[2] - 1.0) < 1e-6);
1463 : :
1464 [ + + ]: 4 : for (int i = 0; i < 3; i++) {
1465 [ + + ]: 12 : for (int j = 0; j < 3; j++) {
1466 : 9 : distort.m[i][j] = (1.0 - c) * w[j] * lms_w[i] / lms_w[j];
1467 [ + + ]: 9 : if (i == j)
1468 : 3 : distort.m[i][j] += c;
1469 : : }
1470 : : }
1471 : : break;
1472 : : }
1473 : :
1474 : : default:
1475 : 0 : pl_unreachable();
1476 : : }
1477 : :
1478 : : // out := RGB<-LMS * distort * LMS<-RGB
1479 : 16 : pl_matrix3x3 out = rgb2lms;
1480 : 16 : pl_matrix3x3_invert(&out);
1481 : 16 : pl_matrix3x3_mul(&out, &distort);
1482 : 16 : pl_matrix3x3_mul(&out, &rgb2lms);
1483 : :
1484 : 16 : return out;
1485 : : }
1486 : :
1487 : 3 : pl_matrix3x3 pl_get_color_mapping_matrix(const struct pl_raw_primaries *src,
1488 : : const struct pl_raw_primaries *dst,
1489 : : enum pl_rendering_intent intent)
1490 : : {
1491 : : // In saturation mapping, we don't care about accuracy and just want
1492 : : // primaries to map to primaries, making this an identity transformation.
1493 [ - + ]: 3 : if (intent == PL_INTENT_SATURATION)
1494 : 0 : return pl_matrix3x3_identity;
1495 : :
1496 : : // RGBd<-RGBs = RGBd<-XYZd * XYZd<-XYZs * XYZs<-RGBs
1497 : : // Equations from: http://www.brucelindbloom.com/index.html?Math.html
1498 : : // Note: Perceptual is treated like relative colorimetric. There's no
1499 : : // definition for perceptual other than "make it look good".
1500 : :
1501 : : // RGBd<-XYZd matrix
1502 : 3 : pl_matrix3x3 xyz2rgb_d = pl_get_xyz2rgb_matrix(dst);
1503 : :
1504 : : // Chromatic adaptation, except in absolute colorimetric intent
1505 [ + - ]: 3 : if (intent != PL_INTENT_ABSOLUTE_COLORIMETRIC)
1506 : 3 : apply_chromatic_adaptation(src->white, dst->white, &xyz2rgb_d);
1507 : :
1508 : : // XYZs<-RGBs
1509 : 3 : pl_matrix3x3 rgb2xyz_s = pl_get_rgb2xyz_matrix(src);
1510 : 3 : pl_matrix3x3_mul(&xyz2rgb_d, &rgb2xyz_s);
1511 : 3 : return xyz2rgb_d;
1512 : : }
1513 : :
1514 : : // Test the sign of 'p' relative to the line 'ab' (barycentric coordinates)
1515 : : static float test_point_line(const struct pl_cie_xy p,
1516 : : const struct pl_cie_xy a,
1517 : : const struct pl_cie_xy b)
1518 : : {
1519 : 2105 : return (p.x - b.x) * (a.y - b.y) - (a.x - b.x) * (p.y - b.y);
1520 : : }
1521 : :
1522 : : // Test if a point is entirely inside a gamut
1523 : 1919 : static float test_point_gamut(struct pl_cie_xy point,
1524 : : const struct pl_raw_primaries *prim)
1525 : : {
1526 : : float d1 = test_point_line(point, prim->red, prim->green),
1527 : : d2 = test_point_line(point, prim->green, prim->blue),
1528 : : d3 = test_point_line(point, prim->blue, prim->red);
1529 : :
1530 [ + + + + : 1919 : bool has_neg = d1 < -1e-6f || d2 < -1e-6f || d3 < -1e-6f,
+ + ]
1531 [ + + + + : 1919 : has_pos = d1 > 1e-6f || d2 > 1e-6f || d3 > 1e-6f;
- + ]
1532 : :
1533 : 1919 : return !(has_neg && has_pos);
1534 : : }
1535 : :
1536 : 100 : bool pl_primaries_superset(const struct pl_raw_primaries *a,
1537 : : const struct pl_raw_primaries *b)
1538 : : {
1539 : 138 : return test_point_gamut(b->red, a) &&
1540 [ + + + + ]: 100 : test_point_gamut(b->green, a) &&
1541 [ - + ]: 34 : test_point_gamut(b->blue, a);
1542 : : }
1543 : :
1544 : 6619 : bool pl_primaries_valid(const struct pl_raw_primaries *prim)
1545 : : {
1546 : : // Test to see if the primaries form a valid triangle (nonzero area)
1547 : 6619 : float area = (prim->blue.x - prim->green.x) * (prim->red.y - prim->green.y)
1548 : 6619 : - (prim->red.x - prim->green.x) * (prim->blue.y - prim->green.y);
1549 : :
1550 [ + + - + ]: 6619 : return fabs(area) > 1e-6 && test_point_gamut(prim->white, prim);
1551 : : }
1552 : :
1553 : : static inline float xy_dist2(struct pl_cie_xy a, struct pl_cie_xy b)
1554 : : {
1555 : 121 : const float dx = a.x - b.x, dy = a.y - b.y;
1556 : 121 : return dx * dx + dy * dy;
1557 : : }
1558 : :
1559 : 121 : bool pl_primaries_compatible(const struct pl_raw_primaries *a,
1560 : : const struct pl_raw_primaries *b)
1561 : : {
1562 : : float RR = xy_dist2(a->red, b->red), RG = xy_dist2(a->red, b->green),
1563 : : RB = xy_dist2(a->red, b->blue), GG = xy_dist2(a->green, b->green),
1564 : : GB = xy_dist2(a->green, b->blue), BB = xy_dist2(a->blue, b->blue);
1565 [ + - + - : 121 : return RR < RG && RR < RB && GG < RG && GG < GB && BB < RB && BB < GB;
+ - + - +
- - + ]
1566 : : }
1567 : :
1568 : : // returns the intersection of the two lines defined by ab and cd
1569 : 1 : static struct pl_cie_xy intersection(struct pl_cie_xy a, struct pl_cie_xy b,
1570 : : struct pl_cie_xy c, struct pl_cie_xy d)
1571 : : {
1572 : 1 : float det = (a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x);
1573 : 1 : float t = ((a.x - c.x) * (c.y - d.y) - (a.y - c.y) * (c.x - d.x)) / det;
1574 : 1 : return (struct pl_cie_xy) {
1575 [ + - ]: 1 : .x = t ? a.x + t * (b.x - a.x) : 0.0f,
1576 : 1 : .y = t ? a.y + t * (b.y - a.y) : 0.0f,
1577 : : };
1578 : : }
1579 : :
1580 : : // x, y, z specified in clockwise order, with a, b, c being the enclosing gamut
1581 : : static struct pl_cie_xy
1582 : 186 : clip_point(struct pl_cie_xy x, struct pl_cie_xy y, struct pl_cie_xy z,
1583 : : struct pl_cie_xy a, struct pl_cie_xy b, struct pl_cie_xy c)
1584 : : {
1585 : : const float d1 = test_point_line(y, a, b);
1586 : : const float d2 = test_point_line(y, b, c);
1587 [ + + + - ]: 186 : if (d1 <= 0.0f && d2 <= 0.0f) {
1588 : 168 : return y; // already inside triangle
1589 [ + - + + ]: 18 : } else if (d1 > 0.0f && d2 > 0.0f) {
1590 : 17 : return b; // target vertex fully enclosed
1591 : : } else if (d1 > 0.0f) {
1592 : 1 : return intersection(a, b, y, z);
1593 : : } else {
1594 : 0 : return intersection(x, y, b, c);
1595 : : }
1596 : : }
1597 : :
1598 : 62 : struct pl_raw_primaries pl_primaries_clip(const struct pl_raw_primaries *src,
1599 : : const struct pl_raw_primaries *dst)
1600 : : {
1601 : 62 : return (struct pl_raw_primaries) {
1602 : 62 : .red = clip_point(src->green, src->red, src->blue,
1603 : : dst->green, dst->red, dst->blue),
1604 : 62 : .green = clip_point(src->blue, src->green, src->red,
1605 : : dst->blue, dst->green, dst->red),
1606 : 62 : .blue = clip_point(src->red, src->blue, src->green,
1607 : : dst->red, dst->blue, dst->green),
1608 : : .white = src->white,
1609 : : };
1610 : : }
1611 : :
1612 : : /* Fill in the Y, U, V vectors of a yuv-to-rgb conversion matrix
1613 : : * based on the given luma weights of the R, G and B components (lr, lg, lb).
1614 : : * lr+lg+lb is assumed to equal 1.
1615 : : * This function is meant for colorspaces satisfying the following
1616 : : * conditions (which are true for common YUV colorspaces):
1617 : : * - The mapping from input [Y, U, V] to output [R, G, B] is linear.
1618 : : * - Y is the vector [1, 1, 1]. (meaning input Y component maps to 1R+1G+1B)
1619 : : * - U maps to a value with zero R and positive B ([0, x, y], y > 0;
1620 : : * i.e. blue and green only).
1621 : : * - V maps to a value with zero B and positive R ([x, y, 0], x > 0;
1622 : : * i.e. red and green only).
1623 : : * - U and V are orthogonal to the luma vector [lr, lg, lb].
1624 : : * - The magnitudes of the vectors U and V are the minimal ones for which
1625 : : * the image of the set Y=[0...1],U=[-0.5...0.5],V=[-0.5...0.5] under the
1626 : : * conversion function will cover the set R=[0...1],G=[0...1],B=[0...1]
1627 : : * (the resulting matrix can be converted for other input/output ranges
1628 : : * outside this function).
1629 : : * Under these conditions the given parameters lr, lg, lb uniquely
1630 : : * determine the mapping of Y, U, V to R, G, B.
1631 : : */
1632 : : static pl_matrix3x3 luma_coeffs(float lr, float lg, float lb)
1633 : : {
1634 : : pl_assert(fabs(lr+lg+lb - 1) < 1e-6);
1635 : 0 : return (pl_matrix3x3) {{
1636 : : {1, 0, 2 * (1-lr) },
1637 : : {1, -2 * (1-lb) * lb/lg, -2 * (1-lr) * lr/lg },
1638 : : {1, 2 * (1-lb), 0 },
1639 : : }};
1640 : : }
1641 : :
1642 : : // Applies hue and saturation controls to a YCbCr->RGB matrix
1643 : 834 : static inline void apply_hue_sat(pl_matrix3x3 *m,
1644 : : const struct pl_color_adjustment *params)
1645 : : {
1646 : : // Hue is equivalent to rotating input [U, V] subvector around the origin.
1647 : : // Saturation scales [U, V].
1648 : 834 : float huecos = params->saturation * cos(params->hue);
1649 : 834 : float huesin = params->saturation * sin(params->hue);
1650 [ + + ]: 3336 : for (int i = 0; i < 3; i++) {
1651 : 2502 : float u = m->m[i][1], v = m->m[i][2];
1652 : 2502 : m->m[i][1] = huecos * u - huesin * v;
1653 : 2502 : m->m[i][2] = huesin * u + huecos * v;
1654 : : }
1655 : 834 : }
1656 : :
1657 : 980 : pl_transform3x3 pl_color_repr_decode(struct pl_color_repr *repr,
1658 : : const struct pl_color_adjustment *params)
1659 : : {
1660 [ + + ]: 980 : params = PL_DEF(params, &pl_color_adjustment_neutral);
1661 : :
1662 : : pl_matrix3x3 m;
1663 [ + + + + : 980 : switch (repr->sys) {
+ + + + +
+ + - - ]
1664 : 800 : case PL_COLOR_SYSTEM_BT_709: m = luma_coeffs(0.2126, 0.7152, 0.0722); break;
1665 : 5 : case PL_COLOR_SYSTEM_BT_601: m = luma_coeffs(0.2990, 0.5870, 0.1140); break;
1666 : 5 : case PL_COLOR_SYSTEM_SMPTE_240M: m = luma_coeffs(0.2122, 0.7013, 0.0865); break;
1667 : 5 : case PL_COLOR_SYSTEM_BT_2020_NC: m = luma_coeffs(0.2627, 0.6780, 0.0593); break;
1668 : 4 : case PL_COLOR_SYSTEM_BT_2020_C:
1669 : : // Note: This outputs into the [-0.5,0.5] range for chroma information.
1670 : 4 : m = (pl_matrix3x3) {{
1671 : : {0, 0, 1},
1672 : : {1, 0, 0},
1673 : : {0, 1, 0},
1674 : : }};
1675 : 4 : break;
1676 : 4 : case PL_COLOR_SYSTEM_BT_2100_PQ: {
1677 : : // Reversed from the matrix in the spec, hard-coded for efficiency
1678 : : // and precision reasons. Exact values truncated from ITU-T H-series
1679 : : // Supplement 18.
1680 : : static const float lm_t = 0.008609, lm_p = 0.111029625;
1681 : 4 : m = (pl_matrix3x3) {{
1682 : : {1.0, lm_t, lm_p},
1683 : : {1.0, -lm_t, -lm_p},
1684 : : {1.0, 0.560031, -0.320627},
1685 : : }};
1686 : : break;
1687 : : }
1688 : 4 : case PL_COLOR_SYSTEM_BT_2100_HLG: {
1689 : : // Similar to BT.2100 PQ, exact values truncated from WolframAlpha
1690 : : static const float lm_t = 0.01571858011, lm_p = 0.2095810681;
1691 : 4 : m = (pl_matrix3x3) {{
1692 : : {1.0, lm_t, lm_p},
1693 : : {1.0, -lm_t, -lm_p},
1694 : : {1.0, 1.02127108, -0.605274491},
1695 : : }};
1696 : : break;
1697 : : }
1698 : 2 : case PL_COLOR_SYSTEM_DOLBYVISION:
1699 : 2 : m = repr->dovi->nonlinear;
1700 : 2 : break;
1701 : 5 : case PL_COLOR_SYSTEM_YCGCO:
1702 : 5 : m = (pl_matrix3x3) {{
1703 : : {1, -1, 1},
1704 : : {1, 1, 0},
1705 : : {1, -1, -1},
1706 : : }};
1707 : 5 : break;
1708 : 142 : case PL_COLOR_SYSTEM_UNKNOWN: // fall through
1709 : : case PL_COLOR_SYSTEM_RGB:
1710 : 142 : m = pl_matrix3x3_identity;
1711 : 142 : break;
1712 : 4 : case PL_COLOR_SYSTEM_XYZ: {
1713 : : // For lack of anything saner to do, just assume the caller wants
1714 : : // DCI-P3 primaries, which is a reasonable assumption.
1715 : 4 : const struct pl_raw_primaries *dst = pl_raw_primaries_get(PL_COLOR_PRIM_DCI_P3);
1716 : 4 : m = pl_get_xyz2rgb_matrix(dst);
1717 : 4 : break;
1718 : : }
1719 : : case PL_COLOR_SYSTEM_COUNT:
1720 : 0 : pl_unreachable();
1721 : : }
1722 : :
1723 : : // Apply hue and saturation in the correct way depending on the colorspace.
1724 [ + + ]: 980 : if (pl_color_system_is_ycbcr_like(repr->sys)) {
1725 : 834 : apply_hue_sat(&m, params);
1726 [ + - - + ]: 146 : } else if (params->saturation != 1.0 || params->hue != 0.0) {
1727 : : // Arbitrarily simulate hue shifts using the BT.709 YCbCr model
1728 : : pl_matrix3x3 yuv2rgb = luma_coeffs(0.2126, 0.7152, 0.0722);
1729 : 0 : pl_matrix3x3 rgb2yuv = yuv2rgb;
1730 : 0 : pl_matrix3x3_invert(&rgb2yuv);
1731 : 0 : apply_hue_sat(&yuv2rgb, params);
1732 : : // M := RGB<-YUV * YUV<-RGB * M
1733 : 0 : pl_matrix3x3_rmul(&rgb2yuv, &m);
1734 : 0 : pl_matrix3x3_rmul(&yuv2rgb, &m);
1735 : : }
1736 : :
1737 : : // Apply color temperature adaptation, relative to BT.709 primaries
1738 [ + + ]: 980 : if (params->temperature) {
1739 : 4 : struct pl_cie_xy src = pl_white_from_temp(6500);
1740 : 4 : struct pl_cie_xy dst = pl_white_from_temp(6500 + 3500 * params->temperature);
1741 : 4 : pl_matrix3x3 adapt = pl_get_adaptation_matrix(src, dst);
1742 : 4 : pl_matrix3x3_rmul(&adapt, &m);
1743 : : }
1744 : :
1745 : 980 : pl_transform3x3 out = { .mat = m };
1746 [ + + + + ]: 980 : int bit_depth = PL_DEF(repr->bits.sample_depth,
1747 : : PL_DEF(repr->bits.color_depth, 8));
1748 : :
1749 : : double ymax, ymin, cmax, cmid;
1750 : 980 : double scale = (1LL << bit_depth) / ((1LL << bit_depth) - 1.0);
1751 : :
1752 [ + + - ]: 980 : switch (pl_color_levels_guess(repr)) {
1753 : 50 : case PL_COLOR_LEVELS_LIMITED: {
1754 : 50 : ymax = 235 / 256. * scale;
1755 : 50 : ymin = 16 / 256. * scale;
1756 : 50 : cmax = 240 / 256. * scale;
1757 : 50 : cmid = 128 / 256. * scale;
1758 : 50 : break;
1759 : : }
1760 : 930 : case PL_COLOR_LEVELS_FULL:
1761 : : // Note: For full-range YUV, there are multiple, subtly inconsistent
1762 : : // standards. So just pick the sanest implementation, which is to
1763 : : // assume MAX_INT == 1.0.
1764 : : ymax = 1.0;
1765 : : ymin = 0.0;
1766 : : cmax = 1.0;
1767 : 930 : cmid = 128 / 256. * scale; // *not* exactly 0.5
1768 : 930 : break;
1769 : : default:
1770 : 0 : pl_unreachable();
1771 : : }
1772 : :
1773 : 980 : double ymul = 1.0 / (ymax - ymin);
1774 : 980 : double cmul = 0.5 / (cmax - cmid);
1775 : :
1776 : 980 : double mul[3] = { ymul, ymul, ymul };
1777 : 980 : double black[3] = { ymin, ymin, ymin };
1778 : :
1779 : : #ifdef PL_HAVE_DOVI
1780 [ + + ]: 980 : if (repr->sys == PL_COLOR_SYSTEM_DOLBYVISION) {
1781 : : // The RPU matrix already includes levels normalization, but in this
1782 : : // case we also have to respect the signalled color offsets
1783 [ + + ]: 8 : for (int i = 0; i < 3; i++) {
1784 : 6 : mul[i] = 1.0;
1785 : 6 : black[i] = repr->dovi->nonlinear_offset[i] * scale;
1786 : : }
1787 : : } else
1788 : : #endif
1789 [ + + ]: 978 : if (pl_color_system_is_ycbcr_like(repr->sys)) {
1790 : 832 : mul[1] = mul[2] = cmul;
1791 : 832 : black[1] = black[2] = cmid;
1792 : : }
1793 : :
1794 : : // Contrast scales the output value range (gain)
1795 : : // Brightness scales the constant output bias (black lift/boost)
1796 [ + + ]: 3920 : for (int i = 0; i < 3; i++) {
1797 : 2940 : mul[i] *= params->contrast;
1798 : 2940 : out.c[i] += params->brightness;
1799 : : }
1800 : :
1801 : : // Multiply in the texture multiplier and adjust `c` so that black[j] keeps
1802 : : // on mapping to RGB=0 (black to black)
1803 [ + + ]: 3920 : for (int i = 0; i < 3; i++) {
1804 [ + + ]: 11760 : for (int j = 0; j < 3; j++) {
1805 : 8820 : out.mat.m[i][j] *= mul[j];
1806 : 8820 : out.c[i] -= out.mat.m[i][j] * black[j];
1807 : : }
1808 : : }
1809 : :
1810 : : // Finally, multiply in the scaling factor required to get the color up to
1811 : : // the correct representation.
1812 : 980 : pl_matrix3x3_scale(&out.mat, pl_color_repr_normalize(repr));
1813 : :
1814 : : // Update the metadata to reflect the change.
1815 : 980 : repr->sys = PL_COLOR_SYSTEM_RGB;
1816 : 980 : repr->levels = PL_COLOR_LEVELS_FULL;
1817 : :
1818 : 980 : return out;
1819 : : }
1820 : :
1821 : 700 : bool pl_icc_profile_equal(const struct pl_icc_profile *p1,
1822 : : const struct pl_icc_profile *p2)
1823 : : {
1824 [ + - ]: 700 : if (p1->len != p2->len)
1825 : : return false;
1826 : :
1827 : : // Ignore signatures on length-0 profiles, as a special case
1828 [ - + - - ]: 1400 : return !p1->len || p1->signature == p2->signature;
1829 : : }
1830 : :
1831 : 0 : void pl_icc_profile_compute_signature(struct pl_icc_profile *profile)
1832 : : {
1833 [ # # ]: 0 : if (!profile->len)
1834 : 0 : profile->signature = 0;
1835 : :
1836 : : // In theory, we could get this value from the profile header itself if
1837 : : // lcms is available, but I'm not sure if it's even worth the trouble. Just
1838 : : // hard-code this to a pl_mem_hash(), which is decently fast anyway.
1839 : 0 : profile->signature = pl_mem_hash(profile->data, profile->len);
1840 : 0 : }
|