From 6099c0e24bb58d35cab694623498915c0084bbac Mon Sep 17 00:00:00 2001 From: Vitaly Prosyak Date: Sat, 27 Nov 2021 22:01:06 -0500 Subject: [PATCH] color-lcms: LCMS transform for color mapping Use 3D LUT for color mapping. For category CMLCMS_CATEGORY_INPUT_TO_BLEND use transform which has 3 profiles: input, output and light linearizing transfer function. For category CMLCMS_CATEGORY_INPUT_TO_OUTPUT use input and output profiles +VCGT. For category CMLCMS_CATEGORY_BLEND_TO_OUTPUT use output inverse EOTF + VCGT. Co-authored-by: Pekka Paalanen Signed-off-by: Vitaly Prosyak --- libweston/color-lcms/color-lcms.c | 49 +++--- libweston/color-lcms/color-lcms.h | 14 -- libweston/color-lcms/color-transform.c | 229 ++++++++++++++++++------- 3 files changed, 195 insertions(+), 97 deletions(-) diff --git a/libweston/color-lcms/color-lcms.c b/libweston/color-lcms/color-lcms.c index e50e0bfd..5e3b52e5 100644 --- a/libweston/color-lcms/color-lcms.c +++ b/libweston/color-lcms/color-lcms.c @@ -99,13 +99,7 @@ cmlcms_get_surface_color_transform(struct weston_color_manager *cm_base, struct weston_surface_color_transform *surf_xform) { struct weston_color_manager_lcms *cm = get_cmlcms(cm_base); - struct cmlcms_color_transform_search_param param = { - /* - * Assumes both content and output color spaces are sRGB SDR. - * This defines the blending space as optical sRGB SDR. - */ - .type = CMLCMS_TYPE_EOTF_sRGB, - }; + struct cmlcms_color_transform_search_param param = {}; struct cmlcms_color_transform *xform; setup_search_param(CMLCMS_CATEGORY_INPUT_TO_BLEND, surface, output, @@ -116,7 +110,15 @@ cmlcms_get_surface_color_transform(struct weston_color_manager *cm_base, return false; surf_xform->transform = &xform->base; - surf_xform->identity_pipeline = true; + /* + * When we introduce LCMS plug-in we can precisely answer this question + * by examining the color pipeline using precision parameters. For now + * we just compare if it is same pointer or not. + */ + if (xform->search_key.input_profile == xform->search_key.output_profile) + surf_xform->identity_pipeline = true; + else + surf_xform->identity_pipeline = false; return true; } @@ -127,13 +129,7 @@ cmlcms_get_output_color_transform(struct weston_color_manager *cm_base, struct weston_color_transform **xform_out) { struct weston_color_manager_lcms *cm = get_cmlcms(cm_base); - struct cmlcms_color_transform_search_param param = { - /* - * Assumes blending space is optical sRGB SDR and - * output color space is sRGB SDR. - */ - .type = CMLCMS_TYPE_EOTF_sRGB_INV, - }; + struct cmlcms_color_transform_search_param param = {}; struct cmlcms_color_transform *xform; setup_search_param(CMLCMS_CATEGORY_BLEND_TO_OUTPUT, NULL, output, @@ -152,16 +148,24 @@ cmlcms_get_sRGB_to_output_color_transform(struct weston_color_manager *cm_base, struct weston_output *output, struct weston_color_transform **xform_out) { - /* Assumes output color space is sRGB SDR */ struct weston_color_manager_lcms *cm = get_cmlcms(cm_base); - struct cmlcms_color_transform_search_param param = {}; + struct cmlcms_color_transform *xform; setup_search_param(CMLCMS_CATEGORY_INPUT_TO_OUTPUT, NULL, output, cm->sRGB_profile, ¶m); - - /* Identity transform */ - *xform_out = NULL; + /* + * Create a color transformation when output profile is not stock + * sRGB profile. + */ + if (param.output_profile != cm->sRGB_profile) { + xform = cmlcms_color_transform_get(cm, ¶m); + if (!xform) + return false; + *xform_out = &xform->base; + } else { + *xform_out = NULL; /* Identity transform */ + } return true; } @@ -172,10 +176,7 @@ cmlcms_get_sRGB_to_blend_color_transform(struct weston_color_manager *cm_base, struct weston_color_transform **xform_out) { struct weston_color_manager_lcms *cm = get_cmlcms(cm_base); - struct cmlcms_color_transform_search_param param = { - /* Assumes blending space is optical sRGB SDR */ - .type = CMLCMS_TYPE_EOTF_sRGB, - }; + struct cmlcms_color_transform_search_param param = {}; struct cmlcms_color_transform *xform; setup_search_param(CMLCMS_CATEGORY_INPUT_TO_BLEND, NULL, output, diff --git a/libweston/color-lcms/color-lcms.h b/libweston/color-lcms/color-lcms.h index ed6dce0a..b7291764 100644 --- a/libweston/color-lcms/color-lcms.h +++ b/libweston/color-lcms/color-lcms.h @@ -131,19 +131,8 @@ cmlcms_get_color_profile_from_icc(struct weston_color_manager *cm, void cmlcms_destroy_color_profile(struct weston_color_profile *cprof_base); -/* - * Perhaps a placeholder, until we get actual color spaces involved and - * see how this would work better. - */ -enum cmlcms_color_transform_type { - CMLCMS_TYPE_EOTF_sRGB = 0, - CMLCMS_TYPE_EOTF_sRGB_INV, - CMLCMS_TYPE__END, -}; struct cmlcms_color_transform_search_param { - enum cmlcms_color_transform_type type; - enum cmlcms_category category; struct cmlcms_color_profile *input_profile; struct cmlcms_color_profile *output_profile; @@ -158,9 +147,6 @@ struct cmlcms_color_transform { struct cmlcms_color_transform_search_param search_key; - /* for EOTF types It would be deprecated */ - cmsToneCurve *curve; - /** * 3D LUT color mapping part of the transformation, if needed. * For category CMLCMS_CATEGORY_INPUT_TO_OUTPUT it includes pre-curve and diff --git a/libweston/color-lcms/color-transform.c b/libweston/color-lcms/color-transform.c index ae53e287..c7b45639 100644 --- a/libweston/color-lcms/color-transform.c +++ b/libweston/color-lcms/color-transform.c @@ -33,12 +33,6 @@ #include "color-lcms.h" #include "shared/helpers.h" -/* Arguments to cmsBuildParametricToneCurve() */ -struct tone_curve_def { - cmsInt32Number cmstype; - cmsFloat64Number params[5]; -}; - /** * The method is used in linearization of an arbitrary color profile * when EOTF is retrieved we want to know a generic way to decide the number @@ -50,42 +44,87 @@ cmlcms_reasonable_1D_points(void) return 1024; } -/* - * LCMS uses the required number of 'params' based on 'cmstype', the parametric - * tone curve number. LCMS honors negative 'cmstype' as inverse function. - * These are LCMS built-in parametric tone curves. - */ -static const struct tone_curve_def predefined_eotf_curves[] = { - [CMLCMS_TYPE_EOTF_sRGB] = { - .cmstype = 4, - .params = { 2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045 }, - }, - [CMLCMS_TYPE_EOTF_sRGB_INV] = { - .cmstype = -4, - .params = { 2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045 }, - }, -}; +static unsigned int +cmlcms_reasonable_3D_points(void) +{ + return 33; +} static void -cmlcms_fill_in_tone_curve(struct weston_color_transform *xform_base, - float *values, unsigned len) +fill_in_curves(cmsToneCurve *curves[3], float *values, unsigned len) { - struct cmlcms_color_transform *xform = get_xform(xform_base); float *R_lut = values; float *G_lut = R_lut + len; float *B_lut = G_lut + len; unsigned i; - cmsFloat32Number x, y; - - assert(xform->curve != NULL); - assert(len > 1); + cmsFloat32Number x; for (i = 0; i < len; i++) { x = (double)i / (len - 1); - y = cmsEvalToneCurveFloat(xform->curve, x); - R_lut[i] = y; - G_lut[i] = y; - B_lut[i] = y; + R_lut[i] = cmsEvalToneCurveFloat(curves[0], x); + G_lut[i] = cmsEvalToneCurveFloat(curves[1], x); + B_lut[i] = cmsEvalToneCurveFloat(curves[2], x); + } +} + +static void +cmlcms_fill_in_pre_curve(struct weston_color_transform *xform_base, + float *values, unsigned len) +{ + struct cmlcms_color_transform *xform = get_xform(xform_base); + + assert(xform->search_key.category == CMLCMS_CATEGORY_BLEND_TO_OUTPUT); + + assert(len > 1); + + fill_in_curves(xform->search_key.output_profile->output_inv_eotf_vcgt, + values, len); +} + +/** + * Clamp value to [0.0, 1.0], except pass NaN through. + * + * This function is not intended for hiding NaN. + */ +static float +ensure_unorm(float v) +{ + if (v <= 0.0f) + return 0.0f; + if (v > 1.0f) + return 1.0f; + return v; +} + +static void +cmlcms_fill_in_3dlut(struct weston_color_transform *xform_base, + float *lut, unsigned int len) +{ + struct cmlcms_color_transform *xform = get_xform(xform_base); + float rgb_in[3]; + float rgb_out[3]; + unsigned int index; + unsigned int value_b, value_r, value_g; + float divider = len - 1; + + assert(xform->search_key.category == CMLCMS_CATEGORY_INPUT_TO_BLEND || + xform->search_key.category == CMLCMS_CATEGORY_INPUT_TO_OUTPUT); + + for (value_b = 0; value_b < len; value_b++) { + for (value_g = 0; value_g < len; value_g++) { + for (value_r = 0; value_r < len; value_r++) { + rgb_in[0] = (float)value_r / divider; + rgb_in[1] = (float)value_g / divider; + rgb_in[2] = (float)value_b / divider; + + cmsDoTransform(xform->cmap_3dlut, rgb_in, rgb_out, 1); + + index = 3 * (value_r + len * (value_g + len * value_b)); + lut[index ] = ensure_unorm(rgb_out[0]); + lut[index + 1] = ensure_unorm(rgb_out[1]); + lut[index + 2] = ensure_unorm(rgb_out[2]); + } + } } } @@ -93,57 +132,129 @@ void cmlcms_color_transform_destroy(struct cmlcms_color_transform *xform) { wl_list_remove(&xform->link); - if (xform->curve) - cmsFreeToneCurve(xform->curve); + + if (xform->cmap_3dlut) + cmsDeleteTransform(xform->cmap_3dlut); + + unref_cprof(xform->search_key.input_profile); + unref_cprof(xform->search_key.output_profile); free(xform); } +static bool +xform_set_cmap_3dlut(struct cmlcms_color_transform *xform, + cmsHPROFILE input_profile, + cmsHPROFILE output_profile, + cmsToneCurve *curves[3], + cmsUInt32Number intent) +{ + struct weston_color_manager_lcms *cm = get_cmlcms(xform->base.cm); + cmsHPROFILE arr_prof[3] = { input_profile, output_profile, NULL }; + int num_profiles = 2; + + if (curves[0]) { + arr_prof[2] = cmsCreateLinearizationDeviceLinkTHR(cm->lcms_ctx, + cmsSigRgbData, + curves); + if (!arr_prof[2]) + return false; + + num_profiles = 3; + } + + xform->cmap_3dlut = cmsCreateMultiprofileTransformTHR(cm->lcms_ctx, + arr_prof, + num_profiles, + TYPE_RGB_FLT, + TYPE_RGB_FLT, + intent, + 0); + if (!xform->cmap_3dlut) { + cmsCloseProfile(arr_prof[2]); + weston_log("color-lcms error: fail cmsCreateMultiprofileTransformTHR.\n"); + return false; + } + xform->base.mapping.type = WESTON_COLOR_MAPPING_TYPE_3D_LUT; + xform->base.mapping.u.lut3d.fill_in = cmlcms_fill_in_3dlut; + xform->base.mapping.u.lut3d.optimal_len = + cmlcms_reasonable_3D_points(); + cmsCloseProfile(arr_prof[2]); + + return true; +} + static struct cmlcms_color_transform * cmlcms_color_transform_create(struct weston_color_manager_lcms *cm, - const struct cmlcms_color_transform_search_param *param) + const struct cmlcms_color_transform_search_param *search_param) { + struct cmlcms_color_profile *input_profile = search_param->input_profile; + struct cmlcms_color_profile *output_profile = search_param->output_profile; struct cmlcms_color_transform *xform; - const struct tone_curve_def *tonedef; - - if (param->type < 0 || param->type >= CMLCMS_TYPE__END) { - weston_log("color-lcms error: bad color transform type in %s.\n", - __func__); - return NULL; - } - tonedef = &predefined_eotf_curves[param->type]; + bool ok = false; xform = zalloc(sizeof *xform); if (!xform) return NULL; - xform->curve = cmsBuildParametricToneCurve(cm->lcms_ctx, - tonedef->cmstype, - tonedef->params); - if (xform->curve == NULL) { - weston_log("color-lcms error: failed to build parametric tone curve.\n"); - free(xform); - return NULL; + weston_color_transform_init(&xform->base, &cm->base); + wl_list_init(&xform->link); + xform->search_key = *search_param; + xform->search_key.input_profile = ref_cprof(input_profile); + xform->search_key.output_profile = ref_cprof(output_profile); + + /* Ensure the linearization etc. have been extracted. */ + if (!output_profile->output_eotf[0]) { + if (!retrieve_eotf_and_output_inv_eotf(cm->lcms_ctx, + output_profile->profile, + output_profile->output_eotf, + output_profile->output_inv_eotf_vcgt, + output_profile->vcgt, + cmlcms_reasonable_1D_points())) + goto error; } - weston_color_transform_init(&xform->base, &cm->base); - xform->search_key = *param; + switch (search_param->category) { + case CMLCMS_CATEGORY_INPUT_TO_BLEND: + /* Use EOTF to linearize the result. */ + ok = xform_set_cmap_3dlut(xform, input_profile->profile, + output_profile->profile, + output_profile->output_eotf, + search_param->intent_output); + break; - xform->base.pre_curve.type = WESTON_COLOR_CURVE_TYPE_LUT_3x1D; - xform->base.pre_curve.u.lut_3x1d.fill_in = cmlcms_fill_in_tone_curve; - xform->base.pre_curve.u.lut_3x1d.optimal_len = 256; + case CMLCMS_CATEGORY_INPUT_TO_OUTPUT: + /* Apply also VCGT if it exists. */ + ok = xform_set_cmap_3dlut(xform, input_profile->profile, + output_profile->profile, + output_profile->vcgt, + search_param->intent_output); + break; - wl_list_insert(&cm->color_transform_list, &xform->link); + case CMLCMS_CATEGORY_BLEND_TO_OUTPUT: + xform->base.pre_curve.type = WESTON_COLOR_CURVE_TYPE_LUT_3x1D; + xform->base.pre_curve.u.lut_3x1d.fill_in = cmlcms_fill_in_pre_curve; + xform->base.pre_curve.u.lut_3x1d.optimal_len = + cmlcms_reasonable_1D_points(); + ok = true; + break; + } + if (!ok) + goto error; + + wl_list_insert(&cm->color_transform_list, &xform->link); return xform; + +error: + cmlcms_color_transform_destroy(xform); + weston_log("CM cmlcms_color_transform_create failed\n"); + return NULL; } static bool transform_matches_params(const struct cmlcms_color_transform *xform, const struct cmlcms_color_transform_search_param *param) { - if (xform->search_key.type != param->type) - return false; - if (xform->search_key.category != param->category) return false;