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_surface_color_transform *surf_xform)
|
||||||
{
|
{
|
||||||
struct weston_color_manager_lcms *cm = get_cmlcms(cm_base);
|
struct weston_color_manager_lcms *cm = get_cmlcms(cm_base);
|
||||||
struct cmlcms_color_transform_search_param param = {
|
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 *xform;
|
struct cmlcms_color_transform *xform;
|
||||||
|
|
||||||
setup_search_param(CMLCMS_CATEGORY_INPUT_TO_BLEND, surface, output,
|
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;
|
return false;
|
||||||
|
|
||||||
surf_xform->transform = &xform->base;
|
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;
|
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_transform **xform_out)
|
||||||
{
|
{
|
||||||
struct weston_color_manager_lcms *cm = get_cmlcms(cm_base);
|
struct weston_color_manager_lcms *cm = get_cmlcms(cm_base);
|
||||||
struct cmlcms_color_transform_search_param param = {
|
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 *xform;
|
struct cmlcms_color_transform *xform;
|
||||||
|
|
||||||
setup_search_param(CMLCMS_CATEGORY_BLEND_TO_OUTPUT, NULL, output,
|
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_output *output,
|
||||||
struct weston_color_transform **xform_out)
|
struct weston_color_transform **xform_out)
|
||||||
{
|
{
|
||||||
/* Assumes output color space is sRGB SDR */
|
|
||||||
struct weston_color_manager_lcms *cm = get_cmlcms(cm_base);
|
struct weston_color_manager_lcms *cm = get_cmlcms(cm_base);
|
||||||
|
|
||||||
struct cmlcms_color_transform_search_param param = {};
|
struct cmlcms_color_transform_search_param param = {};
|
||||||
|
struct cmlcms_color_transform *xform;
|
||||||
|
|
||||||
setup_search_param(CMLCMS_CATEGORY_INPUT_TO_OUTPUT, NULL, output,
|
setup_search_param(CMLCMS_CATEGORY_INPUT_TO_OUTPUT, NULL, output,
|
||||||
cm->sRGB_profile, ¶m);
|
cm->sRGB_profile, ¶m);
|
||||||
|
/*
|
||||||
/* Identity transform */
|
* Create a color transformation when output profile is not stock
|
||||||
*xform_out = NULL;
|
* 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;
|
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_transform **xform_out)
|
||||||
{
|
{
|
||||||
struct weston_color_manager_lcms *cm = get_cmlcms(cm_base);
|
struct weston_color_manager_lcms *cm = get_cmlcms(cm_base);
|
||||||
struct cmlcms_color_transform_search_param param = {
|
struct cmlcms_color_transform_search_param param = {};
|
||||||
/* Assumes blending space is optical sRGB SDR */
|
|
||||||
.type = CMLCMS_TYPE_EOTF_sRGB,
|
|
||||||
};
|
|
||||||
struct cmlcms_color_transform *xform;
|
struct cmlcms_color_transform *xform;
|
||||||
|
|
||||||
setup_search_param(CMLCMS_CATEGORY_INPUT_TO_BLEND, NULL, output,
|
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
|
void
|
||||||
cmlcms_destroy_color_profile(struct weston_color_profile *cprof_base);
|
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 {
|
struct cmlcms_color_transform_search_param {
|
||||||
enum cmlcms_color_transform_type type;
|
|
||||||
|
|
||||||
enum cmlcms_category category;
|
enum cmlcms_category category;
|
||||||
struct cmlcms_color_profile *input_profile;
|
struct cmlcms_color_profile *input_profile;
|
||||||
struct cmlcms_color_profile *output_profile;
|
struct cmlcms_color_profile *output_profile;
|
||||||
@@ -158,9 +147,6 @@ struct cmlcms_color_transform {
|
|||||||
|
|
||||||
struct cmlcms_color_transform_search_param search_key;
|
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.
|
* 3D LUT color mapping part of the transformation, if needed.
|
||||||
* For category CMLCMS_CATEGORY_INPUT_TO_OUTPUT it includes pre-curve and
|
* For category CMLCMS_CATEGORY_INPUT_TO_OUTPUT it includes pre-curve and
|
||||||
|
|||||||
@@ -33,12 +33,6 @@
|
|||||||
#include "color-lcms.h"
|
#include "color-lcms.h"
|
||||||
#include "shared/helpers.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
|
* 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
|
* 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;
|
return 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static unsigned int
|
||||||
* LCMS uses the required number of 'params' based on 'cmstype', the parametric
|
cmlcms_reasonable_3D_points(void)
|
||||||
* tone curve number. LCMS honors negative 'cmstype' as inverse function.
|
{
|
||||||
* These are LCMS built-in parametric tone curves.
|
return 33;
|
||||||
*/
|
}
|
||||||
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 void
|
static void
|
||||||
cmlcms_fill_in_tone_curve(struct weston_color_transform *xform_base,
|
fill_in_curves(cmsToneCurve *curves[3], float *values, unsigned len)
|
||||||
float *values, unsigned len)
|
|
||||||
{
|
{
|
||||||
struct cmlcms_color_transform *xform = get_xform(xform_base);
|
|
||||||
float *R_lut = values;
|
float *R_lut = values;
|
||||||
float *G_lut = R_lut + len;
|
float *G_lut = R_lut + len;
|
||||||
float *B_lut = G_lut + len;
|
float *B_lut = G_lut + len;
|
||||||
unsigned i;
|
unsigned i;
|
||||||
cmsFloat32Number x, y;
|
cmsFloat32Number x;
|
||||||
|
|
||||||
assert(xform->curve != NULL);
|
|
||||||
assert(len > 1);
|
|
||||||
|
|
||||||
for (i = 0; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
x = (double)i / (len - 1);
|
x = (double)i / (len - 1);
|
||||||
y = cmsEvalToneCurveFloat(xform->curve, x);
|
R_lut[i] = cmsEvalToneCurveFloat(curves[0], x);
|
||||||
R_lut[i] = y;
|
G_lut[i] = cmsEvalToneCurveFloat(curves[1], x);
|
||||||
G_lut[i] = y;
|
B_lut[i] = cmsEvalToneCurveFloat(curves[2], x);
|
||||||
B_lut[i] = y;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
cmlcms_color_transform_destroy(struct cmlcms_color_transform *xform)
|
||||||
{
|
{
|
||||||
wl_list_remove(&xform->link);
|
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);
|
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 *
|
static struct cmlcms_color_transform *
|
||||||
cmlcms_color_transform_create(struct weston_color_manager_lcms *cm,
|
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;
|
struct cmlcms_color_transform *xform;
|
||||||
const struct tone_curve_def *tonedef;
|
bool ok = false;
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
xform = zalloc(sizeof *xform);
|
xform = zalloc(sizeof *xform);
|
||||||
if (!xform)
|
if (!xform)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
xform->curve = cmsBuildParametricToneCurve(cm->lcms_ctx,
|
weston_color_transform_init(&xform->base, &cm->base);
|
||||||
tonedef->cmstype,
|
wl_list_init(&xform->link);
|
||||||
tonedef->params);
|
xform->search_key = *search_param;
|
||||||
if (xform->curve == NULL) {
|
xform->search_key.input_profile = ref_cprof(input_profile);
|
||||||
weston_log("color-lcms error: failed to build parametric tone curve.\n");
|
xform->search_key.output_profile = ref_cprof(output_profile);
|
||||||
free(xform);
|
|
||||||
return NULL;
|
/* 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);
|
switch (search_param->category) {
|
||||||
xform->search_key = *param;
|
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;
|
case CMLCMS_CATEGORY_INPUT_TO_OUTPUT:
|
||||||
xform->base.pre_curve.u.lut_3x1d.fill_in = cmlcms_fill_in_tone_curve;
|
/* Apply also VCGT if it exists. */
|
||||||
xform->base.pre_curve.u.lut_3x1d.optimal_len = 256;
|
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);
|
wl_list_insert(&cm->color_transform_list, &xform->link);
|
||||||
|
|
||||||
return xform;
|
return xform;
|
||||||
|
|
||||||
|
error:
|
||||||
|
cmlcms_color_transform_destroy(xform);
|
||||||
|
weston_log("CM cmlcms_color_transform_create failed\n");
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
transform_matches_params(const struct cmlcms_color_transform *xform,
|
transform_matches_params(const struct cmlcms_color_transform *xform,
|
||||||
const struct cmlcms_color_transform_search_param *param)
|
const struct cmlcms_color_transform_search_param *param)
|
||||||
{
|
{
|
||||||
if (xform->search_key.type != param->type)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (xform->search_key.category != param->category)
|
if (xform->search_key.category != param->category)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user