diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:38:23 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:38:23 +0000 |
commit | ff6e3c025658a5fa1affd094f220b623e7e1b24b (patch) | |
tree | 9faab72d69c92d24e349d184f5869b9796f17e0c /src/tests/tone_mapping.c | |
parent | Initial commit. (diff) | |
download | libplacebo-upstream.tar.xz libplacebo-upstream.zip |
Adding upstream version 6.338.2.upstream/6.338.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/tests/tone_mapping.c | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/src/tests/tone_mapping.c b/src/tests/tone_mapping.c new file mode 100644 index 0000000..0a48945 --- /dev/null +++ b/src/tests/tone_mapping.c @@ -0,0 +1,181 @@ +#include "tests.h" +#include "log.h" + +#include <libplacebo/gamut_mapping.h> +#include <libplacebo/tone_mapping.h> + +//#define PRINT_LUTS + +int main() +{ + pl_log log = pl_test_logger(); + + // PQ unit tests + REQUIRE_FEQ(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, 0.0), 0.0, 1e-2); + REQUIRE_FEQ(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, 1.0), 10000.0, 1e-2); + REQUIRE_FEQ(pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, 0.58), 203.0, 1e-2); + + // Test round-trip + for (float x = 0.0f; x < 1.0f; x += 0.01f) { + REQUIRE_FEQ(x, pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, + pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, x)), + 1e-5); + } + + static float lut[128]; + struct pl_tone_map_params params = { + .constants = { PL_TONE_MAP_CONSTANTS }, + .input_scaling = PL_HDR_PQ, + .output_scaling = PL_HDR_PQ, + .lut_size = PL_ARRAY_SIZE(lut), + }; + + // Test regular tone-mapping + params.input_min = pl_hdr_rescale(PL_HDR_NITS, params.input_scaling, 0.005); + params.input_max = pl_hdr_rescale(PL_HDR_NITS, params.input_scaling, 1000.0); + params.output_min = pl_hdr_rescale(PL_HDR_NORM, params.output_scaling, 0.001); + params.output_max = pl_hdr_rescale(PL_HDR_NORM, params.output_scaling, 1.0); + + struct pl_tone_map_params params_inv = params; + PL_SWAP(params_inv.input_min, params_inv.output_min); + PL_SWAP(params_inv.input_max, params_inv.output_max); + + int tested_pure_bpc = 0; + + // Generate example tone mapping curves, forward and inverse + for (int i = 0; i < pl_num_tone_map_functions; i++) { + const struct pl_tone_map_function *fun = pl_tone_map_functions[i]; + printf("Testing tone-mapping function %s\n", fun->name); + params.function = params_inv.function = fun; + pl_clock_t start = pl_clock_now(); + pl_tone_map_generate(lut, ¶ms); + pl_log_cpu_time(log, start, pl_clock_now(), "generating LUT"); + for (int j = 0; j < PL_ARRAY_SIZE(lut); j++) { + REQUIRE(isfinite(lut[j]) && !isnan(lut[j])); + if (j > 0) + REQUIRE_CMP(lut[j], >=, lut[j - 1], "f"); +#ifdef PRINT_LUTS + printf("%f, %f\n", j / (PL_ARRAY_SIZE(lut) - 1.0f), lut[j]); +#endif + } + + if (fun->map_inverse || !tested_pure_bpc++) { + start = pl_clock_now(); + pl_tone_map_generate(lut, ¶ms_inv); + pl_log_cpu_time(log, start, pl_clock_now(), "generating inverse LUT"); + for (int j = 0; j < PL_ARRAY_SIZE(lut); j++) { + REQUIRE(isfinite(lut[j]) && !isnan(lut[j])); + if (j > 0) + REQUIRE_CMP(lut[j], >=, lut[j - 1], "f"); +#ifdef PRINT_LUTS + printf("%f, %f\n", j / (PL_ARRAY_SIZE(lut) - 1.0f), lut[j]); +#endif + } + } + } + + // Test that `spline` is a no-op for 1:1 tone mapping + params.output_min = params.input_min; + params.output_max = params.input_max; + params.function = &pl_tone_map_spline; + pl_tone_map_generate(lut, ¶ms); + for (int j = 0; j < PL_ARRAY_SIZE(lut); j++) { + float x = j / (PL_ARRAY_SIZE(lut) - 1.0f); + x = PL_MIX(params.input_min, params.input_max, x); + REQUIRE_FEQ(x, lut[j], 1e-5); + } + + // Test some gamut mapping methods + for (int i = 0; i < pl_num_gamut_map_functions; i++) { + static const float min_rgb = 0.1f, max_rgb = PL_COLOR_SDR_WHITE; + struct pl_gamut_map_params gamut = { + .function = pl_gamut_map_functions[i], + .input_gamut = *pl_raw_primaries_get(PL_COLOR_PRIM_BT_2020), + .output_gamut = *pl_raw_primaries_get(PL_COLOR_PRIM_BT_709), + .min_luma = pl_hdr_rescale(PL_HDR_NITS, PL_HDR_PQ, min_rgb), + .max_luma = pl_hdr_rescale(PL_HDR_NITS, PL_HDR_PQ, max_rgb), + }; + + printf("Testing gamut-mapping function %s\n", gamut.function->name); + + // Require that black maps to black and white maps to white + float black[3] = { gamut.min_luma, 0.0f, 0.0f }; + float white[3] = { gamut.max_luma, 0.0f, 0.0f }; + pl_gamut_map_sample(black, &gamut); + pl_gamut_map_sample(white, &gamut); + REQUIRE_FEQ(black[0], gamut.min_luma, 1e-4); + REQUIRE_FEQ(black[1], 0.0f, 1e-4); + REQUIRE_FEQ(black[2], 0.0f, 1e-4); + if (gamut.function != &pl_gamut_map_darken) + REQUIRE_FEQ(white[0], gamut.max_luma, 1e-4); + REQUIRE_FEQ(white[1], 0.0f, 1e-4); + REQUIRE_FEQ(white[2], 0.0f, 1e-4); + } + + enum { LUT3D_SIZE = 65 }; // for benchmarking + struct pl_gamut_map_params perceptual = { + .function = &pl_gamut_map_perceptual, + .input_gamut = *pl_raw_primaries_get(PL_COLOR_PRIM_BT_2020), + .output_gamut = *pl_raw_primaries_get(PL_COLOR_PRIM_BT_709), + .max_luma = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, 1.0f), + .lut_size_I = LUT3D_SIZE, + .lut_size_C = LUT3D_SIZE, + .lut_size_h = LUT3D_SIZE, + .lut_stride = 3, + + // Set strength to maximum, because otherwise the saturation mapping + // code will not fully apply, invalidating the following test + .constants.perceptual_strength = 1.0f, + }; + + // Test that primaries round-trip for perceptual gamut mapping + const pl_matrix3x3 rgb2lms_src = pl_ipt_rgb2lms(&perceptual.input_gamut); + const pl_matrix3x3 rgb2lms_dst = pl_ipt_rgb2lms(&perceptual.output_gamut); + const pl_matrix3x3 lms2rgb_dst = pl_ipt_lms2rgb(&perceptual.output_gamut); + static const float refpoints[][3] = { + {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, + {0, 1, 1}, {1, 0, 1}, {1, 1, 0}, + }; + + for (int i = 0; i < PL_ARRAY_SIZE(refpoints); i++) { + float c[3] = { refpoints[i][0], refpoints[i][1], refpoints[i][2] }; + float ref[3] = { refpoints[i][0], refpoints[i][1], refpoints[i][2] }; + printf("Testing primary: RGB {%.0f %.0f %.0f}\n", c[0], c[1], c[2]); + pl_matrix3x3_apply(&rgb2lms_src, c); + c[0] = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, c[0]); + c[1] = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, c[1]); + c[2] = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, c[2]); + pl_matrix3x3_apply(&pl_ipt_lms2ipt, c); + printf("Before: ICh {%f %f %f}\n", + c[0], sqrtf(c[1]*c[1] + c[2]*c[2]), atan2f(c[2], c[1])); + pl_gamut_map_sample(c, &perceptual); + float rgb[3] = { c[0], c[1], c[2] }; + pl_matrix3x3_apply(&pl_ipt_ipt2lms, rgb); + rgb[0] = pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, rgb[0]); + rgb[1] = pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, rgb[1]); + rgb[2] = pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NORM, rgb[2]); + pl_matrix3x3_apply(&lms2rgb_dst, rgb); + const float hue = atan2f(c[2], c[1]); + printf("After: ICh {%f %f %f} = RGB {%f %f %f}\n", + c[0], sqrtf(c[1]*c[1] + c[2]*c[2]), hue, rgb[0], rgb[1], rgb[2]); + pl_matrix3x3_apply(&rgb2lms_dst, ref); + ref[0] = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, ref[0]); + ref[1] = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, ref[1]); + ref[2] = pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, ref[2]); + pl_matrix3x3_apply(&pl_ipt_lms2ipt, ref); + const float hue_ref = atan2f(ref[2], ref[1]); + printf("Should be: ICh {%f %f %f}\n", + ref[0], sqrtf(ref[1]*ref[1] + ref[2]*ref[2]), hue_ref); + REQUIRE_FEQ(hue, hue_ref, 3.0e-3); + } + + float *tmp = malloc(sizeof(float[LUT3D_SIZE][LUT3D_SIZE][LUT3D_SIZE][3])); + if (tmp) { + pl_clock_t start = pl_clock_now(); + pl_gamut_map_generate(tmp, &perceptual); + pl_log_cpu_time(log, start, pl_clock_now(), "generating 3DLUT"); + free(tmp); + } + + pl_log_destroy(&log); +} |