1 #include "tests.h"
2 
main()3 int main()
4 {
5     for (enum pl_color_system sys = 0; sys < PL_COLOR_SYSTEM_COUNT; sys++) {
6         bool ycbcr = sys >= PL_COLOR_SYSTEM_BT_601 && sys <= PL_COLOR_SYSTEM_YCGCO;
7         REQUIRE(ycbcr == pl_color_system_is_ycbcr_like(sys));
8     }
9 
10     for (enum pl_color_transfer trc = 0; trc < PL_COLOR_TRC_COUNT; trc++) {
11         bool hdr = trc >= PL_COLOR_TRC_PQ && trc <= PL_COLOR_TRC_S_LOG2;
12         REQUIRE(hdr == pl_color_transfer_is_hdr(trc));
13         REQUIRE(pl_color_transfer_nominal_peak(trc) >= 1.0);
14     }
15 
16     float pq_peak = pl_color_transfer_nominal_peak(PL_COLOR_TRC_PQ);
17     REQUIRE(feq(PL_COLOR_SDR_WHITE * pq_peak, 10000, 1e-7));
18 
19     struct pl_color_repr tv_repr = {
20         .sys       = PL_COLOR_SYSTEM_BT_709,
21         .levels    = PL_COLOR_LEVELS_LIMITED,
22     };
23 
24     struct pl_color_repr pc_repr = {
25         .sys       = PL_COLOR_SYSTEM_RGB,
26         .levels    = PL_COLOR_LEVELS_FULL,
27     };
28 
29     // Ensure this is a no-op for bits == bits
30     for (int bits = 1; bits <= 16; bits++) {
31         tv_repr.bits.color_depth = tv_repr.bits.sample_depth = bits;
32         pc_repr.bits.color_depth = pc_repr.bits.sample_depth = bits;
33         REQUIRE(feq(pl_color_repr_normalize(&tv_repr), 1.0, 1e-7));
34         REQUIRE(feq(pl_color_repr_normalize(&pc_repr), 1.0, 1e-7));
35     }
36 
37     tv_repr.bits.color_depth  = 8;
38     tv_repr.bits.sample_depth = 10;
39     float tv8to10 = pl_color_repr_normalize(&tv_repr);
40 
41     tv_repr.bits.color_depth  = 8;
42     tv_repr.bits.sample_depth = 12;
43     float tv8to12 = pl_color_repr_normalize(&tv_repr);
44 
45     // Simulate the effect of GPU texture sampling on UNORM texture
46     REQUIRE(feq(tv8to10 * 16 /1023.,  64/1023., 1e-7)); // black
47     REQUIRE(feq(tv8to10 * 235/1023., 940/1023., 1e-7)); // nominal white
48     REQUIRE(feq(tv8to10 * 128/1023., 512/1023., 1e-7)); // achromatic
49     REQUIRE(feq(tv8to10 * 240/1023., 960/1023., 1e-7)); // nominal chroma peak
50 
51     REQUIRE(feq(tv8to12 * 16 /4095., 256 /4095., 1e-7)); // black
52     REQUIRE(feq(tv8to12 * 235/4095., 3760/4095., 1e-7)); // nominal white
53     REQUIRE(feq(tv8to12 * 128/4095., 2048/4095., 1e-7)); // achromatic
54     REQUIRE(feq(tv8to12 * 240/4095., 3840/4095., 1e-7)); // nominal chroma peak
55 
56     // Ensure lavc's xyz12 is handled correctly
57     struct pl_color_repr xyz12 = {
58         .sys    = PL_COLOR_SYSTEM_XYZ,
59         .levels = PL_COLOR_LEVELS_UNKNOWN,
60         .bits   = {
61             .sample_depth = 16,
62             .color_depth  = 12,
63             .bit_shift    = 4,
64         },
65     };
66 
67     float xyz = pl_color_repr_normalize(&xyz12);
68     REQUIRE(feq(xyz * (4095 << 4), 65535, 1e-7));
69 
70     // Assume we uploaded a 10-bit source directly (unshifted) as a 16-bit
71     // texture. This texture multiplication factor should make it behave as if
72     // it was uploaded as a 10-bit texture instead.
73     pc_repr.bits.color_depth = 10;
74     pc_repr.bits.sample_depth = 16;
75     float pc10to16 = pl_color_repr_normalize(&pc_repr);
76     REQUIRE(feq(pc10to16 * 1000/65535., 1000/1023., 1e-7));
77 
78     const struct pl_raw_primaries *bt709, *bt2020;
79     bt709 = pl_raw_primaries_get(PL_COLOR_PRIM_BT_709);
80     bt2020 = pl_raw_primaries_get(PL_COLOR_PRIM_BT_2020);
81 
82     struct pl_matrix3x3 rgb2xyz, rgb2xyz_;
83     rgb2xyz = rgb2xyz_ = pl_get_rgb2xyz_matrix(bt709);
84     pl_matrix3x3_invert(&rgb2xyz_);
85     pl_matrix3x3_invert(&rgb2xyz_);
86 
87     // Make sure the double-inversion round trips
88     for (int y = 0; y < 3; y++) {
89         for (int x = 0; x < 3; x++)
90             REQUIRE(feq(rgb2xyz.m[y][x], rgb2xyz_.m[y][x], 1e-6));
91     }
92 
93     // Make sure mapping the spectral RGB colors (i.e. the matrix rows) matches
94     // our original primaries
95     float Y = rgb2xyz.m[1][0];
96     REQUIRE(feq(rgb2xyz.m[0][0], pl_cie_X(bt709->red) * Y, 1e-7));
97     REQUIRE(feq(rgb2xyz.m[2][0], pl_cie_Z(bt709->red) * Y, 1e-7));
98     Y = rgb2xyz.m[1][1];
99     REQUIRE(feq(rgb2xyz.m[0][1], pl_cie_X(bt709->green) * Y, 1e-7));
100     REQUIRE(feq(rgb2xyz.m[2][1], pl_cie_Z(bt709->green) * Y, 1e-7));
101     Y = rgb2xyz.m[1][2];
102     REQUIRE(feq(rgb2xyz.m[0][2], pl_cie_X(bt709->blue) * Y, 1e-7));
103     REQUIRE(feq(rgb2xyz.m[2][2], pl_cie_Z(bt709->blue) * Y, 1e-7));
104 
105     // Make sure the gamut mapping round-trips
106     struct pl_matrix3x3 bt709_bt2020, bt2020_bt709;
107     bt709_bt2020 = pl_get_color_mapping_matrix(bt709, bt2020, PL_INTENT_RELATIVE_COLORIMETRIC);
108     bt2020_bt709 = pl_get_color_mapping_matrix(bt2020, bt709, PL_INTENT_RELATIVE_COLORIMETRIC);
109     for (int n = 0; n < 10; n++) {
110         float vec[3] = { RANDOM, RANDOM, RANDOM };
111         float dst[3] = { vec[0],    vec[1],    vec[2]    };
112         pl_matrix3x3_apply(&bt709_bt2020, dst);
113         pl_matrix3x3_apply(&bt2020_bt709, dst);
114         for (int i = 0; i < 3; i++)
115             REQUIRE(feq(dst[i], vec[i], 1e-6));
116     }
117 
118     // Ensure the decoding matrix round-trips to white/black
119     for (enum pl_color_system sys = 0; sys < PL_COLOR_SYSTEM_COUNT; sys++) {
120         if (!pl_color_system_is_linear(sys))
121             continue;
122 
123         printf("testing color system %u\n", (unsigned) sys);
124         struct pl_color_repr repr = {
125             .levels = PL_COLOR_LEVELS_LIMITED,
126             .sys = sys,
127         };
128 
129         struct pl_transform3x3 yuv2rgb = pl_color_repr_decode(&repr, NULL);
130         static const float white_ycbcr[3] = { 235/255., 128/255., 128/255. };
131         static const float black_ycbcr[3] = {  16/255., 128/255., 128/255. };
132         static const float white_other[3] = { 235/255., 235/255., 235/255. };
133         static const float black_other[3] = {  16/255.,  16/255.,  16/255. };
134 
135         float white[3], black[3];
136         for (int i = 0; i < 3; i++) {
137             if (pl_color_system_is_ycbcr_like(sys)) {
138                 white[i] = white_ycbcr[i];
139                 black[i] = black_ycbcr[i];
140             } else {
141                 white[i] = white_other[i];
142                 black[i] = black_other[i];
143             }
144         }
145 
146         pl_transform3x3_apply(&yuv2rgb, white);
147         REQUIRE(feq(white[0], 1.0, 1e-6));
148         REQUIRE(feq(white[1], 1.0, 1e-6));
149         REQUIRE(feq(white[2], 1.0, 1e-6));
150 
151         pl_transform3x3_apply(&yuv2rgb, black);
152         REQUIRE(feq(black[0], 0.0, 1e-6));
153         REQUIRE(feq(black[1], 0.0, 1e-6));
154         REQUIRE(feq(black[2], 0.0, 1e-6));
155     }
156 
157     // Make sure chromatic adaptation works
158     struct pl_raw_primaries bt709_d50;
159     bt709_d50 = *pl_raw_primaries_get(PL_COLOR_PRIM_BT_709);
160     bt709_d50.white = (struct pl_cie_xy) { 0.34567, 0.35850 };
161 
162     struct pl_matrix3x3 d50_d65;
163     d50_d65 = pl_get_color_mapping_matrix(&bt709_d50, bt709, PL_INTENT_RELATIVE_COLORIMETRIC);
164 
165     float white[3] = { 1.0, 1.0, 1.0 };
166     pl_matrix3x3_apply(&d50_d65, white);
167     REQUIRE(feq(white[0], 1.0, 1e-6) && feq(white[1], 1.0, 1e-6) && feq(white[2], 1.0, 1e-6));
168 
169     // Simulate a typical 10-bit YCbCr -> 16 bit texture conversion
170     tv_repr.bits.color_depth  = 10;
171     tv_repr.bits.sample_depth = 16;
172     struct pl_transform3x3 yuv2rgb;
173     yuv2rgb = pl_color_repr_decode(&tv_repr, NULL);
174     float test[3] = { 575/65535., 336/65535., 640/65535. };
175     pl_transform3x3_apply(&yuv2rgb, test);
176     REQUIRE(feq(test[0], 0.808305, 1e-6));
177     REQUIRE(feq(test[1], 0.553254, 1e-6));
178     REQUIRE(feq(test[2], 0.218841, 1e-6));
179 
180     // DVD
181     REQUIRE(pl_color_system_guess_ycbcr(720, 480) == PL_COLOR_SYSTEM_BT_601);
182     REQUIRE(pl_color_system_guess_ycbcr(720, 576) == PL_COLOR_SYSTEM_BT_601);
183     REQUIRE(pl_color_primaries_guess(720, 576) == PL_COLOR_PRIM_BT_601_625);
184     REQUIRE(pl_color_primaries_guess(720, 480) == PL_COLOR_PRIM_BT_601_525);
185     // PAL 16:9
186     REQUIRE(pl_color_system_guess_ycbcr(1024, 576) == PL_COLOR_SYSTEM_BT_601);
187     REQUIRE(pl_color_primaries_guess(1024, 576) == PL_COLOR_PRIM_BT_601_625);
188     // HD
189     REQUIRE(pl_color_system_guess_ycbcr(1280, 720)  == PL_COLOR_SYSTEM_BT_709);
190     REQUIRE(pl_color_system_guess_ycbcr(1920, 1080) == PL_COLOR_SYSTEM_BT_709);
191     REQUIRE(pl_color_primaries_guess(1280, 720)  == PL_COLOR_PRIM_BT_709);
192     REQUIRE(pl_color_primaries_guess(1920, 1080) == PL_COLOR_PRIM_BT_709);
193 
194     // Odd/weird videos
195     REQUIRE(pl_color_primaries_guess(2000, 576) == PL_COLOR_PRIM_BT_709);
196     REQUIRE(pl_color_primaries_guess(200, 200) == PL_COLOR_PRIM_BT_709);
197 
198     REQUIRE(pl_color_repr_equal(&pl_color_repr_sdtv, &pl_color_repr_sdtv));
199     REQUIRE(!pl_color_repr_equal(&pl_color_repr_sdtv, &pl_color_repr_hdtv));
200 
201     struct pl_color_repr repr = pl_color_repr_unknown;
202     pl_color_repr_merge(&repr, &pl_color_repr_uhdtv);
203     REQUIRE(pl_color_repr_equal(&repr, &pl_color_repr_uhdtv));
204 
205     REQUIRE(!pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_UNKNOWN));
206     REQUIRE(!pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_BT_601_525));
207     REQUIRE(!pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_BT_601_625));
208     REQUIRE(!pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_BT_709));
209     REQUIRE(!pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_BT_470M));
210     REQUIRE(pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_BT_2020));
211     REQUIRE(pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_APPLE));
212     REQUIRE(pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_ADOBE));
213     REQUIRE(pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_PRO_PHOTO));
214     REQUIRE(pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_CIE_1931));
215     REQUIRE(pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_DCI_P3));
216     REQUIRE(pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_DISPLAY_P3));
217     REQUIRE(pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_V_GAMUT));
218     REQUIRE(pl_color_primaries_is_wide_gamut(PL_COLOR_PRIM_S_GAMUT));
219 
220     REQUIRE(!pl_color_light_is_scene_referred(PL_COLOR_LIGHT_UNKNOWN));
221     REQUIRE(!pl_color_light_is_scene_referred(PL_COLOR_LIGHT_DISPLAY));
222     REQUIRE(pl_color_light_is_scene_referred(PL_COLOR_LIGHT_SCENE_HLG));
223     REQUIRE(pl_color_light_is_scene_referred(PL_COLOR_LIGHT_SCENE_709_1886));
224     REQUIRE(pl_color_light_is_scene_referred(PL_COLOR_LIGHT_SCENE_1_2));
225 
226     struct pl_color_space space = pl_color_space_unknown;
227     pl_color_space_merge(&space, &pl_color_space_bt709);
228     REQUIRE(pl_color_space_equal(&space, &pl_color_space_bt709));
229 
230     // Infer some color spaces
231     struct pl_color_space hlg = {
232         .primaries = PL_COLOR_PRIM_BT_2020,
233         .transfer = PL_COLOR_TRC_HLG,
234     };
235 
236     pl_color_space_infer(&hlg);
237     REQUIRE(hlg.light == PL_COLOR_LIGHT_SCENE_HLG);
238 
239     struct pl_color_space unknown = {0};
240     struct pl_color_space display = {
241         .primaries = PL_COLOR_PRIM_BT_709,
242         .transfer = PL_COLOR_TRC_BT_1886,
243         .light = PL_COLOR_LIGHT_DISPLAY,
244         .sig_peak = 1.0,
245         .sig_avg = 0.25,
246         .sig_scale = 1.0,
247     };
248 
249     pl_color_space_infer(&unknown);
250     REQUIRE(pl_color_space_equal(&unknown, &display));
251 
252     float x, y;
253     pl_chroma_location_offset(PL_CHROMA_LEFT, &x, &y);
254     REQUIRE(x == -0.5 && y == 0.0);
255     pl_chroma_location_offset(PL_CHROMA_TOP_LEFT, &x, &y);
256     REQUIRE(x == -0.5 && y == -0.5);
257     pl_chroma_location_offset(PL_CHROMA_CENTER, &x, &y);
258     REQUIRE(x == 0.0 && y == 0.0);
259     pl_chroma_location_offset(PL_CHROMA_BOTTOM_CENTER, &x, &y);
260     REQUIRE(x == 0.0 && y == 0.5);
261 
262     REQUIRE(pl_raw_primaries_get(PL_COLOR_PRIM_UNKNOWN) ==
263             pl_raw_primaries_get(PL_COLOR_PRIM_BT_709));
264 
265     // Color blindness tests
266     float red[3]   = { 1.0, 0.0, 0.0 };
267     float green[3] = { 0.0, 1.0, 0.0 };
268     float blue[3]  = { 0.0, 0.0, 1.0 };
269 
270 #define TEST_CONE(model, color)                                                 \
271     do {                                                                        \
272         float tmp[3] = { (color)[0], (color)[1], (color)[2] };                  \
273         struct pl_matrix3x3 mat = pl_get_cone_matrix(&(model), bt709);          \
274         pl_matrix3x3_apply(&mat, tmp);                                          \
275         printf("%s + %s = %f %f %f\n", #model, #color, tmp[0], tmp[1], tmp[2]); \
276         for (int i = 0; i < 3; i++)                                             \
277             REQUIRE(fabs((color)[i] - tmp[i]) < 1e-6);                          \
278     } while(0)
279 
280     struct pl_cone_params red_only = { .cones = PL_CONE_MS };
281     struct pl_cone_params green_only = { .cones = PL_CONE_LS };
282     struct pl_cone_params blue_only = pl_vision_monochromacy;
283 
284     // These models should all round-trip white
285     TEST_CONE(pl_vision_normal, white);
286     TEST_CONE(pl_vision_protanopia, white);
287     TEST_CONE(pl_vision_protanomaly, white);
288     TEST_CONE(pl_vision_deuteranomaly, white);
289     TEST_CONE(pl_vision_tritanomaly, white);
290     TEST_CONE(pl_vision_achromatopsia, white);
291     TEST_CONE(red_only, white);
292     TEST_CONE(green_only, white);
293     TEST_CONE(blue_only, white);
294 
295     // These models should round-trip blue
296     TEST_CONE(pl_vision_normal, blue);
297     TEST_CONE(pl_vision_protanomaly, blue);
298     TEST_CONE(pl_vision_deuteranomaly, blue);
299 
300     // These models should round-trip red
301     TEST_CONE(pl_vision_normal, red);
302     TEST_CONE(pl_vision_tritanomaly, red);
303     TEST_CONE(pl_vision_tritanopia, red);
304 
305     // These models should round-trip green
306     TEST_CONE(pl_vision_normal, green);
307 
308     // Color adaptation tests
309     struct pl_cie_xy d65 = pl_white_from_temp(6504);
310     struct pl_cie_xy d55 = pl_white_from_temp(5503);
311     REQUIRE(feq(d65.x, 0.31271, 1e-3) && feq(d65.y, 0.32902, 1e-3));
312     REQUIRE(feq(d55.x, 0.33242, 1e-3) && feq(d55.y, 0.34743, 1e-3));
313 }
314