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>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user