Skip to content
Commits on Source (12)
......@@ -33,6 +33,57 @@
#include "color-lcms.h"
#include "shared/helpers.h"
static cmsUInt32Number
cmlcms_get_render_intent(enum cmlcms_category cat,
struct weston_surface *surface,
struct weston_output *output)
{
/*
* TODO: Take into account client provided content profile,
* output profile, and the category of the wanted color
* transformation.
*/
cmsUInt32Number intent = INTENT_RELATIVE_COLORIMETRIC;
return intent;
}
static void
setup_search_param(enum cmlcms_category cat,
struct weston_surface *surface,
struct weston_output *output,
struct cmlcms_color_profile *stock_sRGB_profile,
struct cmlcms_color_transform_search_param *search_param)
{
struct cmlcms_color_profile *input_profile = NULL;
struct cmlcms_color_profile *output_profile = NULL;
/*
* TODO: un-comment when declare color_profile in struct weston_surface
*/
/* if (surface && surface->color_profile)
input_profile = get_cprof(surface->color_profile); */
if (output && output->color_profile)
output_profile = get_cprof(output->color_profile);
search_param->category = cat;
switch (cat) {
case CMLCMS_CATEGORY_INPUT_TO_BLEND:
case CMLCMS_CATEGORY_INPUT_TO_OUTPUT:
search_param->input_profile =
input_profile ? input_profile : stock_sRGB_profile;
search_param->output_profile =
output_profile ? output_profile : stock_sRGB_profile;
break;
case CMLCMS_CATEGORY_BLEND_TO_OUTPUT:
search_param->output_profile =
output_profile ? output_profile : stock_sRGB_profile;
break;
}
search_param->intent_output = cmlcms_get_render_intent(cat, surface,
output);
}
static void
cmlcms_destroy_color_transform(struct weston_color_transform *xform_base)
{
......@@ -48,25 +99,26 @@ 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;
/* TODO: use output color profile */
if (output->color_profile)
return false;
setup_search_param(CMLCMS_CATEGORY_INPUT_TO_BLEND, surface, output,
cm->sRGB_profile, &param);
xform = cmlcms_color_transform_get(cm, &param);
if (!xform)
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;
}
......@@ -77,18 +129,11 @@ 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;
/* TODO: use output color profile */
if (output->color_profile)
return false;
setup_search_param(CMLCMS_CATEGORY_BLEND_TO_OUTPUT, NULL, output,
cm->sRGB_profile, &param);
xform = cmlcms_color_transform_get(cm, &param);
if (!xform)
......@@ -103,14 +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 */
/* TODO: use output color profile */
if (output->color_profile)
return false;
struct weston_color_manager_lcms *cm = get_cmlcms(cm_base);
struct cmlcms_color_transform_search_param param = {};
struct cmlcms_color_transform *xform;
/* Identity transform */
*xform_out = NULL;
setup_search_param(CMLCMS_CATEGORY_INPUT_TO_OUTPUT, NULL, output,
cm->sRGB_profile, &param);
/*
* 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;
}
......@@ -121,15 +176,11 @@ 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;
/* TODO: use output color profile */
if (output->color_profile)
return false;
setup_search_param(CMLCMS_CATEGORY_INPUT_TO_BLEND, NULL, output,
cm->sRGB_profile, &param);
xform = cmlcms_color_transform_get(cm, &param);
if (!xform)
......@@ -165,6 +216,10 @@ cmlcms_init(struct weston_color_manager *cm_base)
cmsSetLogErrorHandlerTHR(cm->lcms_ctx, lcms_error_logger);
if (!cmlcms_create_stock_profile(cm)) {
weston_log("color-lcms: error: cmlcms_create_stock_profile failed\n");
return false;
}
weston_log("LittleCMS %d initialized.\n", cmsGetEncodedCMMversion());
return true;
......@@ -175,6 +230,8 @@ cmlcms_destroy(struct weston_color_manager *cm_base)
{
struct weston_color_manager_lcms *cm = get_cmlcms(cm_base);
if (cm->sRGB_profile)
cmlcms_color_profile_destroy(cm->sRGB_profile);
assert(wl_list_empty(&cm->color_transform_list));
assert(wl_list_empty(&cm->color_profile_list));
......
......@@ -39,6 +39,7 @@ struct weston_color_manager_lcms {
struct wl_list color_transform_list; /* cmlcms_color_transform::link */
struct wl_list color_profile_list; /* cmlcms_color_profile::link */
struct cmlcms_color_profile *sRGB_profile; /* stock profile */
};
static inline struct weston_color_manager_lcms *
......@@ -59,6 +60,58 @@ struct cmlcms_color_profile {
cmsHPROFILE profile;
struct cmlcms_md5_sum md5sum;
/**
* If the profile does support being an output profile and it is used as an
* output then this field represents a light linearizing transfer function
* and it can not be null. The field is null only if the profile is not
* usable as an output profile. The field is set when cmlcms_color_profile
* is created.
*/
cmsToneCurve *output_eotf[3];
/**
* If the profile does support being an output profile and it is used as an
* output then this field represents a concatenation of inverse EOTF + VCGT,
* if the tag exists and it can not be null.
* VCGT is part of monitor calibration which means: even though we must
* apply VCGT in the compositor, we pretend that it happens inside the
* monitor. This is how the classic color management and ICC profiles work.
* The ICC profile (ignoring the VCGT tag) characterizes the output which
* is VCGT + monitor behavior. The field is null only if the profile is not
* usable as an output profile. The field is set when cmlcms_color_profile
* is created.
*/
cmsToneCurve *output_inv_eotf_vcgt[3];
/**
* VCGT tag cached from output profile, it could be null if not exist
*/
cmsToneCurve *vcgt[3];
};
/**
* Type of LCMS transforms
*/
enum cmlcms_category {
/**
* Uses combination of input profile with output profile, but
* without INV EOTF or with additional EOTF in the transform pipeline
* input→blend = input profile + output profile + output EOTF
*/
CMLCMS_CATEGORY_INPUT_TO_BLEND = 0,
/**
* Uses INV EOTF only concatenated with VCGT tag if present
* blend→output = output inverse EOTF + VCGT
*/
CMLCMS_CATEGORY_BLEND_TO_OUTPUT,
/**
* Transform uses input profile and output profile as is
* input→output = input profile + output profile + VCGT
*/
CMLCMS_CATEGORY_INPUT_TO_OUTPUT,
};
static inline struct cmlcms_color_profile *
......@@ -78,18 +131,12 @@ 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;
cmsUInt32Number intent_output; /* selected intent from output profile */
};
struct cmlcms_color_transform {
......@@ -100,8 +147,15 @@ struct cmlcms_color_transform {
struct cmlcms_color_transform_search_param search_key;
/* for EOTF types */
cmsToneCurve *curve;
/**
* 3D LUT color mapping part of the transformation, if needed.
* For category CMLCMS_CATEGORY_INPUT_TO_OUTPUT it includes pre-curve and
* post-curve.
* For category CMLCMS_CATEGORY_INPUT_TO_BLEND it includes pre-curve.
* For category CMLCMS_CATEGORY_BLEND_TO_OUTPUT and when identity it is
* not used
*/
cmsHTRANSFORM cmap_3dlut;
};
static inline struct cmlcms_color_transform *
......@@ -117,4 +171,27 @@ cmlcms_color_transform_get(struct weston_color_manager_lcms *cm,
void
cmlcms_color_transform_destroy(struct cmlcms_color_transform *xform);
struct cmlcms_color_profile *
ref_cprof(struct cmlcms_color_profile *cprof);
void
unref_cprof(struct cmlcms_color_profile *cprof);
bool
cmlcms_create_stock_profile(struct weston_color_manager_lcms *cm);
void
cmlcms_color_profile_destroy(struct cmlcms_color_profile *cprof);
bool
retrieve_eotf_and_output_inv_eotf(cmsContext lcms_ctx,
cmsHPROFILE hProfile,
cmsToneCurve *output_eotf[3],
cmsToneCurve *output_inv_eotf_vcgt[3],
cmsToneCurve *vcgt[3],
unsigned int num_points);
unsigned int
cmlcms_reasonable_1D_points(void);
#endif /* WESTON_COLOR_LCMS_H */
......@@ -36,6 +36,222 @@
#include "shared/helpers.h"
#include "shared/string-helpers.h"
struct xyz_arr_flt {
float v[3];
};
static double
xyz_dot_prod(const struct xyz_arr_flt a, const struct xyz_arr_flt b)
{
return (double)a.v[0] * b.v[0] +
(double)a.v[1] * b.v[1] +
(double)a.v[2] * b.v[2];
}
/**
* Graeme sketched a linearization method there:
* https://lists.freedesktop.org/archives/wayland-devel/2019-March/040171.html
*/
static bool
build_eotf_from_clut_profile(cmsContext lcms_ctx,
cmsHPROFILE profile,
cmsToneCurve *output_eotf[3],
int num_points)
{
int ch, point;
float *curve_array[3];
float *red = NULL;
cmsHPROFILE xyz_profile = NULL;
cmsHTRANSFORM transform_rgb_to_xyz = NULL;
bool ret = false;
const float div = num_points - 1;
red = malloc(sizeof(float) * num_points * 3);
if (!red)
goto release;
curve_array[0] = red;
curve_array[1] = red + num_points;
curve_array[2] = red + 2 * num_points;
xyz_profile = cmsCreateXYZProfile();
if (!xyz_profile)
goto release;
transform_rgb_to_xyz = cmsCreateTransform(profile, TYPE_RGB_FLT,
xyz_profile, TYPE_XYZ_FLT,
INTENT_ABSOLUTE_COLORIMETRIC,
0);
if (!transform_rgb_to_xyz)
goto release;
for (ch = 0; ch < 3; ch++) {
struct xyz_arr_flt prim_xyz_max;
struct xyz_arr_flt prim_xyz;
double xyz_square_magnitude;
float rgb[3] = { 0.0f, 0.0f, 0.0f };
rgb[ch] = 1.0f;
cmsDoTransform(transform_rgb_to_xyz, rgb, prim_xyz_max.v, 1);
/**
* Calculate xyz square of magnitude uses single channel 100% and
* others are zero.
*/
xyz_square_magnitude = xyz_dot_prod(prim_xyz_max, prim_xyz_max);
/**
* Build rgb tone curves
*/
for (point = 0; point < num_points; point++) {
rgb[ch] = (float)point / div;
cmsDoTransform(transform_rgb_to_xyz, rgb, prim_xyz.v, 1);
curve_array[ch][point] = xyz_dot_prod(prim_xyz,
prim_xyz_max) /
xyz_square_magnitude;
}
/**
* Create LCMS object of rgb tone curves and validate whether
* monotonic
*/
output_eotf[ch] = cmsBuildTabulatedToneCurveFloat(lcms_ctx,
num_points,
curve_array[ch]);
if (!output_eotf[ch])
goto release;
if (!cmsIsToneCurveMonotonic(output_eotf[ch])) {
/**
* It is interesting to see how this profile was created.
* We assume that such a curve could not be used for linearization
* of arbitrary profile.
*/
goto release;
}
}
ret = true;
release:
if (transform_rgb_to_xyz)
cmsDeleteTransform(transform_rgb_to_xyz);
if (xyz_profile)
cmsCloseProfile(xyz_profile);
free(red);
if (ret == false)
cmsFreeToneCurveTriple(output_eotf);
return ret;
}
/**
* Concatenation of two monotonic tone curves.
* LCMS API cmsJoinToneCurve does y = Y^-1(X(t)),
* but want to have y = Y^(X(t))
*/
static cmsToneCurve *
lcmsJoinToneCurve(cmsContext context_id, const cmsToneCurve *X,
const cmsToneCurve *Y, unsigned int resulting_points)
{
cmsToneCurve *out = NULL;
float t, x;
float *res = NULL;
unsigned int i;
res = zalloc(resulting_points * sizeof(float));
if (res == NULL)
goto error;
for (i = 0; i < resulting_points; i++) {
t = (float)i / (resulting_points - 1);
x = cmsEvalToneCurveFloat(X, t);
res[i] = cmsEvalToneCurveFloat(Y, x);
}
out = cmsBuildTabulatedToneCurveFloat(context_id, resulting_points, res);
error:
if (res != NULL)
free(res);
return out;
}
/**
* Extract EOTF from matrix-shaper and cLUT profiles,
* then invert and concatenate with 'vcgt' curve if it
* is available.
*/
bool
retrieve_eotf_and_output_inv_eotf(cmsContext lcms_ctx,
cmsHPROFILE hProfile,
cmsToneCurve *output_eotf[3],
cmsToneCurve *output_inv_eotf_vcgt[3],
cmsToneCurve *vcgt[3],
unsigned int num_points)
{
cmsToneCurve *curve = NULL;
const cmsToneCurve * const *vcgt_curves;
unsigned i;
cmsTagSignature tags[] = {
cmsSigRedTRCTag, cmsSigGreenTRCTag, cmsSigBlueTRCTag
};
if (cmsIsMatrixShaper(hProfile)) {
/**
* Optimization for matrix-shaper profile
* May have 1DLUT->3x3->3x3->1DLUT, 1DLUT->3x3->1DLUT
*/
for (i = 0 ; i < 3; i++) {
curve = cmsReadTag(hProfile, tags[i]);
if (!curve)
goto fail;
output_eotf[i] = cmsDupToneCurve(curve);
if (!output_eotf[i])
goto fail;
}
} else {
/**
* Linearization of cLUT profile may have 1DLUT->3DLUT->1DLUT,
* 1DLUT->3DLUT, 3DLUT
*/
if (!build_eotf_from_clut_profile(lcms_ctx, hProfile,
output_eotf, num_points))
goto fail;
}
/**
* If the caller looking for eotf only then return early.
* It could be used for input profile when identity case: EOTF + INV_EOTF
* in pipeline only.
*/
if (output_inv_eotf_vcgt == NULL)
return true;
for (i = 0; i < 3; i++) {
curve = cmsReverseToneCurve(output_eotf[i]);
if (!curve)
goto fail;
output_inv_eotf_vcgt[i] = curve;
}
vcgt_curves = cmsReadTag(hProfile, cmsSigVcgtTag);
if (vcgt_curves && vcgt_curves[0] && vcgt_curves[1] && vcgt_curves[2]) {
for (i = 0; i < 3; i++) {
curve = lcmsJoinToneCurve(lcms_ctx,
output_inv_eotf_vcgt[i],
vcgt_curves[i], num_points);
if (!curve)
goto fail;
cmsFreeToneCurve(output_inv_eotf_vcgt[i]);
output_inv_eotf_vcgt[i] = curve;
if (vcgt)
vcgt[i] = cmsDupToneCurve(vcgt_curves[i]);
}
}
return true;
fail:
cmsFreeToneCurveTriple(output_eotf);
cmsFreeToneCurveTriple(output_inv_eotf_vcgt);
return false;
}
/* FIXME: sync with spec! */
static bool
validate_icc_profile(cmsHPROFILE profile, char **errmsg)
......@@ -102,15 +318,37 @@ cmlcms_color_profile_create(struct weston_color_manager_lcms *cm,
return cprof;
}
static void
void
cmlcms_color_profile_destroy(struct cmlcms_color_profile *cprof)
{
wl_list_remove(&cprof->link);
cmsFreeToneCurveTriple(cprof->vcgt);
cmsFreeToneCurveTriple(cprof->output_eotf);
cmsFreeToneCurveTriple(cprof->output_inv_eotf_vcgt);
cmsCloseProfile(cprof->profile);
free(cprof->base.description);
free(cprof);
}
struct cmlcms_color_profile *
ref_cprof(struct cmlcms_color_profile *cprof)
{
if (!cprof)
return NULL;
weston_color_profile_ref(&cprof->base);
return cprof;
}
void
unref_cprof(struct cmlcms_color_profile *cprof)
{
if (!cprof)
return;
weston_color_profile_unref(&cprof->base);
}
static char *
make_icc_file_description(cmsHPROFILE profile,
const struct cmlcms_md5_sum *md5sum,
......@@ -131,6 +369,52 @@ make_icc_file_description(cmsHPROFILE profile,
return desc;
}
/**
*
* Build stock profile which available for clients unaware of color management
*/
bool
cmlcms_create_stock_profile(struct weston_color_manager_lcms *cm)
{
cmsHPROFILE profile;
struct cmlcms_md5_sum md5sum;
char *desc = NULL;
profile = cmsCreate_sRGBProfileTHR(cm->lcms_ctx);
if (!profile) {
weston_log("color-lcms: error: cmsCreate_sRGBProfileTHR failed\n");
return false;
}
if (!cmsMD5computeID(profile)) {
weston_log("Failed to compute MD5 for ICC profile\n");
goto err_close;
}
cmsGetHeaderProfileID(profile, md5sum.bytes);
desc = make_icc_file_description(profile, &md5sum, "sRGB stock");
if (!desc)
goto err_close;
cm->sRGB_profile = cmlcms_color_profile_create(cm, profile, desc, NULL);
if (!cm->sRGB_profile)
goto err_close;
if (!retrieve_eotf_and_output_inv_eotf(cm->lcms_ctx,
cm->sRGB_profile->profile,
cm->sRGB_profile->output_eotf,
cm->sRGB_profile->output_inv_eotf_vcgt,
cm->sRGB_profile->vcgt,
cmlcms_reasonable_1D_points()))
goto err_close;
return true;
err_close:
free(desc);
cmsCloseProfile(profile);
return false;
}
bool
cmlcms_get_color_profile_from_icc(struct weston_color_manager *cm_base,
const void *icc_data,
......
......@@ -33,48 +33,98 @@
#include "color-lcms.h"
#include "shared/helpers.h"
/* Arguments to cmsBuildParametricToneCurve() */
struct tone_curve_def {
cmsInt32Number cmstype;
cmsFloat64Number params[5];
};
/*
* 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.
/**
* 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
* of points
*/
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 },
},
};
unsigned int
cmlcms_reasonable_1D_points(void)
{
return 1024;
}
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]);
}
}
}
}
......@@ -82,55 +132,135 @@ 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)
if (xform->search_key.category != param->category)
return false;
if (xform->search_key.intent_output != param->intent_output ||
xform->search_key.output_profile != param->output_profile ||
xform->search_key.input_profile != param->input_profile)
return false;
return true;
......
/*
* Copyright 2021 Collabora, Ltd.
* Copyright 2021 Advanced Micro Devices, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
......@@ -98,6 +99,79 @@ struct weston_color_curve {
} u;
};
/** Type or formula for a color mapping */
enum weston_color_mapping_type {
/** Identity function, no-op */
WESTON_COLOR_MAPPING_TYPE_IDENTITY = 0,
/** 3D-dimensional look-up table */
WESTON_COLOR_MAPPING_TYPE_3D_LUT,
};
/**
* A three-dimensional look-up table
*
* A 3D LUT is a three-dimensional array where each element is an RGB triplet.
* A 3D LUT is usually an approximation of some arbitrary color mapping
* function that cannot be represented in any simpler form. The array contains
* samples from the approximated function, and values between samples are
* estimated by interpolation. The array is accessed with three indices, one
* for each input dimension (color channel).
*
* Color channel values in the range [0.0, 1.0] are mapped linearly to
* 3D LUT indices such that 0.0 maps exactly to the first element and 1.0 maps
* exactly to the last element in each dimension.
*
* This object represents a 3D LUT and offers an interface for realizing it
* as a data array with a custom size.
*/
struct weston_color_mapping_3dlut {
/**
* Create a 3D LUT data array
*
* \param xform This color transformation object.
* \param values Memory to hold the resulting data array.
* \param len The number of elements in each dimension.
*
* The array \c values must be at least 3 * len * len * len elements
* in size.
*
* Given the red index ri, green index gi and blue index bi, the
* corresponding array element index
*
* i = 3 * (len * len * bi + len * gi + ri) + c
*
* where
*
* c = 0 for red output value,
* c = 1 for green output value, and
* c = 2 for blue output value
*/
void
(*fill_in)(struct weston_color_transform *xform,
float *values, unsigned len);
/** Optimal 3D LUT size along each dimension */
unsigned optimal_len;
};
/**
* Color mapping function
*
* This object can represent a 3D LUT to do a color space conversion
*
*/
struct weston_color_mapping {
/** Which member of 'u' defines the color mapping type */
enum weston_color_mapping_type type;
/** Parameters for the color mapping function */
union {
/* identity: no parameters */
struct weston_color_mapping_3dlut lut3d;
} u;
};
/**
* Describes a color transformation formula
*
......@@ -124,7 +198,7 @@ struct weston_color_transform {
struct weston_color_curve pre_curve;
/** Step 3: color mapping */
/* TBD: e.g. a 3D LUT or a matrix */
struct weston_color_mapping mapping;
/** Step 4: color curve after color mapping */
/* struct weston_color_curve post_curve; */
......
......@@ -2,6 +2,7 @@
* Copyright 2012 Intel Corporation
* Copyright 2015,2019,2021 Collabora, Ltd.
* Copyright 2016 NVIDIA Corporation
* Copyright 2021 Advanced Micro Devices, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
......@@ -46,10 +47,18 @@
#define SHADER_COLOR_CURVE_IDENTITY 0
#define SHADER_COLOR_CURVE_LUT_3x1D 1
/* enum gl_shader_color_mapping */
#define SHADER_COLOR_MAPPING_IDENTITY 0
#define SHADER_COLOR_MAPPING_3DLUT 1
#if DEF_VARIANT == SHADER_VARIANT_EXTERNAL
#extension GL_OES_EGL_image_external : require
#endif
#if DEF_COLOR_MAPPING == SHADER_COLOR_MAPPING_3DLUT
#extension GL_OES_texture_3D : require
#endif
#ifdef GL_FRAGMENT_PRECISION_HIGH
#define HIGHPRECISION highp
#else
......@@ -66,6 +75,7 @@ compile_const int c_variant = DEF_VARIANT;
compile_const bool c_input_is_premult = DEF_INPUT_IS_PREMULT;
compile_const bool c_green_tint = DEF_GREEN_TINT;
compile_const int c_color_pre_curve = DEF_COLOR_PRE_CURVE;
compile_const int c_color_mapping = DEF_COLOR_MAPPING;
vec4
yuva2rgba(vec4 yuva)
......@@ -110,6 +120,11 @@ uniform vec4 unicolor;
uniform HIGHPRECISION sampler2D color_pre_curve_lut_2d;
uniform HIGHPRECISION vec2 color_pre_curve_lut_scale_offset;
#if DEF_COLOR_MAPPING == SHADER_COLOR_MAPPING_3DLUT
uniform HIGHPRECISION sampler3D color_mapping_lut_3d;
uniform HIGHPRECISION vec2 color_mapping_lut_scale_offset;
#endif
vec4
sample_input_texture()
{
......@@ -168,6 +183,12 @@ lut_texcoord(float x, vec2 scale_offset)
return x * scale_offset.s + scale_offset.t;
}
vec3
lut_texcoord(vec3 pos, vec2 scale_offset)
{
return pos * scale_offset.s + scale_offset.t;
}
/*
* Sample a 1D LUT which is a single row of a 2D texture. The 2D texture has
* four rows so that the centers of texels have precise y-coordinates.
......@@ -199,6 +220,28 @@ color_pre_curve(vec3 color)
}
}
vec3
sample_color_mapping_lut_3d(vec3 color)
{
vec3 pos, ret = vec3(0.0, 0.0, 0.0);
#if DEF_COLOR_MAPPING == SHADER_COLOR_MAPPING_3DLUT
pos = lut_texcoord(color, color_mapping_lut_scale_offset);
ret = texture3D(color_mapping_lut_3d, pos).rgb;
#endif
return ret;
}
vec3
color_mapping(vec3 color)
{
if (c_color_mapping == SHADER_COLOR_MAPPING_IDENTITY)
return color;
else if (c_color_mapping == SHADER_COLOR_MAPPING_3DLUT)
return sample_color_mapping_lut_3d(color);
else /* Never reached, bad c_color_mapping. */
return vec3(1.0, 0.3, 1.0);
}
vec4
color_pipeline(vec4 color)
{
......@@ -206,6 +249,7 @@ color_pipeline(vec4 color)
color.a *= alpha;
color.rgb = color_pre_curve(color.rgb);
color.rgb = color_mapping(color.rgb);
return color;
}
......
......@@ -2,6 +2,7 @@
* Copyright © 2019 Collabora, Ltd.
* Copyright © 2019 Harish Krupo
* Copyright © 2019 Intel Corporation
* Copyright 2021 Advanced Micro Devices, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
......@@ -56,6 +57,12 @@ enum gl_shader_color_curve {
SHADER_COLOR_CURVE_LUT_3x1D,
};
/* Keep the following in sync with fragment.glsl. */
enum gl_shader_color_mapping {
SHADER_COLOR_MAPPING_IDENTITY = 0,
SHADER_COLOR_MAPPING_3DLUT,
};
/** GL shader requirements key
*
* This structure is used as a binary blob key for building and searching
......@@ -70,13 +77,14 @@ struct gl_shader_requirements
unsigned variant:4; /* enum gl_shader_texture_variant */
bool input_is_premult:1;
bool green_tint:1;
unsigned color_pre_curve:1; /* enum gl_shader_color_curve */
unsigned color_pre_curve:1; /* enum gl_shader_color_curve */
unsigned color_mapping:1; /* enum gl_shader_color_mapping */
/*
* The total size of all bitfields plus pad_bits_ must fill up exactly
* how many bytes the compiler allocates for them together.
*/
unsigned pad_bits_:25;
unsigned pad_bits_:24;
};
static_assert(sizeof(struct gl_shader_requirements) ==
4 /* total bitfield size in bytes */,
......@@ -96,6 +104,12 @@ struct gl_shader_config {
GLuint input_tex[GL_SHADER_INPUT_TEX_MAX];
GLuint color_pre_curve_lut_tex;
GLfloat color_pre_curve_lut_scale_offset[2];
union {
struct {
GLuint tex;
GLfloat scale_offset[2];
} lut3d;
} color_mapping;
};
struct gl_renderer {
......
......@@ -3977,7 +3977,8 @@ gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface)
if (gr->gl_version >= gr_gl_version(3, 0) &&
weston_check_egl_extension(extensions, "GL_OES_texture_float_linear") &&
weston_check_egl_extension(extensions, "GL_EXT_color_buffer_half_float")) {
weston_check_egl_extension(extensions, "GL_EXT_color_buffer_half_float") &&
weston_check_egl_extension(extensions, "GL_OES_texture_3D")) {
gr->gl_supports_color_transforms = true;
}
......
/*
* Copyright 2021 Collabora, Ltd.
* Copyright 2021 Advanced Micro Devices, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
......@@ -44,11 +45,22 @@ struct gl_renderer_color_curve {
float offset;
};
struct gl_renderer_color_mapping {
enum gl_shader_color_mapping type;
union {
struct {
GLuint tex3d;
float scale;
float offset;
} lut3d;
};
} ;
struct gl_renderer_color_transform {
struct weston_color_transform *owner;
struct wl_listener destroy_listener;
struct gl_renderer_color_curve pre_curve;
struct gl_renderer_color_mapping mapping;
};
static void
......@@ -58,10 +70,19 @@ gl_renderer_color_curve_fini(struct gl_renderer_color_curve *gl_curve)
glDeleteTextures(1, &gl_curve->tex);
}
static void
gl_renderer_color_mapping_fini(struct gl_renderer_color_mapping *gl_mapping)
{
if (gl_mapping->type == SHADER_COLOR_MAPPING_3DLUT &&
gl_mapping->lut3d.tex3d)
glDeleteTextures(1, &gl_mapping->lut3d.tex3d);
}
static void
gl_renderer_color_transform_destroy(struct gl_renderer_color_transform *gl_xform)
{
gl_renderer_color_curve_fini(&gl_xform->pre_curve);
gl_renderer_color_mapping_fini(&gl_xform->mapping);
wl_list_remove(&gl_xform->destroy_listener.link);
free(gl_xform);
}
......@@ -152,6 +173,47 @@ gl_color_curve_lut_3x1d(struct gl_renderer_color_curve *gl_curve,
return true;
}
static bool
gl_3d_lut(struct gl_renderer_color_transform *gl_xform,
struct weston_color_transform *xform)
{
GLuint tex3d;
float *lut;
const unsigned dim_size = xform->mapping.u.lut3d.optimal_len;
lut = calloc(3 * dim_size * dim_size * dim_size, sizeof *lut);
if (!lut)
return false;
xform->mapping.u.lut3d.fill_in(xform, lut, dim_size);
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &tex3d);
glBindTexture(GL_TEXTURE_3D, tex3d);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0);
glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB32F, dim_size, dim_size, dim_size, 0,
GL_RGB, GL_FLOAT, lut);
glBindTexture(GL_TEXTURE_3D, 0);
gl_xform->mapping.type = SHADER_COLOR_MAPPING_3DLUT;
gl_xform->mapping.lut3d.tex3d = tex3d;
gl_xform->mapping.lut3d.scale = (float)(dim_size - 1) / dim_size;
gl_xform->mapping.lut3d.offset = 0.5f / dim_size;
free(lut);
return true;
}
static const struct gl_renderer_color_transform *
gl_renderer_color_transform_from(struct weston_color_transform *xform)
{
......@@ -160,6 +222,7 @@ gl_renderer_color_transform_from(struct weston_color_transform *xform)
.pre_curve.tex = 0,
.pre_curve.scale = 0.0f,
.pre_curve.offset = 0.0f,
.mapping.type = SHADER_COLOR_MAPPING_IDENTITY,
};
struct gl_renderer_color_transform *gl_xform;
bool ok = false;
......@@ -190,6 +253,19 @@ gl_renderer_color_transform_from(struct weston_color_transform *xform)
break;
}
if (!ok) {
gl_renderer_color_transform_destroy(gl_xform);
return NULL;
}
switch (xform->mapping.type) {
case WESTON_COLOR_MAPPING_TYPE_IDENTITY:
gl_xform->mapping = no_op_gl_xform.mapping;
ok = true;
break;
case WESTON_COLOR_MAPPING_TYPE_3D_LUT:
ok = gl_3d_lut(gl_xform, xform);
break;
}
if (!ok) {
gl_renderer_color_transform_destroy(gl_xform);
return NULL;
......@@ -203,6 +279,7 @@ gl_shader_config_set_color_transform(struct gl_shader_config *sconf,
struct weston_color_transform *xform)
{
const struct gl_renderer_color_transform *gl_xform;
bool ret = false;
gl_xform = gl_renderer_color_transform_from(xform);
if (!gl_xform)
......@@ -213,5 +290,22 @@ gl_shader_config_set_color_transform(struct gl_shader_config *sconf,
sconf->color_pre_curve_lut_scale_offset[0] = gl_xform->pre_curve.scale;
sconf->color_pre_curve_lut_scale_offset[1] = gl_xform->pre_curve.offset;
return true;
sconf->req.color_mapping = gl_xform->mapping.type;
switch (gl_xform->mapping.type) {
case SHADER_COLOR_MAPPING_3DLUT:
sconf->color_mapping.lut3d.tex = gl_xform->mapping.lut3d.tex3d;
sconf->color_mapping.lut3d.scale_offset[0] =
gl_xform->mapping.lut3d.scale;
sconf->color_mapping.lut3d.scale_offset[1] =
gl_xform->mapping.lut3d.offset;
assert(sconf->color_mapping.lut3d.scale_offset[0] > 0.0);
assert(sconf->color_mapping.lut3d.scale_offset[1] > 0.0);
ret = true;
break;
case SHADER_COLOR_MAPPING_IDENTITY:
ret = true;
break;
}
return ret;
}
......@@ -4,6 +4,7 @@
* Copyright 2016 NVIDIA Corporation
* Copyright 2019 Harish Krupo
* Copyright 2019 Intel Corporation
* Copyright 2021 Advanced Micro Devices, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
......@@ -60,6 +61,12 @@ struct gl_shader {
GLint color_uniform;
GLint color_pre_curve_lut_2d_uniform;
GLint color_pre_curve_lut_scale_offset_uniform;
union {
struct {
GLint tex_uniform;
GLint scale_offset_uniform;
} lut3d;
} color_mapping;
struct wl_list link; /* gl_renderer::shader_list */
struct timespec last_used;
};
......@@ -97,6 +104,19 @@ gl_shader_color_curve_to_string(enum gl_shader_color_curve kind)
return "!?!?"; /* never reached */
}
static const char *
gl_shader_color_mapping_to_string(enum gl_shader_color_mapping kind)
{
switch (kind) {
#define CASERET(x) case x: return #x;
CASERET(SHADER_COLOR_MAPPING_IDENTITY)
CASERET(SHADER_COLOR_MAPPING_3DLUT)
#undef CASERET
}
return "!?!?"; /* never reached */
}
static void
dump_program_with_line_numbers(int count, const char **sources)
{
......@@ -162,9 +182,10 @@ create_shader_description_string(const struct gl_shader_requirements *req)
int size;
char *str;
size = asprintf(&str, "%s %s %cinput_is_premult %cgreen",
size = asprintf(&str, "%s %s %s %cinput_is_premult %cgreen",
gl_shader_texture_variant_to_string(req->variant),
gl_shader_color_curve_to_string(req->color_pre_curve),
gl_shader_color_mapping_to_string(req->color_mapping),
req->input_is_premult ? '+' : '-',
req->green_tint ? '+' : '-');
if (size < 0)
......@@ -182,10 +203,12 @@ create_shader_config_string(const struct gl_shader_requirements *req)
"#define DEF_GREEN_TINT %s\n"
"#define DEF_INPUT_IS_PREMULT %s\n"
"#define DEF_COLOR_PRE_CURVE %s\n"
"#define DEF_COLOR_MAPPING %s\n"
"#define DEF_VARIANT %s\n",
req->green_tint ? "true" : "false",
req->input_is_premult ? "true" : "false",
gl_shader_color_curve_to_string(req->color_pre_curve),
gl_shader_color_mapping_to_string(req->color_mapping),
gl_shader_texture_variant_to_string(req->variant));
if (size < 0)
return NULL;
......@@ -268,6 +291,16 @@ gl_shader_create(struct gl_renderer *gr,
shader->color_pre_curve_lut_scale_offset_uniform =
glGetUniformLocation(shader->program, "color_pre_curve_lut_scale_offset");
switch(requirements->color_mapping) {
case SHADER_COLOR_MAPPING_3DLUT:
shader->color_mapping.lut3d.tex_uniform =
glGetUniformLocation(shader->program, "color_mapping_lut_3d");
shader->color_mapping.lut3d.scale_offset_uniform =
glGetUniformLocation(shader->program,"color_mapping_lut_scale_offset");
break;
case SHADER_COLOR_MAPPING_IDENTITY:
break;
}
free(conf);
wl_list_insert(&gr->shader_list, &shader->link);
......@@ -376,6 +409,7 @@ gl_renderer_create_fallback_shader(struct gl_renderer *gr)
.variant = SHADER_VARIANT_SOLID,
.input_is_premult = true,
.color_pre_curve = SHADER_COLOR_CURVE_IDENTITY,
.color_mapping = SHADER_COLOR_MAPPING_IDENTITY,
};
struct gl_shader *shader;
......@@ -497,9 +531,8 @@ gl_shader_load_config(struct gl_shader *shader,
glTexParameteri(in_tgt, GL_TEXTURE_MAG_FILTER, in_filter);
}
/* Fixed texture unit for color_pre_curve LUT */
/* Fixed texture unit for color_pre_curve LUT if it is available */
i = GL_SHADER_INPUT_TEX_MAX;
glActiveTexture(GL_TEXTURE0 + i);
switch (sconf->req.color_pre_curve) {
case SHADER_COLOR_CURVE_IDENTITY:
assert(sconf->color_pre_curve_lut_tex == 0);
......@@ -508,13 +541,32 @@ gl_shader_load_config(struct gl_shader *shader,
assert(sconf->color_pre_curve_lut_tex != 0);
assert(shader->color_pre_curve_lut_2d_uniform != -1);
assert(shader->color_pre_curve_lut_scale_offset_uniform != -1);
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, sconf->color_pre_curve_lut_tex);
glUniform1i(shader->color_pre_curve_lut_2d_uniform, i);
i++;
glUniform2fv(shader->color_pre_curve_lut_scale_offset_uniform,
1, sconf->color_pre_curve_lut_scale_offset);
break;
}
switch (sconf->req.color_mapping) {
case SHADER_COLOR_MAPPING_IDENTITY:
break;
case SHADER_COLOR_MAPPING_3DLUT:
assert(shader->color_mapping.lut3d.tex_uniform != -1);
assert(sconf->color_mapping.lut3d.tex != 0);
assert(shader->color_mapping.lut3d.scale_offset_uniform != -1);
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_3D, sconf->color_mapping.lut3d.tex);
glUniform1i(shader->color_mapping.lut3d.tex_uniform, i);
glUniform2fv(shader->color_mapping.lut3d.scale_offset_uniform,
1, sconf->color_mapping.lut3d.scale_offset);
break;
default:
assert(false);
break;
}
}
bool
......
/*
* Copyright 2021 Advanced Micro Devices, Inc.
*
* 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 <math.h>
#include "weston-test-client-helper.h"
#include "weston-test-fixture-compositor.h"
#include "color_util.h"
#include <string.h>
#include <lcms2.h>
#include <linux/limits.h>
struct lcms_pipeline {
/**
* Color space name
*/
const char *color_space;
/**
* Chromaticities for output profile
*/
cmsCIExyYTRIPLE prim_output;
/**
* tone curve enum
*/
enum transfer_fn pre_fn;
/**
* Transform matrix from sRGB to target chromaticities in prim_output
*/
struct lcmsMAT3 mat;
/**
* tone curve enum
*/
enum transfer_fn post_fn;
/**
* 2/255 or 3/255 maximum possible error, where 255 is 8 bit max value
*/
int tolerance;
};
static const int WINDOW_WIDTH = 256;
static const int WINDOW_HEIGHT = 24;
static cmsCIExyY wp_d65 = { 0.31271, 0.32902, 1.0 };
struct setup_args {
struct fixture_metadata meta;
struct lcms_pipeline pipeline;
};
/*
* Using currently destination gamut bigger than source.
* Using https://www.colour-science.org/ we can extract conversion matrix:
* import colour
* colour.matrix_RGB_to_RGB(colour.RGB_COLOURSPACES['sRGB'], colour.RGB_COLOURSPACES['Adobe RGB (1998)'], None)
* colour.matrix_RGB_to_RGB(colour.RGB_COLOURSPACES['sRGB'], colour.RGB_COLOURSPACES['ITU-R BT.2020'], None)
*/
const struct setup_args arr_setup[] = {
{
.meta.name = "sRGB->sRGB unity",
.pipeline = {
.color_space = "sRGB",
.prim_output = {
.Red = { 0.640, 0.330, 1.0 },
.Green = { 0.300, 0.600, 1.0 },
.Blue = { 0.150, 0.060, 1.0 }
},
.pre_fn = TRANSFER_FN_SRGB_EOTF,
.mat = LCMSMAT3(1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0),
.post_fn = TRANSFER_FN_SRGB_EOTF_INVERSE,
.tolerance = 0
}
},
{
.meta.name = "sRGB->adobeRGB",
.pipeline = {
.color_space = "adobeRGB",
.prim_output = {
.Red = { 0.640, 0.330, 1.0 },
.Green = { 0.210, 0.710, 1.0 },
.Blue = { 0.150, 0.060, 1.0 }
},
.pre_fn = TRANSFER_FN_SRGB_EOTF,
.mat = LCMSMAT3(0.715119, 0.284881, 0.0,
0.0, 1.0, 0.0,
0.0, 0.041169, 0.958831),
.post_fn = TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE,
.tolerance = 1
/*
* Tolerance depends more on the 1D LUT used for the
* inv EOTF than the tested 3D LUT size:
* 9x9x9, 17x17x17, 33x33x33, 127x127x127
*/
}
},
{
.meta.name = "sRGB->bt2020",
.pipeline = {
.color_space = "bt2020",
.prim_output = {
.Red = { 0.708, 0.292, 1.0 },
.Green = { 0.170, 0.797, 1.0 },
.Blue = { 0.131, 0.046, 1.0 }
},
.pre_fn = TRANSFER_FN_SRGB_EOTF,
.mat = LCMSMAT3(0.627402, 0.329292, 0.043306,
0.069095, 0.919544, 0.011360,
0.016394, 0.088028, 0.895578),
/* this is equivalent to BT.1886 with zero black level */
.post_fn = TRANSFER_FN_POWER2_4_EOTF_INVERSE,
.tolerance = 5
/*
* TODO: when we add power-law in the curve enumeration
* in GL-renderer, then we should fix the tolerance
* as the error should reduce a lot.
*/
}
}
};
struct image_header {
int width;
int height;
int stride;
int depth;
pixman_format_code_t pix_format;
uint32_t *data;
};
static void
get_image_prop(struct buffer *buf, struct image_header *header)
{
header->width = pixman_image_get_width(buf->image);
header->height = pixman_image_get_height(buf->image);
header->stride = pixman_image_get_stride(buf->image);
header->depth = pixman_image_get_depth(buf->image);
header->pix_format = pixman_image_get_format (buf->image);
header->data = pixman_image_get_data(buf->image);
}
static void
gen_ramp_rgb(const struct image_header *header, int bitwidth, int width_bar)
{
static const int hue[][3] = {
{ 1, 1, 1 }, /* White */
{ 1, 1, 0 }, /* Yellow */
{ 0, 1, 1 }, /* Cyan */
{ 0, 1, 0 }, /* Green */
{ 1, 0, 1 }, /* Magenta */
{ 1, 0, 0 }, /* Red */
{ 0, 0, 1 }, /* Blue */
};
const int num_hues = ARRAY_LENGTH(hue);
float val_max;
int x, y;
int hue_index;
float value;
unsigned char r, g, b;
uint32_t *pixel;
float n_steps = width_bar - 1;
val_max = (1 << bitwidth) - 1;
for (y = 0; y < header->height; y++) {
hue_index = (y * num_hues) / (header->height - 1);
hue_index = MIN(hue_index, num_hues - 1);
for (x = 0; x < header->width; x++) {
struct color_float rgb = { 0, 0, 0 };
value = (float)x / (float)(header->width - 1);
if (width_bar > 1)
value = floor(value * n_steps) / n_steps;
if (hue[hue_index][0])
rgb.r = value;
if (hue[hue_index][1])
rgb.g = value;
if (hue[hue_index][2])
rgb.b = value;
sRGB_delinearize(&rgb);
r = round(rgb.r * val_max);
g = round(rgb.g * val_max);
b = round(rgb.b * val_max);
pixel = header->data + (y * header->stride / 4) + x;
*pixel = (255U << 24) | (r << 16) | (g << 8) | b;
}
}
}
static cmsHPROFILE
build_lcms_profile_output(const struct lcms_pipeline *pipeline)
{
cmsToneCurve *arr_curves[3];
cmsHPROFILE hRGB;
int type_inverse_tone_curve;
double inverse_tone_curve_param[5];
assert(find_tone_curve_type(pipeline->post_fn, &type_inverse_tone_curve,
inverse_tone_curve_param));
/*
* We are creating output profile and therefore we can use the following:
* calling semantics:
* cmsBuildParametricToneCurve(type_inverse_tone_curve, inverse_tone_curve_param)
* The function find_tone_curve_type sets the type of curve positive if it
* is tone curve and negative if it is inverse. When we create an ICC
* profile we should use a tone curve, the inversion is done by LCMS
* when the profile is used for output.
*/
arr_curves[0] = arr_curves[1] = arr_curves[2] =
cmsBuildParametricToneCurve(NULL,
(-1) * type_inverse_tone_curve,
inverse_tone_curve_param);
assert(arr_curves[0]);
hRGB = cmsCreateRGBProfileTHR(NULL, &wp_d65,
&pipeline->prim_output, arr_curves);
assert(hRGB);
cmsFreeToneCurve(arr_curves[0]);
return hRGB;
}
static char *
build_output_icc_profile(const struct lcms_pipeline *pipe)
{
char *profile_name = NULL;
cmsHPROFILE profile = NULL;
char *wd;
int ret;
bool saved;
wd = realpath(".", NULL);
assert(wd);
ret = asprintf(&profile_name, "%s/matrix-shaper-test-%s.icm", wd,
pipe->color_space);
assert(ret > 0);
profile = build_lcms_profile_output(pipe);
assert(profile);
saved = cmsSaveProfileToFile(profile, profile_name);
assert(saved);
cmsCloseProfile(profile);
return profile_name;
}
static enum test_result_code
fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg)
{
struct compositor_setup setup;
char *file_name;
compositor_setup_defaults(&setup);
setup.renderer = RENDERER_GL;
setup.backend = WESTON_BACKEND_HEADLESS;
setup.width = WINDOW_WIDTH;
setup.height = WINDOW_HEIGHT;
setup.shell = SHELL_TEST_DESKTOP;
file_name = build_output_icc_profile(&arg->pipeline);
if (!file_name)
return RESULT_HARD_ERROR;
weston_ini_setup(&setup,
cfgln("[core]"),
cfgln("color-management=true"),
cfgln("[output]"),
cfgln("name=headless"),
cfgln("icc_profile=%s", file_name));
free(file_name);
return weston_test_harness_execute_as_client(harness, &setup);
}
DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, arr_setup, meta);
static bool
compare_float(float ref, float dst, int x, const char *chan,
float *max_diff, float max_allow_diff)
{
#if 0
/*
* This file can be loaded in Octave for visualization.
*
* S = load('compare_float_dump.txt');
*
* rvec = S(S(:,1)==114, 2:3);
* gvec = S(S(:,1)==103, 2:3);
* bvec = S(S(:,1)==98, 2:3);
*
* figure
* subplot(3, 1, 1);
* plot(rvec(:,1), rvec(:,2) .* 255, 'r');
* subplot(3, 1, 2);
* plot(gvec(:,1), gvec(:,2) .* 255, 'g');
* subplot(3, 1, 3);
* plot(bvec(:,1), bvec(:,2) .* 255, 'b');
*/
static FILE *fp = NULL;
if (!fp)
fp = fopen("compare_float_dump.txt", "w");
fprintf(fp, "%d %d %f\n", chan[0], x, dst - ref);
fflush(fp);
#endif
float diff = fabsf(ref - dst);
if (diff > *max_diff)
*max_diff = diff;
if (diff <= max_allow_diff)
return true;
testlog("x=%d %s: ref %f != dst %f, delta %f\n",
x, chan, ref, dst, dst - ref);
return false;
}
static bool
process_pipeline_comparison(const struct image_header *src,
const struct image_header *shot,
const struct setup_args * arg)
{
const float max_pixel_value = 255.0;
struct color_float max_diff_pipeline = { 0.0f, 0.0f, 0.0f, 0.0f };
float max_allow_diff = arg->pipeline.tolerance / max_pixel_value;
float max_err = 0;
float f_max_err = 0;
bool ok = true;
uint32_t *row_ptr, *row_ptr_shot;
int y, x;
struct color_float pix_src;
struct color_float pix_src_pipeline;
struct color_float pix_shot;
for (y = 0; y < src->height; y++) {
row_ptr = (uint32_t*)((uint8_t*)src->data + (src->stride * y));
row_ptr_shot = (uint32_t*)((uint8_t*)shot->data + (shot->stride * y));
for (x = 0; x < src->width; x++) {
pix_src = a8r8g8b8_to_float(row_ptr[x]);
pix_shot = a8r8g8b8_to_float(row_ptr_shot[x]);
/* do pipeline processing */
process_pixel_using_pipeline(arg->pipeline.pre_fn,
&arg->pipeline.mat,
arg->pipeline.post_fn,
&pix_src, &pix_src_pipeline);
/* check if pipeline matches to shader variant */
ok &= compare_float(pix_src_pipeline.r, pix_shot.r, x,"r",
&max_diff_pipeline.r, max_allow_diff);
ok &= compare_float(pix_src_pipeline.g, pix_shot.g, x, "g",
&max_diff_pipeline.g, max_allow_diff);
ok &= compare_float(pix_src_pipeline.b, pix_shot.b, x, "b",
&max_diff_pipeline.b, max_allow_diff);
}
}
max_err = max_diff_pipeline.r;
if (max_err < max_diff_pipeline.g)
max_err = max_diff_pipeline.g;
if (max_err < max_diff_pipeline.b)
max_err = max_diff_pipeline.b;
f_max_err = max_pixel_value * max_err;
testlog("%s %s %s tol_req %d, tol_cal %f, max diff: r=%f, g=%f, b=%f\n",
__func__, ok == true? "SUCCESS":"FAILURE",
arg->meta.name, arg->pipeline.tolerance, f_max_err,
max_diff_pipeline.r, max_diff_pipeline.g, max_diff_pipeline.b);
return ok;
}
static bool
check_process_pattern_ex(struct buffer *src, struct buffer *shot,
const struct setup_args * arg)
{
struct image_header header_src;
struct image_header header_shot;
bool ok;
get_image_prop(src, &header_src);
get_image_prop(shot, &header_shot);
/* no point to compare different images */
assert(header_src.width == header_shot.width);
assert(header_src.height == header_shot.height);
ok = process_pipeline_comparison(&header_src, &header_shot, arg);
return ok;
}
/*
* Test that matrix-shaper profile does CM correctly, it is used color ramp pattern
*/
TEST(shaper_matrix)
{
const int width = WINDOW_WIDTH;
const int height = WINDOW_HEIGHT;
const int bitwidth = 8;
const int width_bar = 32;
struct client *client;
struct buffer *buf;
struct buffer *shot;
struct wl_surface *surface;
struct image_header image;
bool match;
int seq_no = get_test_fixture_index();
client = create_client_and_test_surface(0, 0, width, height);
assert(client);
surface = client->surface->wl_surface;
buf = create_shm_buffer_a8r8g8b8(client, width, height);
get_image_prop(buf, &image);
gen_ramp_rgb(&image, bitwidth, width_bar);
wl_surface_attach(surface, buf->proxy, 0, 0);
wl_surface_damage(surface, 0, 0, width, height);
wl_surface_commit(surface);
shot = capture_screenshot_of_output(client);
assert(shot);
match = verify_image(shot, "shaper_matrix", seq_no, NULL, seq_no);
assert(check_process_pattern_ex(buf, shot, &arr_setup[seq_no]));
assert(match);
buffer_destroy(shot);
buffer_destroy(buf);
client_destroy(client);
}
......@@ -23,18 +23,90 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "config.h"
#include <math.h>
#include "color_util.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "shared/helpers.h"
struct color_tone_curve {
enum transfer_fn fn;
enum transfer_fn inv_fn;
/* LCMS2 API */
int internal_type;
double param[5];
};
const struct color_tone_curve arr_curves[] = {
{
.fn = TRANSFER_FN_SRGB_EOTF,
.inv_fn = TRANSFER_FN_SRGB_EOTF_INVERSE,
.internal_type = 4,
.param = { 2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045 } ,
},
{
.fn = TRANSFER_FN_ADOBE_RGB_EOTF,
.inv_fn = TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE,
.internal_type = 1,
.param = { 563./256., 0.0, 0.0, 0.0 , 0.0 } ,
},
{
.fn = TRANSFER_FN_POWER2_4_EOTF,
.inv_fn = TRANSFER_FN_POWER2_4_EOTF_INVERSE,
.internal_type = 1,
.param = { 2.4, 0.0, 0.0, 0.0 , 0.0 } ,
}
};
bool
find_tone_curve_type(enum transfer_fn fn, int *type, double params[5])
{
const int size_arr = ARRAY_LENGTH(arr_curves);
const struct color_tone_curve *curve;
for (curve = &arr_curves[0]; curve < &arr_curves[size_arr]; curve++ ) {
if (curve->fn == fn )
*type = curve->internal_type;
else if (curve->inv_fn == fn)
*type = -curve->internal_type;
else
continue;
memcpy(params, curve->param, sizeof(curve->param));
return true;
}
return false;
}
/**
* NaN comes out as is
*This function is not intended for hiding NaN.
*/
static float
sRGB_EOTF(float e)
ensure_unit_range(float v)
{
assert(e >= 0.0f);
assert(e <= 1.0f);
const float tol = 1e-5f;
const float lim_lo = -tol;
const float lim_hi = 1.0f + tol;
assert(v >= lim_lo);
if (v < 0.0f)
return 0.0f;
assert(v <= lim_hi);
if (v > 1.0f)
return 1.0f;
return v;
}
static float
sRGB_EOTF(float e)
{
e = ensure_unit_range(e);
if (e <= 0.04045)
return e / 12.92;
else
......@@ -44,15 +116,40 @@ sRGB_EOTF(float e)
static float
sRGB_EOTF_inv(float o)
{
assert(o >= 0.0f);
assert(o <= 1.0f);
o = ensure_unit_range(o);
if (o <= 0.04045 / 12.92)
return o * 12.92;
else
return pow(o, 1.0 / 2.4) * 1.055 - 0.055;
}
static float
AdobeRGB_EOTF(float e)
{
e = ensure_unit_range(e);
return pow(e, 563./256.);
}
static float
AdobeRGB_EOTF_inv(float o)
{
o = ensure_unit_range(o);
return pow(o, 256./563.);
}
static float
Power2_4_EOTF(float e)
{
e = ensure_unit_range(e);
return pow(e, 2.4);
}
static float
Power2_4_EOTF_inv(float o)
{
o = ensure_unit_range(o);
return pow(o, 1./2.4);
}
void
sRGB_linearize(struct color_float *cf)
......@@ -62,6 +159,35 @@ sRGB_linearize(struct color_float *cf)
cf->b = sRGB_EOTF(cf->b);
}
static float
apply_tone_curve(enum transfer_fn fn, float r)
{
float ret = 0;
switch(fn) {
case TRANSFER_FN_SRGB_EOTF:
ret = sRGB_EOTF(r);
break;
case TRANSFER_FN_SRGB_EOTF_INVERSE:
ret = sRGB_EOTF_inv(r);
break;
case TRANSFER_FN_ADOBE_RGB_EOTF:
ret = AdobeRGB_EOTF(r);
break;
case TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE:
ret = AdobeRGB_EOTF_inv(r);
break;
case TRANSFER_FN_POWER2_4_EOTF:
ret = Power2_4_EOTF(r);
break;
case TRANSFER_FN_POWER2_4_EOTF_INVERSE:
ret = Power2_4_EOTF_inv(r);
break;
}
return ret;
}
void
sRGB_delinearize(struct color_float *cf)
{
......@@ -82,3 +208,37 @@ a8r8g8b8_to_float(uint32_t v)
return cf;
}
void
process_pixel_using_pipeline(enum transfer_fn pre_curve,
const struct lcmsMAT3 *mat,
enum transfer_fn post_curve,
const struct color_float *in,
struct color_float *out)
{
int i, j;
float rgb_in[3];
float out_blend[3];
float tmp;
rgb_in[0] = in->r;
rgb_in[1] = in->g;
rgb_in[2] = in->b;
for (i = 0; i < 3; i++)
rgb_in[i] = apply_tone_curve(pre_curve, rgb_in[i]);
for (i = 0; i < 3; i++) {
tmp = 0.0f;
for (j = 0; j < 3; j++)
tmp += rgb_in[j] * mat->v[j].n[i];
out_blend[i] = tmp;
}
for (i = 0; i < 3; i++)
out_blend[i] = apply_tone_curve(post_curve, out_blend[i]);
out->r = out_blend[0];
out->g = out_blend[1];
out->b = out_blend[2];
}
......@@ -25,18 +25,62 @@
*/
#include <stdint.h>
#include <stdbool.h>
struct color_float {
float r, g, b, a;
};
struct lcmsVEC3 {
float n[3];
};
struct lcmsMAT3 {
struct lcmsVEC3 v[3];
};
enum transfer_fn {
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,
};
/*
* A helper to lay out a matrix in the natural writing order in code
* instead of needing to transpose in your mind every time you read it.
* The matrix is laid out as written:
* ⎡ a11 a12 a13 ⎤
* ⎢ a21 a22 a23 ⎥
* ⎣ a31 a32 a33 ⎦
* where the first digit is row and the second digit is column.
*/
#define LCMSMAT3(a11, a12, a13, \
a21, a22, a23, \
a31, a32, a33) ((struct lcmsMAT3) \
{ /* Each vector is a column => looks like a transpose */ \
.v[0] = { .n = { a11, a21, a31} }, \
.v[1] = { .n = { a12, a22, a32} }, \
.v[2] = { .n = { a13, a23, a33} }, \
})
void
sRGB_linearize(struct color_float *cf);
void
sRGB_delinearize(struct color_float *cf);
struct color_float
a8r8g8b8_to_float(uint32_t v);
bool
find_tone_curve_type(enum transfer_fn fn, int *type, double params[5]);
void
process_pixel_using_pipeline(enum transfer_fn pre_curve,
const struct lcmsMAT3 *mat,
enum transfer_fn post_curve,
const struct color_float *in,
struct color_float *out);
......@@ -226,6 +226,18 @@ tests = [
},
]
if get_option('color-management-lcms')
dep_lcms2 = dependency('lcms2', version: '>= 2.9', required: false)
if not dep_lcms2.found()
error('color-management-lcms tests require lcms2 which was not found. Or, you can use \'-Dcolor-management-lcms=false\'.')
endif
tests += {
'name': 'color-shaper-matrix',
'dep_objs': [ dep_libm, dep_lcms2 ]
}
endif
tests_standalone = [
['config-parser', [], [ dep_zucmain ]],
['matrix', [], [ dep_libm, dep_matrix_c ]],
......