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 <pekka.paalanen@collabora.com>
Signed-off-by: Vitaly Prosyak <vitaly.prosyak@amd.com>
dev
Vitaly Prosyak 3 years ago
parent c199aade3f
commit 6099c0e24b
  1. 49
      libweston/color-lcms/color-lcms.c
  2. 14
      libweston/color-lcms/color-lcms.h
  3. 229
      libweston/color-lcms/color-transform.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, &param);
/* 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, &param);
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,

@ -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

@ -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;

Loading…
Cancel
Save