Branch data Line data Source code
1 : : #include "utils.h"
2 : : #include "log.h"
3 : :
4 : : #include <libplacebo/gamut_mapping.h>
5 : : #include <libplacebo/tone_mapping.h>
6 : :
7 : : //#define PRINT_LUTS
8 : :
9 : 1 : int main()
10 : : {
11 : 1 : pl_log log = pl_test_logger();
12 : :
13 : : // PQ unit tests
14 [ - + ]: 1 : REQUIRE_FEQ(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, 0.0), 0.0, 1e-2);
15 [ - + ]: 1 : REQUIRE_FEQ(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, 1.0), 10000.0, 1e-2);
16 [ - + ]: 1 : REQUIRE_FEQ(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, 0.58), 203.0, 1e-2);
17 : :
18 : : // Test round-trip
19 [ + + ]: 102 : for (float x = 0.0f; x < 1.0f; x += 0.01f) {
20 [ - + ]: 101 : REQUIRE_FEQ(x, pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ,
21 : : pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, x)),
22 : : 1e-5);
23 : : }
24 : :
25 : : static float lut[128];
26 : 1 : struct pl_tone_map_params params = {
27 : : .constants = { PL_TONE_MAP_CONSTANTS },
28 : : .input_scaling = PL_HDR_PQ,
29 : : .output_scaling = PL_HDR_PQ,
30 : : .lut_size = PL_ARRAY_SIZE(lut),
31 : : };
32 : :
33 : : // Test regular tone-mapping
34 : 1 : params.input_min = pl_hdr_rescale(PL_HDR_NITS, params.input_scaling, 0.005);
35 : 1 : params.input_max = pl_hdr_rescale(PL_HDR_NITS, params.input_scaling, 1000.0);
36 : 1 : params.output_min = pl_hdr_rescale(PL_HDR_NORM, params.output_scaling, 0.001);
37 : 1 : params.output_max = pl_hdr_rescale(PL_HDR_NORM, params.output_scaling, 1.0);
38 : :
39 : 1 : struct pl_tone_map_params params_inv = params;
40 : 1 : PL_SWAP(params_inv.input_min, params_inv.output_min);
41 : 1 : PL_SWAP(params_inv.input_max, params_inv.output_max);
42 : :
43 : : int tested_pure_bpc = 0;
44 : :
45 : : // Generate example tone mapping curves, forward and inverse
46 [ + + ]: 13 : for (int i = 0; i < pl_num_tone_map_functions; i++) {
47 : 12 : const struct pl_tone_map_function *fun = pl_tone_map_functions[i];
48 : 12 : printf("Testing tone-mapping function %s\n", fun->name);
49 : 12 : params.function = params_inv.function = fun;
50 : : pl_clock_t start = pl_clock_now();
51 : 12 : pl_tone_map_generate(lut, ¶ms);
52 : 12 : pl_log_cpu_time(log, start, pl_clock_now(), "generating LUT");
53 [ + + ]: 1548 : for (int j = 0; j < PL_ARRAY_SIZE(lut); j++) {
54 [ - + ]: 1536 : REQUIRE(isfinite(lut[j]) && !isnan(lut[j]));
55 [ + + ]: 1536 : if (j > 0)
56 [ - + ]: 1524 : REQUIRE_CMP(lut[j], >=, lut[j - 1], "f");
57 : : #ifdef PRINT_LUTS
58 : : printf("%f, %f\n", j / (PL_ARRAY_SIZE(lut) - 1.0f), lut[j]);
59 : : #endif
60 : : }
61 : :
62 [ + + + + ]: 12 : if (fun->map_inverse || !tested_pure_bpc++) {
63 : : start = pl_clock_now();
64 : 6 : pl_tone_map_generate(lut, ¶ms_inv);
65 : 6 : pl_log_cpu_time(log, start, pl_clock_now(), "generating inverse LUT");
66 [ + + ]: 774 : for (int j = 0; j < PL_ARRAY_SIZE(lut); j++) {
67 [ - + ]: 768 : REQUIRE(isfinite(lut[j]) && !isnan(lut[j]));
68 [ + + ]: 768 : if (j > 0)
69 [ - + ]: 762 : REQUIRE_CMP(lut[j], >=, lut[j - 1], "f");
70 : : #ifdef PRINT_LUTS
71 : : printf("%f, %f\n", j / (PL_ARRAY_SIZE(lut) - 1.0f), lut[j]);
72 : : #endif
73 : : }
74 : : }
75 : : }
76 : :
77 : : // Test that `spline` is a no-op for 1:1 tone mapping
78 : 1 : params.output_min = params.input_min;
79 : 1 : params.output_max = params.input_max;
80 : 1 : params.function = &pl_tone_map_spline;
81 : 1 : pl_tone_map_generate(lut, ¶ms);
82 [ + + ]: 129 : for (int j = 0; j < PL_ARRAY_SIZE(lut); j++) {
83 : 128 : float x = j / (PL_ARRAY_SIZE(lut) - 1.0f);
84 : 128 : x = PL_MIX(params.input_min, params.input_max, x);
85 [ - + ]: 128 : REQUIRE_FEQ(x, lut[j], 1e-5);
86 : : }
87 : :
88 : : // Test some gamut mapping methods
89 [ + + ]: 11 : for (int i = 0; i < pl_num_gamut_map_functions; i++) {
90 : : static const float min_rgb = 0.1f, max_rgb = PL_COLOR_SDR_WHITE;
91 : 50 : struct pl_gamut_map_params gamut = {
92 : 10 : .function = pl_gamut_map_functions[i],
93 : 10 : .input_gamut = *pl_raw_primaries_get(PL_COLOR_PRIM_BT_2020),
94 : 10 : .output_gamut = *pl_raw_primaries_get(PL_COLOR_PRIM_BT_709),
95 : 10 : .min_luma = pl_hdr_rescale(PL_HDR_NITS, PL_HDR_PQ, min_rgb),
96 : 10 : .max_luma = pl_hdr_rescale(PL_HDR_NITS, PL_HDR_PQ, max_rgb),
97 : : };
98 : :
99 : 10 : printf("Testing gamut-mapping function %s\n", gamut.function->name);
100 : :
101 : : // Require that black maps to black and white maps to white
102 : 10 : float black[3] = { gamut.min_luma, 0.0f, 0.0f };
103 : 10 : float white[3] = { gamut.max_luma, 0.0f, 0.0f };
104 : 10 : pl_gamut_map_sample(black, &gamut);
105 : 10 : pl_gamut_map_sample(white, &gamut);
106 [ - + ]: 10 : REQUIRE_FEQ(black[0], gamut.min_luma, 1e-4);
107 [ - + ]: 10 : REQUIRE_FEQ(black[1], 0.0f, 1e-4);
108 [ - + ]: 10 : REQUIRE_FEQ(black[2], 0.0f, 1e-4);
109 [ + + ]: 10 : if (gamut.function != &pl_gamut_map_darken)
110 [ - + ]: 9 : REQUIRE_FEQ(white[0], gamut.max_luma, 1e-4);
111 [ - + ]: 10 : REQUIRE_FEQ(white[1], 0.0f, 1e-4);
112 [ - + ]: 10 : REQUIRE_FEQ(white[2], 0.0f, 1e-4);
113 : : }
114 : :
115 : : enum { LUT3D_SIZE = 65 }; // for benchmarking
116 : 4 : struct pl_gamut_map_params perceptual = {
117 : : .function = &pl_gamut_map_perceptual,
118 : 1 : .input_gamut = *pl_raw_primaries_get(PL_COLOR_PRIM_BT_2020),
119 : 1 : .output_gamut = *pl_raw_primaries_get(PL_COLOR_PRIM_BT_709),
120 : 1 : .max_luma = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, 1.0f),
121 : : .lut_size_I = LUT3D_SIZE,
122 : : .lut_size_C = LUT3D_SIZE,
123 : : .lut_size_h = LUT3D_SIZE,
124 : : .lut_stride = 3,
125 : :
126 : : // Set strength to maximum, because otherwise the saturation mapping
127 : : // code will not fully apply, invalidating the following test
128 : : .constants.perceptual_strength = 1.0f,
129 : : };
130 : :
131 : : // Test that primaries round-trip for perceptual gamut mapping
132 : 1 : const pl_matrix3x3 rgb2lms_src = pl_ipt_rgb2lms(&perceptual.input_gamut);
133 : 1 : const pl_matrix3x3 rgb2lms_dst = pl_ipt_rgb2lms(&perceptual.output_gamut);
134 : 1 : const pl_matrix3x3 lms2rgb_dst = pl_ipt_lms2rgb(&perceptual.output_gamut);
135 : : static const float refpoints[][3] = {
136 : : {1, 0, 0}, {0, 1, 0}, {0, 0, 1},
137 : : {0, 1, 1}, {1, 0, 1}, {1, 1, 0},
138 : : };
139 : :
140 [ + + ]: 7 : for (int i = 0; i < PL_ARRAY_SIZE(refpoints); i++) {
141 : 6 : float c[3] = { refpoints[i][0], refpoints[i][1], refpoints[i][2] };
142 : 6 : float ref[3] = { refpoints[i][0], refpoints[i][1], refpoints[i][2] };
143 : 6 : printf("Testing primary: RGB {%.0f %.0f %.0f}\n", c[0], c[1], c[2]);
144 : 6 : pl_matrix3x3_apply(&rgb2lms_src, c);
145 : 6 : c[0] = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, c[0]);
146 : 6 : c[1] = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, c[1]);
147 : 6 : c[2] = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, c[2]);
148 : 6 : pl_matrix3x3_apply(&pl_ipt_lms2ipt, c);
149 : 6 : printf("Before: ICh {%f %f %f}\n",
150 : 6 : c[0], sqrtf(c[1]*c[1] + c[2]*c[2]), atan2f(c[2], c[1]));
151 : 6 : pl_gamut_map_sample(c, &perceptual);
152 : 6 : float rgb[3] = { c[0], c[1], c[2] };
153 : 6 : pl_matrix3x3_apply(&pl_ipt_ipt2lms, rgb);
154 : 6 : rgb[0] = pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, rgb[0]);
155 : 6 : rgb[1] = pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, rgb[1]);
156 : 6 : rgb[2] = pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, rgb[2]);
157 : 6 : pl_matrix3x3_apply(&lms2rgb_dst, rgb);
158 : 6 : const float hue = atan2f(c[2], c[1]);
159 : 6 : printf("After: ICh {%f %f %f} = RGB {%f %f %f}\n",
160 : 6 : c[0], sqrtf(c[1]*c[1] + c[2]*c[2]), hue, rgb[0], rgb[1], rgb[2]);
161 : 6 : pl_matrix3x3_apply(&rgb2lms_dst, ref);
162 : 6 : ref[0] = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, ref[0]);
163 : 6 : ref[1] = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, ref[1]);
164 : 6 : ref[2] = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, ref[2]);
165 : 6 : pl_matrix3x3_apply(&pl_ipt_lms2ipt, ref);
166 : 6 : const float hue_ref = atan2f(ref[2], ref[1]);
167 : 6 : printf("Should be: ICh {%f %f %f}\n",
168 : 6 : ref[0], sqrtf(ref[1]*ref[1] + ref[2]*ref[2]), hue_ref);
169 [ - + ]: 6 : REQUIRE_FEQ(hue, hue_ref, 3.0e-3);
170 : : }
171 : :
172 : 1 : float *tmp = malloc(sizeof(float[LUT3D_SIZE][LUT3D_SIZE][LUT3D_SIZE][3]));
173 [ + - ]: 1 : if (tmp) {
174 : : pl_clock_t start = pl_clock_now();
175 : 1 : pl_gamut_map_generate(tmp, &perceptual);
176 : 1 : pl_log_cpu_time(log, start, pl_clock_now(), "generating 3DLUT");
177 : 1 : free(tmp);
178 : : }
179 : :
180 : 1 : pl_log_destroy(&log);
181 : : }
|