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