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