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
pl_color_system_is_ycbcr_like(enum pl_color_system sys)22 bool pl_color_system_is_ycbcr_like(enum pl_color_system sys)
23 {
24 switch (sys) {
25 case PL_COLOR_SYSTEM_UNKNOWN:
26 case PL_COLOR_SYSTEM_RGB:
27 case PL_COLOR_SYSTEM_XYZ:
28 return false;
29 case PL_COLOR_SYSTEM_BT_601:
30 case PL_COLOR_SYSTEM_BT_709:
31 case PL_COLOR_SYSTEM_SMPTE_240M:
32 case PL_COLOR_SYSTEM_BT_2020_NC:
33 case PL_COLOR_SYSTEM_BT_2020_C:
34 case PL_COLOR_SYSTEM_BT_2100_PQ:
35 case PL_COLOR_SYSTEM_BT_2100_HLG:
36 case PL_COLOR_SYSTEM_YCGCO:
37 return true;
38 case PL_COLOR_SYSTEM_COUNT: break;
39 };
40
41 pl_unreachable();
42 }
43
pl_color_system_is_linear(enum pl_color_system sys)44 bool pl_color_system_is_linear(enum pl_color_system sys)
45 {
46 switch (sys) {
47 case PL_COLOR_SYSTEM_UNKNOWN:
48 case PL_COLOR_SYSTEM_RGB:
49 case PL_COLOR_SYSTEM_BT_601:
50 case PL_COLOR_SYSTEM_BT_709:
51 case PL_COLOR_SYSTEM_SMPTE_240M:
52 case PL_COLOR_SYSTEM_BT_2020_NC:
53 case PL_COLOR_SYSTEM_YCGCO:
54 return true;
55 case PL_COLOR_SYSTEM_BT_2020_C:
56 case PL_COLOR_SYSTEM_BT_2100_PQ:
57 case PL_COLOR_SYSTEM_BT_2100_HLG:
58 case PL_COLOR_SYSTEM_XYZ:
59 return false;
60 case PL_COLOR_SYSTEM_COUNT: break;
61 };
62
63 pl_unreachable();
64 }
65
pl_color_system_guess_ycbcr(int width,int height)66 enum pl_color_system pl_color_system_guess_ycbcr(int width, int height)
67 {
68 if (width >= 1280 || height > 576) {
69 // Typical HD content
70 return PL_COLOR_SYSTEM_BT_709;
71 } else {
72 // Typical SD content
73 return PL_COLOR_SYSTEM_BT_601;
74 }
75 }
76
pl_bit_encoding_equal(const struct pl_bit_encoding * b1,const struct pl_bit_encoding * b2)77 bool pl_bit_encoding_equal(const struct pl_bit_encoding *b1,
78 const struct pl_bit_encoding *b2)
79 {
80 return b1->sample_depth == b2->sample_depth &&
81 b1->color_depth == b2->color_depth &&
82 b1->bit_shift == b2->bit_shift;
83 }
84
85 const struct pl_color_repr pl_color_repr_unknown = {0};
86
87 const struct pl_color_repr pl_color_repr_rgb = {
88 .sys = PL_COLOR_SYSTEM_RGB,
89 .levels = PL_COLOR_LEVELS_FULL,
90 };
91
92 const struct pl_color_repr pl_color_repr_sdtv = {
93 .sys = PL_COLOR_SYSTEM_BT_601,
94 .levels = PL_COLOR_LEVELS_LIMITED,
95 };
96
97 const struct pl_color_repr pl_color_repr_hdtv = {
98 .sys = PL_COLOR_SYSTEM_BT_709,
99 .levels = PL_COLOR_LEVELS_LIMITED,
100 };
101
102 const struct pl_color_repr pl_color_repr_uhdtv = {
103 .sys = PL_COLOR_SYSTEM_BT_2020_NC,
104 .levels = PL_COLOR_LEVELS_LIMITED,
105 };
106
107 const struct pl_color_repr pl_color_repr_jpeg = {
108 .sys = PL_COLOR_SYSTEM_BT_601,
109 .levels = PL_COLOR_LEVELS_FULL,
110 };
111
pl_color_repr_equal(const struct pl_color_repr * c1,const struct pl_color_repr * c2)112 bool pl_color_repr_equal(const struct pl_color_repr *c1,
113 const struct pl_color_repr *c2)
114 {
115 return c1->sys == c2->sys &&
116 c1->levels == c2->levels &&
117 c1->alpha == c2->alpha &&
118 pl_bit_encoding_equal(&c1->bits, &c2->bits);
119 }
120
pl_bit_encoding_merge(const struct pl_bit_encoding * orig,const struct pl_bit_encoding * new)121 static struct pl_bit_encoding pl_bit_encoding_merge(const struct pl_bit_encoding *orig,
122 const struct pl_bit_encoding *new)
123 {
124 return (struct pl_bit_encoding) {
125 .sample_depth = PL_DEF(orig->sample_depth, new->sample_depth),
126 .color_depth = PL_DEF(orig->color_depth, new->color_depth),
127 .bit_shift = PL_DEF(orig->bit_shift, new->bit_shift),
128 };
129 }
130
pl_color_repr_merge(struct pl_color_repr * orig,const struct pl_color_repr * new)131 void pl_color_repr_merge(struct pl_color_repr *orig, const struct pl_color_repr *new)
132 {
133 *orig = (struct pl_color_repr) {
134 .sys = PL_DEF(orig->sys, new->sys),
135 .levels = PL_DEF(orig->levels, new->levels),
136 .alpha = PL_DEF(orig->alpha, new->alpha),
137 .bits = pl_bit_encoding_merge(&orig->bits, &new->bits),
138 };
139 }
140
pl_color_levels_guess(const struct pl_color_repr * repr)141 enum pl_color_levels pl_color_levels_guess(const struct pl_color_repr *repr)
142 {
143 if (repr->levels)
144 return repr->levels;
145
146 return pl_color_system_is_ycbcr_like(repr->sys)
147 ? PL_COLOR_LEVELS_LIMITED
148 : PL_COLOR_LEVELS_FULL;
149 }
150
pl_color_repr_normalize(struct pl_color_repr * repr)151 float pl_color_repr_normalize(struct pl_color_repr *repr)
152 {
153 float scale = 1.0;
154 struct pl_bit_encoding *bits = &repr->bits;
155
156 if (bits->bit_shift) {
157 scale /= (1LL << bits->bit_shift);
158 bits->bit_shift = 0;
159 }
160
161 // If one of these is set but not the other, use the set one
162 int tex_bits = PL_DEF(bits->sample_depth, 8);
163 int col_bits = PL_DEF(bits->color_depth, tex_bits);
164 tex_bits = PL_DEF(tex_bits, col_bits);
165
166 if (pl_color_levels_guess(repr) == PL_COLOR_LEVELS_LIMITED) {
167 // Limit range is always shifted directly
168 scale *= (float) (1LL << tex_bits) / (1LL << col_bits);
169 } else {
170 // Full range always uses the full range available
171 scale *= ((1LL << tex_bits) - 1.) / ((1LL << col_bits) - 1.);
172 }
173
174 bits->sample_depth = bits->color_depth;
175 return scale;
176 }
177
pl_color_primaries_is_wide_gamut(enum pl_color_primaries prim)178 bool pl_color_primaries_is_wide_gamut(enum pl_color_primaries prim)
179 {
180 switch (prim) {
181 case PL_COLOR_PRIM_UNKNOWN:
182 case PL_COLOR_PRIM_BT_601_525:
183 case PL_COLOR_PRIM_BT_601_625:
184 case PL_COLOR_PRIM_BT_709:
185 case PL_COLOR_PRIM_BT_470M:
186 case PL_COLOR_PRIM_EBU_3213:
187 return false;
188 case PL_COLOR_PRIM_BT_2020:
189 case PL_COLOR_PRIM_APPLE:
190 case PL_COLOR_PRIM_ADOBE:
191 case PL_COLOR_PRIM_PRO_PHOTO:
192 case PL_COLOR_PRIM_CIE_1931:
193 case PL_COLOR_PRIM_DCI_P3:
194 case PL_COLOR_PRIM_DISPLAY_P3:
195 case PL_COLOR_PRIM_V_GAMUT:
196 case PL_COLOR_PRIM_S_GAMUT:
197 case PL_COLOR_PRIM_FILM_C:
198 return true;
199 case PL_COLOR_PRIM_COUNT: break;
200 }
201
202 pl_unreachable();
203 }
204
pl_color_primaries_guess(int width,int height)205 enum pl_color_primaries pl_color_primaries_guess(int width, int height)
206 {
207 // HD content
208 if (width >= 1280 || height > 576)
209 return PL_COLOR_PRIM_BT_709;
210
211 switch (height) {
212 case 576: // Typical PAL content, including anamorphic/squared
213 return PL_COLOR_PRIM_BT_601_625;
214
215 case 480: // Typical NTSC content, including squared
216 case 486: // NTSC Pro or anamorphic NTSC
217 return PL_COLOR_PRIM_BT_601_525;
218
219 default: // No good metric, just pick BT.709 to minimize damage
220 return PL_COLOR_PRIM_BT_709;
221 }
222 }
223
pl_color_transfer_nominal_peak(enum pl_color_transfer trc)224 float pl_color_transfer_nominal_peak(enum pl_color_transfer trc)
225 {
226 switch (trc) {
227 case PL_COLOR_TRC_UNKNOWN:
228 case PL_COLOR_TRC_BT_1886:
229 case PL_COLOR_TRC_SRGB:
230 case PL_COLOR_TRC_LINEAR:
231 case PL_COLOR_TRC_GAMMA18:
232 case PL_COLOR_TRC_GAMMA20:
233 case PL_COLOR_TRC_GAMMA22:
234 case PL_COLOR_TRC_GAMMA24:
235 case PL_COLOR_TRC_GAMMA26:
236 case PL_COLOR_TRC_GAMMA28:
237 case PL_COLOR_TRC_PRO_PHOTO:
238 return 1.0;
239 case PL_COLOR_TRC_PQ: return 10000.0 / PL_COLOR_SDR_WHITE;
240 case PL_COLOR_TRC_HLG: return 12.0 / PL_COLOR_SDR_WHITE_HLG;
241 case PL_COLOR_TRC_V_LOG: return 46.0855;
242 case PL_COLOR_TRC_S_LOG1: return 6.52;
243 case PL_COLOR_TRC_S_LOG2: return 9.212;
244 case PL_COLOR_TRC_COUNT: break;
245 }
246
247 pl_unreachable();
248 }
249
pl_color_light_is_scene_referred(enum pl_color_light light)250 bool pl_color_light_is_scene_referred(enum pl_color_light light)
251 {
252 switch (light) {
253 case PL_COLOR_LIGHT_UNKNOWN:
254 case PL_COLOR_LIGHT_DISPLAY:
255 return false;
256 case PL_COLOR_LIGHT_SCENE_HLG:
257 case PL_COLOR_LIGHT_SCENE_709_1886:
258 case PL_COLOR_LIGHT_SCENE_1_2:
259 return true;
260 case PL_COLOR_LIGHT_COUNT: break;
261 }
262
263 pl_unreachable();
264 }
265
266 const struct pl_color_space pl_color_space_unknown = {0};
267
268 const struct pl_color_space pl_color_space_srgb = {
269 .primaries = PL_COLOR_PRIM_BT_709,
270 .transfer = PL_COLOR_TRC_SRGB,
271 .light = PL_COLOR_LIGHT_DISPLAY,
272 };
273
274 const struct pl_color_space pl_color_space_bt709 = {
275 .primaries = PL_COLOR_PRIM_BT_709,
276 .transfer = PL_COLOR_TRC_BT_1886,
277 .light = PL_COLOR_LIGHT_DISPLAY,
278 };
279
280 const struct pl_color_space pl_color_space_hdr10 = {
281 .primaries = PL_COLOR_PRIM_BT_2020,
282 .transfer = PL_COLOR_TRC_PQ,
283 .light = PL_COLOR_LIGHT_DISPLAY,
284 };
285
286 const struct pl_color_space pl_color_space_bt2020_hlg = {
287 .primaries = PL_COLOR_PRIM_BT_2020,
288 .transfer = PL_COLOR_TRC_HLG,
289 .light = PL_COLOR_LIGHT_SCENE_HLG,
290 };
291
292 const struct pl_color_space pl_color_space_monitor = {
293 .primaries = PL_COLOR_PRIM_BT_709, // sRGB primaries
294 .transfer = PL_COLOR_TRC_UNKNOWN, // unknown SDR response
295 .light = PL_COLOR_LIGHT_DISPLAY,
296 };
297
pl_color_space_is_hdr(struct pl_color_space csp)298 bool pl_color_space_is_hdr(struct pl_color_space csp)
299 {
300 float peak = pl_color_transfer_nominal_peak(csp.transfer);
301 peak *= PL_DEF(csp.sig_scale, 1.0);
302
303 return peak > 1.0;
304 }
305
pl_color_space_is_black_scaled(struct pl_color_space csp)306 bool pl_color_space_is_black_scaled(struct pl_color_space csp)
307 {
308 switch (csp.transfer) {
309 case PL_COLOR_TRC_UNKNOWN:
310 case PL_COLOR_TRC_SRGB:
311 case PL_COLOR_TRC_LINEAR:
312 case PL_COLOR_TRC_GAMMA18:
313 case PL_COLOR_TRC_GAMMA20:
314 case PL_COLOR_TRC_GAMMA22:
315 case PL_COLOR_TRC_GAMMA24:
316 case PL_COLOR_TRC_GAMMA26:
317 case PL_COLOR_TRC_GAMMA28:
318 case PL_COLOR_TRC_PRO_PHOTO:
319 case PL_COLOR_TRC_HLG:
320 return true;
321
322 case PL_COLOR_TRC_BT_1886:
323 case PL_COLOR_TRC_PQ:
324 case PL_COLOR_TRC_V_LOG:
325 case PL_COLOR_TRC_S_LOG1:
326 case PL_COLOR_TRC_S_LOG2:
327 return false;
328
329 case PL_COLOR_TRC_COUNT: break;
330 }
331
332 pl_unreachable();
333 }
334
pl_color_space_merge(struct pl_color_space * orig,const struct pl_color_space * new)335 void pl_color_space_merge(struct pl_color_space *orig,
336 const struct pl_color_space *new)
337 {
338 if (!orig->primaries)
339 orig->primaries = new->primaries;
340 if (!orig->transfer)
341 orig->transfer = new->transfer;
342 if (!orig->light)
343 orig->light = new->light;
344 if (!orig->sig_peak)
345 orig->sig_peak = new->sig_peak;
346 if (!orig->sig_avg)
347 orig->sig_avg = new->sig_avg;
348 if (!orig->sig_scale)
349 orig->sig_scale = new->sig_scale;
350 }
351
pl_color_space_equal(const struct pl_color_space * c1,const struct pl_color_space * c2)352 bool pl_color_space_equal(const struct pl_color_space *c1,
353 const struct pl_color_space *c2)
354 {
355 return c1->primaries == c2->primaries &&
356 c1->transfer == c2->transfer &&
357 c1->light == c2->light &&
358 c1->sig_peak == c2->sig_peak &&
359 c1->sig_avg == c2->sig_avg &&
360 c1->sig_scale == c2->sig_scale;
361 }
362
363 // Average light level for SDR signals. This is equal to a signal level of 0.5
364 // under a typical presentation gamma of about 2.0.
365 static const float sdr_avg = 0.25;
366
pl_color_space_infer(struct pl_color_space * space)367 void pl_color_space_infer(struct pl_color_space *space)
368 {
369 if (!space->primaries)
370 space->primaries = PL_COLOR_PRIM_BT_709;
371 if (!space->transfer)
372 space->transfer = PL_COLOR_TRC_BT_1886;
373 if (!space->light) {
374 space->light = (space->transfer == PL_COLOR_TRC_HLG)
375 ? PL_COLOR_LIGHT_SCENE_HLG
376 : PL_COLOR_LIGHT_DISPLAY;
377 }
378 float nom_peak = pl_color_transfer_nominal_peak(space->transfer);
379 space->sig_peak = PL_CLAMP(space->sig_peak, 0.0, nom_peak);
380 if (!space->sig_peak) {
381 space->sig_peak = nom_peak;
382
383 // Exception: For HLG content, we want to infer a value of 1000 cd/m²
384 // (corresponding to a peak of 10.0) instead of the true nominal peak
385 // of 12.0. A peak of 1000 is considered the "reference" HLG display.
386 if (space->transfer == PL_COLOR_TRC_HLG)
387 space->sig_peak = 10.0 / PL_COLOR_SDR_WHITE_HLG;
388 }
389
390 if (!space->sig_scale)
391 space->sig_scale = 1.0;
392
393 // In theory, for HDR signals, this is typically no longer true - but
394 // without adequate metadata there's not much else we can assume
395 if (!space->sig_avg)
396 space->sig_avg = sdr_avg / space->sig_scale;
397
398 // First infer this to good values, and then strip it for color spaces for
399 // which it doesn't make any sense
400 if (!space->sig_floor) {
401 if (pl_color_transfer_is_hdr(space->transfer)) {
402 space->sig_floor = 0.0050 / PL_COLOR_SDR_WHITE; // Typical HDR black
403 } else {
404 space->sig_floor = space->sig_peak / 1000.0; // Typical SDR contrast
405 }
406 }
407
408 // Preserve metadata for PL_COLOR_TRC_LINEAR in particular because it's
409 // used heavily as an intermediate color space.
410 if (pl_color_space_is_black_scaled(*space) &&
411 space->transfer != PL_COLOR_TRC_LINEAR)
412 {
413 space->sig_floor = 0.0;
414 }
415 }
416
pl_color_space_infer_ref(struct pl_color_space * space,const struct pl_color_space * refp)417 void pl_color_space_infer_ref(struct pl_color_space *space,
418 const struct pl_color_space *refp)
419 {
420 struct pl_color_space ref = *refp;
421 pl_color_space_infer(&ref);
422
423 if (!space->primaries) {
424 if (pl_color_primaries_is_wide_gamut(ref.primaries)) {
425 space->primaries = PL_COLOR_PRIM_BT_709;
426 } else {
427 space->primaries = ref.primaries;
428 }
429 }
430
431 if (!space->transfer) {
432 if (pl_color_transfer_is_hdr(ref.transfer)) {
433 space->transfer = PL_COLOR_TRC_BT_1886;
434 } else if (ref.transfer == PL_COLOR_TRC_LINEAR) {
435 space->transfer = PL_COLOR_TRC_GAMMA22;
436 } else {
437 space->transfer = ref.transfer;
438 }
439 }
440
441 // Defaults the sig_avg based on the ref, unless only the ref is HDR
442 if (!space->sig_avg) {
443 bool csp_hdr = pl_color_space_is_hdr(*space);
444 bool ref_hdr = pl_color_space_is_hdr(ref);
445 if (!(ref_hdr && !csp_hdr))
446 space->sig_avg = ref.sig_avg;
447 }
448
449 // Infer the remaining fields after making the above choices
450 pl_color_space_infer(space);
451 }
452
453 const struct pl_color_adjustment pl_color_adjustment_neutral = {
454 .brightness = 0.0,
455 .contrast = 1.0,
456 .saturation = 1.0,
457 .hue = 0.0,
458 .gamma = 1.0,
459 .temperature = 0.0,
460 };
461
pl_chroma_location_offset(enum pl_chroma_location loc,float * x,float * y)462 void pl_chroma_location_offset(enum pl_chroma_location loc, float *x, float *y)
463 {
464 *x = *y = 0;
465
466 // This is the majority of subsampled chroma content out there
467 loc = PL_DEF(loc, PL_CHROMA_LEFT);
468
469 switch (loc) {
470 case PL_CHROMA_LEFT:
471 case PL_CHROMA_TOP_LEFT:
472 case PL_CHROMA_BOTTOM_LEFT:
473 *x = -0.5;
474 break;
475 default: break;
476 }
477
478 switch (loc) {
479 case PL_CHROMA_TOP_LEFT:
480 case PL_CHROMA_TOP_CENTER:
481 *y = -0.5;
482 break;
483 default: break;
484 }
485
486 switch (loc) {
487 case PL_CHROMA_BOTTOM_LEFT:
488 case PL_CHROMA_BOTTOM_CENTER:
489 *y = 0.5;
490 break;
491 default: break;
492 }
493 }
494
pl_white_from_temp(float temp)495 struct pl_cie_xy pl_white_from_temp(float temp)
496 {
497 temp = PL_CLAMP(temp, 2500, 25000);
498
499 double ti = 1000.0 / temp, ti2 = ti * ti, ti3 = ti2 * ti, x;
500 if (temp <= 7000) {
501 x = -4.6070 * ti3 + 2.9678 * ti2 + 0.09911 * ti + 0.244063;
502 } else {
503 x = -2.0064 * ti3 + 1.9018 * ti2 + 0.24748 * ti + 0.237040;
504 }
505
506 return (struct pl_cie_xy) {
507 .x = x,
508 .y = -3 * (x*x) + 2.87 * x - 0.275,
509 };
510 }
511
512
pl_raw_primaries_equal(const struct pl_raw_primaries * a,const struct pl_raw_primaries * b)513 bool pl_raw_primaries_equal(const struct pl_raw_primaries *a,
514 const struct pl_raw_primaries *b)
515 {
516 return pl_cie_xy_equal(&a->red, &b->red) &&
517 pl_cie_xy_equal(&a->green, &b->green) &&
518 pl_cie_xy_equal(&a->blue, &b->blue) &&
519 pl_cie_xy_equal(&a->white, &b->white);
520 }
521
pl_raw_primaries_get(enum pl_color_primaries prim)522 const struct pl_raw_primaries *pl_raw_primaries_get(enum pl_color_primaries prim)
523 {
524 /*
525 Values from: ITU-R Recommendations BT.470-6, BT.601-7, BT.709-5, BT.2020-0
526
527 https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.470-6-199811-S!!PDF-E.pdf
528 https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf
529 https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-5-200204-I!!PDF-E.pdf
530 https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2020-0-201208-I!!PDF-E.pdf
531
532 Other colorspaces from https://en.wikipedia.org/wiki/RGB_color_space#Specifications
533 */
534
535 // CIE standard illuminant series
536 #define CIE_D50 {0.34577, 0.35850}
537 #define CIE_D65 {0.31271, 0.32902}
538 #define CIE_DCI {0.31400, 0.35100}
539 #define CIE_C {0.31006, 0.31616}
540 #define CIE_E {1.0/3.0, 1.0/3.0}
541
542 static const struct pl_raw_primaries primaries[] = {
543 [PL_COLOR_PRIM_BT_470M] = {
544 .red = {0.670, 0.330},
545 .green = {0.210, 0.710},
546 .blue = {0.140, 0.080},
547 .white = CIE_C,
548 },
549
550 [PL_COLOR_PRIM_BT_601_525] = {
551 .red = {0.630, 0.340},
552 .green = {0.310, 0.595},
553 .blue = {0.155, 0.070},
554 .white = CIE_D65,
555 },
556 [PL_COLOR_PRIM_BT_601_625] = {
557 .red = {0.640, 0.330},
558 .green = {0.290, 0.600},
559 .blue = {0.150, 0.060},
560 .white = CIE_D65,
561 },
562 [PL_COLOR_PRIM_BT_709] = {
563 .red = {0.640, 0.330},
564 .green = {0.300, 0.600},
565 .blue = {0.150, 0.060},
566 .white = CIE_D65,
567 },
568 [PL_COLOR_PRIM_BT_2020] = {
569 .red = {0.708, 0.292},
570 .green = {0.170, 0.797},
571 .blue = {0.131, 0.046},
572 .white = CIE_D65,
573 },
574 [PL_COLOR_PRIM_APPLE] = {
575 .red = {0.625, 0.340},
576 .green = {0.280, 0.595},
577 .blue = {0.115, 0.070},
578 .white = CIE_D65,
579 },
580 [PL_COLOR_PRIM_ADOBE] = {
581 .red = {0.640, 0.330},
582 .green = {0.210, 0.710},
583 .blue = {0.150, 0.060},
584 .white = CIE_D65,
585 },
586 [PL_COLOR_PRIM_PRO_PHOTO] = {
587 .red = {0.7347, 0.2653},
588 .green = {0.1596, 0.8404},
589 .blue = {0.0366, 0.0001},
590 .white = CIE_D50,
591 },
592 [PL_COLOR_PRIM_CIE_1931] = {
593 .red = {0.7347, 0.2653},
594 .green = {0.2738, 0.7174},
595 .blue = {0.1666, 0.0089},
596 .white = CIE_E,
597 },
598 // From SMPTE RP 431-2
599 [PL_COLOR_PRIM_DCI_P3] = {
600 .red = {0.680, 0.320},
601 .green = {0.265, 0.690},
602 .blue = {0.150, 0.060},
603 .white = CIE_DCI,
604 },
605 [PL_COLOR_PRIM_DISPLAY_P3] = {
606 .red = {0.680, 0.320},
607 .green = {0.265, 0.690},
608 .blue = {0.150, 0.060},
609 .white = CIE_D65,
610 },
611 // From Panasonic VARICAM reference manual
612 [PL_COLOR_PRIM_V_GAMUT] = {
613 .red = {0.730, 0.280},
614 .green = {0.165, 0.840},
615 .blue = {0.100, -0.03},
616 .white = CIE_D65,
617 },
618 // From Sony S-Log reference manual
619 [PL_COLOR_PRIM_S_GAMUT] = {
620 .red = {0.730, 0.280},
621 .green = {0.140, 0.855},
622 .blue = {0.100, -0.05},
623 .white = CIE_D65,
624 },
625 // From FFmpeg source code
626 [PL_COLOR_PRIM_FILM_C] = {
627 .red = {0.681, 0.319},
628 .green = {0.243, 0.692},
629 .blue = {0.145, 0.049},
630 .white = CIE_C,
631 },
632 [PL_COLOR_PRIM_EBU_3213] = {
633 .red = {0.630, 0.340},
634 .green = {0.295, 0.605},
635 .blue = {0.155, 0.077},
636 .white = CIE_D65,
637 },
638 };
639
640 // This is the default assumption if no colorspace information could
641 // be determined, eg. for files which have no video channel.
642 if (!prim)
643 prim = PL_COLOR_PRIM_BT_709;
644
645 pl_assert(prim < PL_ARRAY_SIZE(primaries));
646 return &primaries[prim];
647 }
648
649 // Compute the RGB/XYZ matrix as described here:
650 // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
pl_get_rgb2xyz_matrix(const struct pl_raw_primaries * prim)651 struct pl_matrix3x3 pl_get_rgb2xyz_matrix(const struct pl_raw_primaries *prim)
652 {
653 struct pl_matrix3x3 out = {{{0}}};
654 float S[3], X[4], Z[4];
655
656 // Convert from CIE xyY to XYZ. Note that Y=1 holds true for all primaries
657 X[0] = prim->red.x / prim->red.y;
658 X[1] = prim->green.x / prim->green.y;
659 X[2] = prim->blue.x / prim->blue.y;
660 X[3] = prim->white.x / prim->white.y;
661
662 Z[0] = (1 - prim->red.x - prim->red.y) / prim->red.y;
663 Z[1] = (1 - prim->green.x - prim->green.y) / prim->green.y;
664 Z[2] = (1 - prim->blue.x - prim->blue.y) / prim->blue.y;
665 Z[3] = (1 - prim->white.x - prim->white.y) / prim->white.y;
666
667 // S = XYZ^-1 * W
668 for (int i = 0; i < 3; i++) {
669 out.m[0][i] = X[i];
670 out.m[1][i] = 1;
671 out.m[2][i] = Z[i];
672 }
673
674 pl_matrix3x3_invert(&out);
675
676 for (int i = 0; i < 3; i++)
677 S[i] = out.m[i][0] * X[3] + out.m[i][1] * 1 + out.m[i][2] * Z[3];
678
679 // M = [Sc * XYZc]
680 for (int i = 0; i < 3; i++) {
681 out.m[0][i] = S[i] * X[i];
682 out.m[1][i] = S[i] * 1;
683 out.m[2][i] = S[i] * Z[i];
684 }
685
686 return out;
687 }
688
pl_get_xyz2rgb_matrix(const struct pl_raw_primaries * prim)689 struct pl_matrix3x3 pl_get_xyz2rgb_matrix(const struct pl_raw_primaries *prim)
690 {
691 // For simplicity, just invert the rgb2xyz matrix
692 struct pl_matrix3x3 out = pl_get_rgb2xyz_matrix(prim);
693 pl_matrix3x3_invert(&out);
694 return out;
695 }
696
697 // LMS<-XYZ revised matrix from CIECAM97, based on a linear transform and
698 // normalized for equal energy on monochrome inputs
699 static const struct pl_matrix3x3 m_cat97 = {{
700 { 0.8562, 0.3372, -0.1934 },
701 { -0.8360, 1.8327, 0.0033 },
702 { 0.0357, -0.0469, 1.0112 },
703 }};
704
705 // M := M * XYZd<-XYZs
apply_chromatic_adaptation(struct pl_cie_xy src,struct pl_cie_xy dest,struct pl_matrix3x3 * mat)706 static void apply_chromatic_adaptation(struct pl_cie_xy src,
707 struct pl_cie_xy dest,
708 struct pl_matrix3x3 *mat)
709 {
710 // If the white points are nearly identical, this is a wasteful identity
711 // operation.
712 if (fabs(src.x - dest.x) < 1e-6 && fabs(src.y - dest.y) < 1e-6)
713 return;
714
715 // XYZd<-XYZs = Ma^-1 * (I*[Cd/Cs]) * Ma
716 // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
717 // For Ma, we use the CIECAM97 revised (linear) matrix
718 float C[3][2];
719
720 for (int i = 0; i < 3; i++) {
721 // source cone
722 C[i][0] = m_cat97.m[i][0] * pl_cie_X(src)
723 + m_cat97.m[i][1] * 1
724 + m_cat97.m[i][2] * pl_cie_Z(src);
725
726 // dest cone
727 C[i][1] = m_cat97.m[i][0] * pl_cie_X(dest)
728 + m_cat97.m[i][1] * 1
729 + m_cat97.m[i][2] * pl_cie_Z(dest);
730 }
731
732 // tmp := I * [Cd/Cs] * Ma
733 struct pl_matrix3x3 tmp = {0};
734 for (int i = 0; i < 3; i++)
735 tmp.m[i][i] = C[i][1] / C[i][0];
736
737 pl_matrix3x3_mul(&tmp, &m_cat97);
738
739 // M := M * Ma^-1 * tmp
740 struct pl_matrix3x3 ma_inv = m_cat97;
741 pl_matrix3x3_invert(&ma_inv);
742 pl_matrix3x3_mul(mat, &ma_inv);
743 pl_matrix3x3_mul(mat, &tmp);
744 }
745
pl_get_adaptation_matrix(struct pl_cie_xy src,struct pl_cie_xy dst)746 struct pl_matrix3x3 pl_get_adaptation_matrix(struct pl_cie_xy src, struct pl_cie_xy dst)
747 {
748 // Use BT.709 primaries (with chosen white point) as an XYZ reference
749 struct pl_raw_primaries csp = *pl_raw_primaries_get(PL_COLOR_PRIM_BT_709);
750 csp.white = src;
751
752 struct pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(&csp);
753 struct pl_matrix3x3 xyz2rgb = rgb2xyz;
754 pl_matrix3x3_invert(&xyz2rgb);
755
756 apply_chromatic_adaptation(src, dst, &xyz2rgb);
757 pl_matrix3x3_mul(&xyz2rgb, &rgb2xyz);
758 return xyz2rgb;
759 }
760
761 const struct pl_cone_params pl_vision_normal = {PL_CONE_NONE, 1.0};
762 const struct pl_cone_params pl_vision_protanomaly = {PL_CONE_L, 0.5};
763 const struct pl_cone_params pl_vision_protanopia = {PL_CONE_L, 0.0};
764 const struct pl_cone_params pl_vision_deuteranomaly = {PL_CONE_M, 0.5};
765 const struct pl_cone_params pl_vision_deuteranopia = {PL_CONE_M, 0.0};
766 const struct pl_cone_params pl_vision_tritanomaly = {PL_CONE_S, 0.5};
767 const struct pl_cone_params pl_vision_tritanopia = {PL_CONE_S, 0.0};
768 const struct pl_cone_params pl_vision_monochromacy = {PL_CONE_LM, 0.0};
769 const struct pl_cone_params pl_vision_achromatopsia = {PL_CONE_LMS, 0.0};
770
pl_get_cone_matrix(const struct pl_cone_params * params,const struct pl_raw_primaries * prim)771 struct pl_matrix3x3 pl_get_cone_matrix(const struct pl_cone_params *params,
772 const struct pl_raw_primaries *prim)
773 {
774 // LMS<-RGB := LMS<-XYZ * XYZ<-RGB
775 struct pl_matrix3x3 rgb2lms = m_cat97;
776 struct pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(prim);
777 pl_matrix3x3_mul(&rgb2lms, &rgb2xyz);
778
779 // LMS versions of the two opposing primaries, plus neutral
780 float lms_r[3] = {1.0, 0.0, 0.0},
781 lms_b[3] = {0.0, 0.0, 1.0},
782 lms_w[3] = {1.0, 1.0, 1.0};
783
784 pl_matrix3x3_apply(&rgb2lms, lms_r);
785 pl_matrix3x3_apply(&rgb2lms, lms_b);
786 pl_matrix3x3_apply(&rgb2lms, lms_w);
787
788 float a, b, c = params->strength;
789 struct pl_matrix3x3 distort;
790
791 switch (params->cones) {
792 case PL_CONE_NONE:
793 return pl_matrix3x3_identity;
794
795 case PL_CONE_L:
796 // Solve to preserve neutral and blue
797 a = (lms_b[0] - lms_b[2] * lms_w[0] / lms_w[2]) /
798 (lms_b[1] - lms_b[2] * lms_w[1] / lms_w[2]);
799 b = (lms_b[0] - lms_b[1] * lms_w[0] / lms_w[1]) /
800 (lms_b[2] - lms_b[1] * lms_w[2] / lms_w[1]);
801 assert(fabs(a * lms_w[1] + b * lms_w[2] - lms_w[0]) < 1e-6);
802
803 distort = (struct pl_matrix3x3) {{
804 { c, (1.0 - c) * a, (1.0 - c) * b},
805 { 0.0, 1.0, 0.0},
806 { 0.0, 0.0, 1.0},
807 }};
808 break;
809
810 case PL_CONE_M:
811 // Solve to preserve neutral and blue
812 a = (lms_b[1] - lms_b[2] * lms_w[1] / lms_w[2]) /
813 (lms_b[0] - lms_b[2] * lms_w[0] / lms_w[2]);
814 b = (lms_b[1] - lms_b[0] * lms_w[1] / lms_w[0]) /
815 (lms_b[2] - lms_b[0] * lms_w[2] / lms_w[0]);
816 assert(fabs(a * lms_w[0] + b * lms_w[2] - lms_w[1]) < 1e-6);
817
818 distort = (struct pl_matrix3x3) {{
819 { 1.0, 0.0, 0.0},
820 {(1.0 - c) * a, c, (1.0 - c) * b},
821 { 0.0, 0.0, 1.0},
822 }};
823 break;
824
825 case PL_CONE_S:
826 // Solve to preserve neutral and red
827 a = (lms_r[2] - lms_r[1] * lms_w[2] / lms_w[1]) /
828 (lms_r[0] - lms_r[1] * lms_w[0] / lms_w[1]);
829 b = (lms_r[2] - lms_r[0] * lms_w[2] / lms_w[0]) /
830 (lms_r[1] - lms_r[0] * lms_w[1] / lms_w[0]);
831 assert(fabs(a * lms_w[0] + b * lms_w[1] - lms_w[2]) < 1e-6);
832
833 distort = (struct pl_matrix3x3) {{
834 { 1.0, 0.0, 0.0},
835 { 0.0, 1.0, 0.0},
836 {(1.0 - c) * a, (1.0 - c) * b, c},
837 }};
838 break;
839
840 case PL_CONE_LM:
841 // Solve to preserve neutral
842 a = lms_w[0] / lms_w[2];
843 b = lms_w[1] / lms_w[2];
844
845 distort = (struct pl_matrix3x3) {{
846 { c, 0.0, (1.0 - c) * a},
847 { 0.0, c, (1.0 - c) * b},
848 { 0.0, 0.0, 1.0},
849 }};
850 break;
851
852 case PL_CONE_MS:
853 // Solve to preserve neutral
854 a = lms_w[1] / lms_w[0];
855 b = lms_w[2] / lms_w[0];
856
857 distort = (struct pl_matrix3x3) {{
858 { 1.0, 0.0, 0.0},
859 {(1.0 - c) * a, c, 0.0},
860 {(1.0 - c) * b, 0.0, c},
861 }};
862 break;
863
864 case PL_CONE_LS:
865 // Solve to preserve neutral
866 a = lms_w[0] / lms_w[1];
867 b = lms_w[2] / lms_w[1];
868
869 distort = (struct pl_matrix3x3) {{
870 { c, (1.0 - c) * a, 0.0},
871 { 0.0, 1.0, 0.0},
872 { 0.0, (1.0 - c) * b, c},
873 }};
874 break;
875
876 case PL_CONE_LMS: {
877 // Rod cells only, which can be modelled somewhat as a combination of
878 // L and M cones. Either way, this is pushing the limits of the our
879 // color model, so this is only a rough approximation.
880 const float w[3] = {0.3605, 0.6415, -0.002};
881 assert(fabs(w[0] + w[1] + w[2] - 1.0) < 1e-6);
882
883 for (int i = 0; i < 3; i++) {
884 for (int j = 0; j < 3; j++) {
885 distort.m[i][j] = (1.0 - c) * w[j] * lms_w[i] / lms_w[j];
886 if (i == j)
887 distort.m[i][j] += c;
888 }
889 }
890 break;
891 }
892
893 default:
894 pl_unreachable();
895 }
896
897 // out := RGB<-LMS * distort * LMS<-RGB
898 struct pl_matrix3x3 out = rgb2lms;
899 pl_matrix3x3_invert(&out);
900 pl_matrix3x3_mul(&out, &distort);
901 pl_matrix3x3_mul(&out, &rgb2lms);
902
903 return out;
904 }
905
pl_get_color_mapping_matrix(const struct pl_raw_primaries * src,const struct pl_raw_primaries * dst,enum pl_rendering_intent intent)906 struct pl_matrix3x3 pl_get_color_mapping_matrix(const struct pl_raw_primaries *src,
907 const struct pl_raw_primaries *dst,
908 enum pl_rendering_intent intent)
909 {
910 // In saturation mapping, we don't care about accuracy and just want
911 // primaries to map to primaries, making this an identity transformation.
912 if (intent == PL_INTENT_SATURATION)
913 return pl_matrix3x3_identity;
914
915 // RGBd<-RGBs = RGBd<-XYZd * XYZd<-XYZs * XYZs<-RGBs
916 // Equations from: http://www.brucelindbloom.com/index.html?Math.html
917 // Note: Perceptual is treated like relative colorimetric. There's no
918 // definition for perceptual other than "make it look good".
919
920 // RGBd<-XYZd matrix
921 struct pl_matrix3x3 xyz2rgb_d = pl_get_xyz2rgb_matrix(dst);
922
923 // Chromatic adaptation, except in absolute colorimetric intent
924 if (intent != PL_INTENT_ABSOLUTE_COLORIMETRIC)
925 apply_chromatic_adaptation(src->white, dst->white, &xyz2rgb_d);
926
927 // XYZs<-RGBs
928 struct pl_matrix3x3 rgb2xyz_s = pl_get_rgb2xyz_matrix(src);
929 pl_matrix3x3_mul(&xyz2rgb_d, &rgb2xyz_s);
930 return xyz2rgb_d;
931 }
932
933 // Test the sign of 'p' relative to the line 'ab' (barycentric coordinates)
test_point_line(const struct pl_cie_xy p,const struct pl_cie_xy a,const struct pl_cie_xy b)934 static float test_point_line(const struct pl_cie_xy p,
935 const struct pl_cie_xy a,
936 const struct pl_cie_xy b)
937 {
938 return (p.x - b.x) * (a.y - b.y) - (a.x - b.x) * (p.y - b.y);
939 }
940
941 // Test if a point is entirely inside a gamut
test_point_gamut(struct pl_cie_xy point,const struct pl_raw_primaries * prim)942 static float test_point_gamut(struct pl_cie_xy point,
943 const struct pl_raw_primaries *prim)
944 {
945 float d1 = test_point_line(point, prim->red, prim->green),
946 d2 = test_point_line(point, prim->green, prim->blue),
947 d3 = test_point_line(point, prim->blue, prim->red);
948
949 bool has_neg = d1 < 0 || d2 < 0 || d3 < 0,
950 has_pos = d1 > 0 || d2 > 0 || d3 > 0;
951
952 return !(has_neg && has_pos);
953 }
954
pl_primaries_superset(const struct pl_raw_primaries * a,const struct pl_raw_primaries * b)955 bool pl_primaries_superset(const struct pl_raw_primaries *a,
956 const struct pl_raw_primaries *b)
957 {
958 return test_point_gamut(b->red, a) &&
959 test_point_gamut(b->green, a) &&
960 test_point_gamut(b->blue, a);
961 }
962
963 /* Fill in the Y, U, V vectors of a yuv-to-rgb conversion matrix
964 * based on the given luma weights of the R, G and B components (lr, lg, lb).
965 * lr+lg+lb is assumed to equal 1.
966 * This function is meant for colorspaces satisfying the following
967 * conditions (which are true for common YUV colorspaces):
968 * - The mapping from input [Y, U, V] to output [R, G, B] is linear.
969 * - Y is the vector [1, 1, 1]. (meaning input Y component maps to 1R+1G+1B)
970 * - U maps to a value with zero R and positive B ([0, x, y], y > 0;
971 * i.e. blue and green only).
972 * - V maps to a value with zero B and positive R ([x, y, 0], x > 0;
973 * i.e. red and green only).
974 * - U and V are orthogonal to the luma vector [lr, lg, lb].
975 * - The magnitudes of the vectors U and V are the minimal ones for which
976 * the image of the set Y=[0...1],U=[-0.5...0.5],V=[-0.5...0.5] under the
977 * conversion function will cover the set R=[0...1],G=[0...1],B=[0...1]
978 * (the resulting matrix can be converted for other input/output ranges
979 * outside this function).
980 * Under these conditions the given parameters lr, lg, lb uniquely
981 * determine the mapping of Y, U, V to R, G, B.
982 */
luma_coeffs(float lr,float lg,float lb)983 static struct pl_matrix3x3 luma_coeffs(float lr, float lg, float lb)
984 {
985 pl_assert(fabs(lr+lg+lb - 1) < 1e-6);
986 return (struct pl_matrix3x3) {{
987 {1, 0, 2 * (1-lr) },
988 {1, -2 * (1-lb) * lb/lg, -2 * (1-lr) * lr/lg },
989 {1, 2 * (1-lb), 0 },
990 }};
991 }
992
993 // Applies hue and saturation controls to a YCbCr->RGB matrix
apply_hue_sat(struct pl_matrix3x3 * m,const struct pl_color_adjustment * params)994 static inline void apply_hue_sat(struct pl_matrix3x3 *m,
995 const struct pl_color_adjustment *params)
996 {
997 // Hue is equivalent to rotating input [U, V] subvector around the origin.
998 // Saturation scales [U, V].
999 float huecos = params->saturation * cos(params->hue);
1000 float huesin = params->saturation * sin(params->hue);
1001 for (int i = 0; i < 3; i++) {
1002 float u = m->m[i][1], v = m->m[i][2];
1003 m->m[i][1] = huecos * u - huesin * v;
1004 m->m[i][2] = huesin * u + huecos * v;
1005 }
1006 }
1007
pl_color_repr_decode(struct pl_color_repr * repr,const struct pl_color_adjustment * params)1008 struct pl_transform3x3 pl_color_repr_decode(struct pl_color_repr *repr,
1009 const struct pl_color_adjustment *params)
1010 {
1011 params = PL_DEF(params, &pl_color_adjustment_neutral);
1012
1013 struct pl_matrix3x3 m;
1014 switch (repr->sys) {
1015 case PL_COLOR_SYSTEM_BT_709: m = luma_coeffs(0.2126, 0.7152, 0.0722); break;
1016 case PL_COLOR_SYSTEM_BT_601: m = luma_coeffs(0.2990, 0.5870, 0.1140); break;
1017 case PL_COLOR_SYSTEM_SMPTE_240M: m = luma_coeffs(0.2122, 0.7013, 0.0865); break;
1018 case PL_COLOR_SYSTEM_BT_2020_NC: m = luma_coeffs(0.2627, 0.6780, 0.0593); break;
1019 case PL_COLOR_SYSTEM_BT_2020_C:
1020 // Note: This outputs into the [-0.5,0.5] range for chroma information.
1021 m = (struct pl_matrix3x3) {{
1022 {0, 0, 1},
1023 {1, 0, 0},
1024 {0, 1, 0}
1025 }};
1026 break;
1027 case PL_COLOR_SYSTEM_BT_2100_PQ: {
1028 // Reversed from the matrix in the spec, hard-coded for efficiency
1029 // and precision reasons. Exact values truncated from ITU-T H-series
1030 // Supplement 18.
1031 static const float lm_t = 0.008609, lm_p = 0.111029625;
1032 m = (struct pl_matrix3x3) {{
1033 {1.0, lm_t, lm_p},
1034 {1.0, -lm_t, -lm_p},
1035 {1.0, 0.560031, -0.320627},
1036 }};
1037 break;
1038 }
1039 case PL_COLOR_SYSTEM_BT_2100_HLG: {
1040 // Similar to BT.2100 PQ, exact values truncated from WolframAlpha
1041 static const float lm_t = 0.01571858011, lm_p = 0.2095810681;
1042 m = (struct pl_matrix3x3) {{
1043 {1.0, lm_t, lm_p},
1044 {1.0, -lm_t, -lm_p},
1045 {1.0, 1.02127108, -0.605274491},
1046 }};
1047 break;
1048 }
1049 case PL_COLOR_SYSTEM_YCGCO:
1050 m = (struct pl_matrix3x3) {{
1051 {1, -1, 1},
1052 {1, 1, 0},
1053 {1, -1, -1},
1054 }};
1055 break;
1056 case PL_COLOR_SYSTEM_UNKNOWN: // fall through
1057 case PL_COLOR_SYSTEM_RGB:
1058 m = pl_matrix3x3_identity;
1059 break;
1060 case PL_COLOR_SYSTEM_XYZ: {
1061 // For lack of anything saner to do, just assume the caller wants
1062 // BT.709 primaries, which is a reasonable assumption.
1063 m = pl_get_xyz2rgb_matrix(pl_raw_primaries_get(PL_COLOR_PRIM_BT_709));
1064 break;
1065 }
1066 case PL_COLOR_SYSTEM_COUNT:
1067 pl_unreachable();
1068 }
1069
1070 // Apply hue and saturation in the correct way depending on the colorspace.
1071 if (pl_color_system_is_ycbcr_like(repr->sys)) {
1072 apply_hue_sat(&m, params);
1073 } else if (params->saturation != 1.0 || params->hue != 0.0) {
1074 // Arbitrarily simulate hue shifts using the BT.709 YCbCr model
1075 struct pl_matrix3x3 yuv2rgb = luma_coeffs(0.2126, 0.7152, 0.0722);
1076 struct pl_matrix3x3 rgb2yuv = yuv2rgb;
1077 pl_matrix3x3_invert(&rgb2yuv);
1078 apply_hue_sat(&yuv2rgb, params);
1079 // M := RGB<-YUV * YUV<-RGB * M
1080 pl_matrix3x3_rmul(&rgb2yuv, &m);
1081 pl_matrix3x3_rmul(&yuv2rgb, &m);
1082 }
1083
1084 // Apply color temperature adaptation, relative to BT.709 primaries
1085 if (params->temperature) {
1086 struct pl_cie_xy src = pl_white_from_temp(6500);
1087 struct pl_cie_xy dst = pl_white_from_temp(6500 + 3500 * params->temperature);
1088 struct pl_matrix3x3 adapt = pl_get_adaptation_matrix(src, dst);
1089 pl_matrix3x3_rmul(&adapt, &m);
1090 }
1091
1092 struct pl_transform3x3 out = { .mat = m };
1093 int bit_depth = PL_DEF(repr->bits.sample_depth,
1094 PL_DEF(repr->bits.color_depth, 8));
1095
1096 double ymax, ymin, cmax, cmid;
1097 double scale = (1LL << bit_depth) / ((1LL << bit_depth) - 1.0);
1098
1099 switch (pl_color_levels_guess(repr)) {
1100 case PL_COLOR_LEVELS_LIMITED: {
1101 ymax = 235 / 256. * scale;
1102 ymin = 16 / 256. * scale;
1103 cmax = 240 / 256. * scale;
1104 cmid = 128 / 256. * scale;
1105 break;
1106 }
1107 case PL_COLOR_LEVELS_FULL:
1108 // Note: For full-range YUV, there are multiple, subtly inconsistent
1109 // standards. So just pick the sanest implementation, which is to
1110 // assume MAX_INT == 1.0.
1111 ymax = 1.0;
1112 ymin = 0.0;
1113 cmax = 1.0;
1114 cmid = 128 / 256. * scale; // *not* exactly 0.5
1115 break;
1116 default:
1117 pl_unreachable();
1118 }
1119
1120 double ymul = 1.0 / (ymax - ymin);
1121 double cmul = 0.5 / (cmax - cmid);
1122
1123 double mul[3] = { ymul, ymul, ymul };
1124 double black[3] = { ymin, ymin, ymin };
1125
1126 if (pl_color_system_is_ycbcr_like(repr->sys)) {
1127 mul[1] = mul[2] = cmul;
1128 black[1] = black[2] = cmid;
1129 }
1130
1131 // Contrast scales the output value range (gain)
1132 // Brightness scales the constant output bias (black lift/boost)
1133 for (int i = 0; i < 3; i++) {
1134 mul[i] *= params->contrast;
1135 out.c[i] += params->brightness;
1136 }
1137
1138 // Multiply in the texture multiplier and adjust `c` so that black[j] keeps
1139 // on mapping to RGB=0 (black to black)
1140 for (int i = 0; i < 3; i++) {
1141 for (int j = 0; j < 3; j++) {
1142 out.mat.m[i][j] *= mul[j];
1143 out.c[i] -= out.mat.m[i][j] * black[j];
1144 }
1145 }
1146
1147 // Finally, multiply in the scaling factor required to get the color up to
1148 // the correct representation.
1149 pl_matrix3x3_scale(&out.mat, pl_color_repr_normalize(repr));
1150
1151 // Update the metadata to reflect the change.
1152 repr->sys = PL_COLOR_SYSTEM_RGB;
1153 repr->levels = PL_COLOR_LEVELS_FULL;
1154
1155 return out;
1156 }
1157
pl_icc_profile_equal(const struct pl_icc_profile * p1,const struct pl_icc_profile * p2)1158 bool pl_icc_profile_equal(const struct pl_icc_profile *p1,
1159 const struct pl_icc_profile *p2)
1160 {
1161 if (p1->len != p2->len)
1162 return false;
1163
1164 // Ignore signatures on length-0 profiles, as a special case
1165 return !p1->len || p1->signature == p2->signature;
1166 }
1167
pl_icc_profile_compute_signature(struct pl_icc_profile * profile)1168 void pl_icc_profile_compute_signature(struct pl_icc_profile *profile)
1169 {
1170 // In theory, we could get this value from the profile header itself if
1171 // lcms is available, but I'm not sure if it's even worth the trouble. Just
1172 // hard-code this to a siphash64(), which is decently fast anyway.
1173 profile->signature = pl_str_hash((pl_str) {
1174 .buf = (uint8_t *) profile->data,
1175 .len = profile->len
1176 });
1177 }
1178