From 44c307956762dc16fcd67c955fb6a22890730830 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Fri, 20 May 2022 14:07:04 +0300 Subject: [PATCH] tests: add lcms-util with MPE curves This adds a new test helper library that depends on LittleCMS 2. For starters, the library implements conversion from enum transfer_fn to ICC multiProcessingElements compatible LittleCMS curve object. That conversion allows encoding transfer funtions in ICC files and LittleCMS pipelines with full float32 precision instead of forcing a conversion to a 1D LUT which for power-type curves is surprisingly imprecise. This also adds CI tests to make sure the conversion matches our hand-coded transfer functions. Signed-off-by: Pekka Paalanen --- tests/color_util.c | 2 +- tests/color_util.h | 3 + tests/lcms-util-test.c | 85 ++++++++++++++++++ tests/lcms_util.c | 200 +++++++++++++++++++++++++++++++++++++++++ tests/lcms_util.h | 36 ++++++++ tests/meson.build | 21 ++++- 6 files changed, 345 insertions(+), 2 deletions(-) create mode 100644 tests/lcms-util-test.c create mode 100644 tests/lcms_util.c create mode 100644 tests/lcms_util.h diff --git a/tests/color_util.c b/tests/color_util.c index 0e0c738a..4bbd4071 100644 --- a/tests/color_util.c +++ b/tests/color_util.c @@ -212,7 +212,7 @@ Power2_4_EOTF_inv(float o) return pow(o, 1./2.4); } -static float +float apply_tone_curve(enum transfer_fn fn, float r) { float ret = 0; diff --git a/tests/color_util.h b/tests/color_util.h index 27d7d3c8..e9d931cd 100644 --- a/tests/color_util.h +++ b/tests/color_util.h @@ -97,6 +97,9 @@ a8r8g8b8_to_float(uint32_t v); bool find_tone_curve_type(enum transfer_fn fn, int *type, double params[5]); +float +apply_tone_curve(enum transfer_fn fn, float r); + void process_pixel_using_pipeline(enum transfer_fn pre_curve, const struct lcmsMAT3 *mat, diff --git a/tests/lcms-util-test.c b/tests/lcms-util-test.c new file mode 100644 index 00000000..e7d8b69e --- /dev/null +++ b/tests/lcms-util-test.c @@ -0,0 +1,85 @@ +/* + * Copyright 2022 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include "weston-test-client-helper.h" +#include "color_util.h" +#include "lcms_util.h" + +static void +compare_pipeline_to_transfer_fn(cmsPipeline *pipeline, enum transfer_fn fn, + struct scalar_stat *stat) +{ + const unsigned N = 100000; + unsigned i; + + for (i = 0; i < N; i++) { + float x = (double)i / N; + float ref = apply_tone_curve(fn, x); + float y; + + cmsPipelineEvalFloat(&x, &y, pipeline); + scalar_stat_update(stat, y - ref, &(struct color_float){ .r = x }); + } +} + +static const enum transfer_fn build_MPE_curves_test_set[] = { + TRANSFER_FN_SRGB_EOTF, + TRANSFER_FN_SRGB_EOTF_INVERSE, + TRANSFER_FN_ADOBE_RGB_EOTF, + TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE, + TRANSFER_FN_POWER2_4_EOTF, + TRANSFER_FN_POWER2_4_EOTF_INVERSE, +}; + +TEST_P(build_MPE_curves, build_MPE_curves_test_set) +{ + const enum transfer_fn *fn = data; + const cmsContext ctx = 0; + cmsToneCurve *curve; + cmsStage *stage; + cmsPipeline *pipeline; + struct scalar_stat stat = {}; + + curve = build_MPE_curve(ctx, *fn); + stage = cmsStageAllocToneCurves(ctx, 1, &curve); + cmsFreeToneCurve(curve); + + pipeline = cmsPipelineAlloc(ctx, 1, 1); + cmsPipelineInsertStage(pipeline, cmsAT_END, stage); + + compare_pipeline_to_transfer_fn(pipeline, *fn, &stat); + testlog("Transfer function %s as a segmented curve element, error:\n", + transfer_fn_name(*fn)); + scalar_stat_print_float(&stat); + assert(fabs(stat.max) < 1e-7); + assert(fabs(stat.min) < 1e-7); + + cmsPipelineFree(pipeline); +} diff --git a/tests/lcms_util.c b/tests/lcms_util.c new file mode 100644 index 00000000..d6e05d64 --- /dev/null +++ b/tests/lcms_util.c @@ -0,0 +1,200 @@ +/* + * Copyright 2022 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "shared/helpers.h" +#include "color_util.h" +#include "lcms_util.h" + +/* + * MPE tone curves can only use LittleCMS parametric curve types 6-8 and not + * inverses. + * type 6: Y = (aX + b)^g + c; params [g, a, b, c] + * type 7: Y = a log(bX^g + c) + d; params [g, a, b, c, d] + * type 8: Y = a b^(cX + d) + e; params [a, b, c, d, e] + * Additionally, type 0 is sampled segment. + * + * cmsCurveSegment.x1 is the breakpoint stored in ICC files, except for the + * last segment. First segment always begins at -Inf, and last segment always + * ends at Inf. + */ + +static cmsToneCurve * +build_MPE_curve_sRGB(cmsContext ctx) +{ + cmsCurveSegment segments[] = { + { + /* Constant zero segment */ + .x0 = -HUGE_VAL, + .x1 = 0.0, + .Type = 6, + .Params = { 1.0, 0.0, 0.0, 0.0 }, + }, + { + /* Linear segment y = x / 12.92 */ + .x0 = 0.0, + .x1 = 0.04045, + .Type = 0, + .nGridPoints = 2, + .SampledPoints = (float[]){ 0.0, 0.04045 / 12.92 }, + }, + { + /* Power segment y = ((x + 0.055) / 1.055)^2.4 + * which is translated to + * y = (1/1.055 * x + 0.055 / 1.055)^2.4 + 0.0 + */ + .x0 = 0.04045, + .x1 = 1.0, + .Type = 6, + .Params = { 2.4, 1.0 / 1.055, 0.055 / 1.055, 0.0 }, + }, + { + /* Constant one segment */ + .x0 = 1.0, + .x1 = HUGE_VAL, + .Type = 6, + .Params = { 1.0, 0.0, 0.0, 1.0 }, + } + }; + + return cmsBuildSegmentedToneCurve(ctx, ARRAY_LENGTH(segments), segments); +} + +static cmsToneCurve * +build_MPE_curve_sRGB_inv(cmsContext ctx) +{ + cmsCurveSegment segments[] = { + { + /* Constant zero segment */ + .x0 = -HUGE_VAL, + .x1 = 0.0, + .Type = 6, + .Params = { 1.0, 0.0, 0.0, 0.0 }, + }, + { + /* Linear segment y = x * 12.92 */ + .x0 = 0.0, + .x1 = 0.04045 / 12.92, + .Type = 0, + .nGridPoints = 2, + .SampledPoints = (float[]){ 0.0, 0.04045 }, + }, + { + /* Power segment y = 1.055 * x^(1/2.4) - 0.055 + * which is translated to + * y = (1.055^2.4 * x + 0.0)^(1/2.4) - 0.055 + */ + .x0 = 0.04045 / 12.92, + .x1 = 1.0, + .Type = 6, + .Params = { 1.0 / 2.4, pow(1.055, 2.4), 0.0, -0.055 }, + }, + { + /* Constant one segment */ + .x0 = 1.0, + .x1 = HUGE_VAL, + .Type = 6, + .Params = { 1.0, 0.0, 0.0, 1.0 }, + } + }; + + return cmsBuildSegmentedToneCurve(ctx, ARRAY_LENGTH(segments), segments); +} + +static cmsToneCurve * +build_MPE_curve_power(cmsContext ctx, double exponent) +{ + cmsCurveSegment segments[] = { + { + /* Constant zero segment */ + .x0 = -HUGE_VAL, + .x1 = 0.0, + .Type = 6, + .Params = { 1.0, 0.0, 0.0, 0.0 }, + }, + { + /* Power segment y = x^exponent + * which is translated to + * y = (1.0 * x + 0.0)^exponent + 0.0 + */ + .x0 = 0.0, + .x1 = 1.0, + .Type = 6, + .Params = { exponent, 1.0, 0.0, 0.0 }, + }, + { + /* Constant one segment */ + .x0 = 1.0, + .x1 = HUGE_VAL, + .Type = 6, + .Params = { 1.0, 0.0, 0.0, 1.0 }, + } + }; + + return cmsBuildSegmentedToneCurve(ctx, ARRAY_LENGTH(segments), segments); +} + +cmsToneCurve * +build_MPE_curve(cmsContext ctx, enum transfer_fn fn) +{ + switch (fn) { + case TRANSFER_FN_ADOBE_RGB_EOTF: + return build_MPE_curve_power(ctx, 563.0 / 256.0); + case TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE: + return build_MPE_curve_power(ctx, 256.0 / 563.0); + case TRANSFER_FN_POWER2_4_EOTF: + return build_MPE_curve_power(ctx, 2.4); + case TRANSFER_FN_POWER2_4_EOTF_INVERSE: + return build_MPE_curve_power(ctx, 1.0 / 2.4); + case TRANSFER_FN_SRGB_EOTF: + return build_MPE_curve_sRGB(ctx); + case TRANSFER_FN_SRGB_EOTF_INVERSE: + return build_MPE_curve_sRGB_inv(ctx); + default: + assert(0 && "unimplemented MPE curve"); + } + + return NULL; +} + +cmsStage * +build_MPE_curve_stage(cmsContext context_id, enum transfer_fn fn) +{ + cmsToneCurve *c; + cmsStage *stage; + + c = build_MPE_curve(context_id, fn); + stage = cmsStageAllocToneCurves(context_id, 3, + (cmsToneCurve *[3]){ c, c, c }); + assert(stage); + cmsFreeToneCurve(c); + + return stage; +} diff --git a/tests/lcms_util.h b/tests/lcms_util.h new file mode 100644 index 00000000..278d56cd --- /dev/null +++ b/tests/lcms_util.h @@ -0,0 +1,36 @@ +/* + * Copyright 2022 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include + +#include "color_util.h" + +cmsToneCurve * +build_MPE_curve(cmsContext ctx, enum transfer_fn fn); + +cmsStage * +build_MPE_curve_stage(cmsContext context_id, enum transfer_fn fn); diff --git a/tests/meson.build b/tests/meson.build index 4dfd57e9..eb4f9cf2 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -60,6 +60,21 @@ dep_test_client = declare_dependency( ] ) +lib_lcms_util = static_library( + 'lib_lcms_util', + [ 'lcms_util.c' ], + include_directories: common_inc, + dependencies: [ + dep_lcms2, dep_libm + ], + build_by_default: false, + install: false, +) +dep_lcms_util = declare_dependency( + link_with: lib_lcms_util, + dependencies: [ dep_lcms2 ] +) + exe_plugin_test = shared_library( 'test-plugin', 'weston-test.c', @@ -243,11 +258,15 @@ if get_option('color-management-lcms') error('color-management-lcms tests require lcms2 which was not found. Or, you can use \'-Dcolor-management-lcms=false\'.') endif tests += [ + { 'name': 'color-metadata-parsing' }, { 'name': 'color-shaper-matrix', 'dep_objs': [ dep_libm, dep_lcms2 ] }, - { 'name': 'color-metadata-parsing' }, + { + 'name': 'lcms-util', + 'dep_objs': [ dep_lcms_util ] + }, ] endif