mirror of
https://gitlab.freedesktop.org/wayland/weston.git
synced 2025-12-20 04:40:07 +01:00
Merge branch 'mr/paraicc2' into 'main'
Basics for ICC<->parametric color transformations See merge request wayland/weston!1916
This commit is contained in:
commit
69efa1aa89
8 changed files with 562 additions and 36 deletions
|
|
@ -89,6 +89,13 @@ weston_v3f_from_v4f_xyz(struct weston_vec4f v)
|
|||
return WESTON_VEC3F(v.x, v.y, v.z);
|
||||
}
|
||||
|
||||
/** Element-wise add */
|
||||
static inline struct weston_vec3f
|
||||
weston_v3f_add_v3f(struct weston_vec3f a, struct weston_vec3f b)
|
||||
{
|
||||
return WESTON_VEC3F(a.x + b.x, a.y + b.y, a.z + b.z);
|
||||
}
|
||||
|
||||
/** 3-vector dot product */
|
||||
static inline float
|
||||
weston_v3f_dot_v3f(struct weston_vec3f a, struct weston_vec3f b)
|
||||
|
|
|
|||
|
|
@ -345,6 +345,12 @@ transforms_scope_new_sub(struct weston_log_subscription *subs, void *data)
|
|||
weston_log_subscription_printf(subs, "%s", str);
|
||||
free(str);
|
||||
}
|
||||
|
||||
str = cmlcms_color_transformer_string(4, &xform->transformer);
|
||||
if (str) {
|
||||
weston_log_subscription_printf(subs, "%s", str);
|
||||
free(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include <lcms2.h>
|
||||
#include <libweston/libweston.h>
|
||||
#include <libweston/weston-log.h>
|
||||
#include <libweston/linalg-3.h>
|
||||
|
||||
#include "color.h"
|
||||
#include "shared/helpers.h"
|
||||
|
|
@ -200,6 +201,28 @@ struct color_transform_steps_mask {
|
|||
uint8_t steps;
|
||||
};
|
||||
|
||||
enum cmlcms_color_transformer_elem {
|
||||
CMLCMS_TRANSFORMER_CURVE1 = 1 << 0,
|
||||
CMLCMS_TRANSFORMER_LIN1 = 1 << 1,
|
||||
CMLCMS_TRANSFORMER_ICC_CHAIN = 1 << 2,
|
||||
CMLCMS_TRANSFORMER_LIN2 = 1 << 3,
|
||||
CMLCMS_TRANSFORMER_CURVE2 = 1 << 4,
|
||||
};
|
||||
|
||||
/** A complete color transformation to be computed on the CPU */
|
||||
struct cmlcms_color_transformer {
|
||||
/** Or'd together from enum cmlcms_color_transformer_elem */
|
||||
uint8_t element_mask;
|
||||
|
||||
struct weston_color_curve curve1;
|
||||
struct weston_mat3f lin1_matrix;
|
||||
struct weston_vec3f lin1_offset;
|
||||
cmsHTRANSFORM icc_chain;
|
||||
struct weston_mat3f lin2_matrix;
|
||||
struct weston_vec3f lin2_offset;
|
||||
struct weston_color_curve curve2;
|
||||
};
|
||||
|
||||
struct cmlcms_color_transform_recipe {
|
||||
enum cmlcms_category category;
|
||||
struct cmlcms_color_profile *input_profile;
|
||||
|
|
@ -228,13 +251,11 @@ struct cmlcms_color_transform {
|
|||
cmsToneCurve *post_curve[3];
|
||||
|
||||
/**
|
||||
* 3D LUT color mapping part of the transformation, if needed by the
|
||||
* weston_color_transform. This is used as a fallback when an
|
||||
* arbitrary LittleCMS pipeline cannot be translated into a more
|
||||
* specific form or when the backend/renderer is not able to use
|
||||
* such optimized form.
|
||||
* For evaluating points through the complete color transformation,
|
||||
* even when base.steps_valid is false. This is used for the 3D LUT
|
||||
* path.
|
||||
*/
|
||||
cmsHTRANSFORM cmap_3dlut;
|
||||
struct cmlcms_color_transformer transformer;
|
||||
|
||||
/**
|
||||
* Certain categories of transformations need their own LittleCMS
|
||||
|
|
@ -298,4 +319,18 @@ cmsToneCurve *
|
|||
lcmsJoinToneCurve(cmsContext context_id, const cmsToneCurve *X,
|
||||
const cmsToneCurve *Y, unsigned int resulting_points);
|
||||
|
||||
void
|
||||
cmlcms_color_transformer_fini(struct cmlcms_color_transformer *t);
|
||||
|
||||
void
|
||||
cmlcms_color_transformer_eval(struct weston_compositor *compositor,
|
||||
const struct cmlcms_color_transformer *t,
|
||||
struct weston_vec3f *dst,
|
||||
const struct weston_vec3f *src,
|
||||
size_t len);
|
||||
|
||||
char *
|
||||
cmlcms_color_transformer_string(int indent,
|
||||
const struct cmlcms_color_transformer *t);
|
||||
|
||||
#endif /* WESTON_COLOR_LCMS_H */
|
||||
|
|
|
|||
|
|
@ -121,11 +121,8 @@ cmlcms_color_transform_destroy(struct cmlcms_color_transform *xform)
|
|||
wl_list_remove(&xform->link);
|
||||
|
||||
cmsFreeToneCurveTriple(xform->pre_curve);
|
||||
|
||||
if (xform->cmap_3dlut)
|
||||
cmsDeleteTransform(xform->cmap_3dlut);
|
||||
|
||||
cmsFreeToneCurveTriple(xform->post_curve);
|
||||
cmlcms_color_transformer_fini(&xform->transformer);
|
||||
|
||||
if (xform->lcms_ctx)
|
||||
cmsDeleteContext(xform->lcms_ctx);
|
||||
|
|
@ -1378,11 +1375,14 @@ init_icc_to_icc_chain(struct cmlcms_color_transform *xform)
|
|||
|
||||
assert(chain_len <= ARRAY_LENGTH(chain));
|
||||
|
||||
weston_assert_ptr_null(cm->base.compositor, xform->cmap_3dlut);
|
||||
xform->cmap_3dlut = xform_realize_icc_chain(xform, chain, chain_len,
|
||||
render_intent, allowed);
|
||||
weston_assert_ptr_null(cm->base.compositor, xform->transformer.icc_chain);
|
||||
xform->transformer.icc_chain = xform_realize_icc_chain(xform, chain, chain_len,
|
||||
render_intent, allowed);
|
||||
if (!xform->transformer.icc_chain)
|
||||
return false;
|
||||
|
||||
return !!xform->cmap_3dlut;
|
||||
xform->transformer.element_mask |= CMLCMS_TRANSFORMER_ICC_CHAIN;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -1390,18 +1390,26 @@ weston_color_curve_set_from_params(struct weston_color_curve *curve,
|
|||
const struct weston_color_profile_params *p,
|
||||
enum weston_tf_direction dir)
|
||||
{
|
||||
curve->type = WESTON_COLOR_CURVE_TYPE_ENUM;
|
||||
curve->u.enumerated.tf = p->tf;
|
||||
curve->u.enumerated.tf_direction = dir;
|
||||
if (p->tf.info->tf == WESTON_TF_EXT_LINEAR) {
|
||||
curve->type = WESTON_COLOR_CURVE_TYPE_IDENTITY;
|
||||
} else {
|
||||
curve->type = WESTON_COLOR_CURVE_TYPE_ENUM;
|
||||
curve->u.enumerated.tf = p->tf;
|
||||
curve->u.enumerated.tf_direction = dir;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
weston_color_mapping_set_from_m4f(struct weston_color_mapping *mapping,
|
||||
struct weston_mat4f mat)
|
||||
{
|
||||
mapping->type = WESTON_COLOR_MAPPING_TYPE_MATRIX;
|
||||
mapping->u.mat.matrix = weston_m3f_from_m4f_xyz(mat);
|
||||
mapping->u.mat.offset = weston_v3f_from_v4f_xyz(mat.col[3]);
|
||||
if (matrix_is_identity(mat, MATRIX_PRECISION_BITS)) {
|
||||
mapping->type = WESTON_COLOR_MAPPING_TYPE_IDENTITY;
|
||||
} else {
|
||||
mapping->type = WESTON_COLOR_MAPPING_TYPE_MATRIX;
|
||||
mapping->u.mat.matrix = weston_m3f_from_m4f_xyz(mat);
|
||||
mapping->u.mat.offset = weston_v3f_from_v4f_xyz(mat.col[3]);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
|
|
@ -1426,6 +1434,9 @@ init_blend_to_parametric(struct cmlcms_color_transform *xform)
|
|||
xform->base.post_curve.type = WESTON_COLOR_CURVE_TYPE_IDENTITY;
|
||||
xform->base.steps_valid = true;
|
||||
|
||||
xform->transformer.curve1 = xform->base.pre_curve;
|
||||
xform->transformer.element_mask = CMLCMS_TRANSFORMER_CURVE1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1643,6 +1654,8 @@ init_parametric_to_parametric(struct cmlcms_color_transform *xform)
|
|||
*/
|
||||
weston_color_curve_set_from_params(&xform->base.pre_curve,
|
||||
recipe->input_profile->params, WESTON_FORWARD_TF);
|
||||
xform->transformer.curve1 = xform->base.pre_curve;
|
||||
xform->transformer.element_mask = CMLCMS_TRANSFORMER_CURVE1;
|
||||
|
||||
if (!rgb_to_rgb_matrix(&mat,
|
||||
recipe->input_profile->params,
|
||||
|
|
@ -1655,6 +1668,15 @@ init_parametric_to_parametric(struct cmlcms_color_transform *xform)
|
|||
}
|
||||
|
||||
weston_color_mapping_set_from_m4f(&xform->base.mapping, mat);
|
||||
switch (xform->base.mapping.type) {
|
||||
case WESTON_COLOR_MAPPING_TYPE_IDENTITY:
|
||||
break;
|
||||
case WESTON_COLOR_MAPPING_TYPE_MATRIX:
|
||||
xform->transformer.lin1_matrix = xform->base.mapping.u.mat.matrix;
|
||||
xform->transformer.lin1_offset = xform->base.mapping.u.mat.offset;
|
||||
xform->transformer.element_mask |= CMLCMS_TRANSFORMER_LIN1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* TODO: Use HLG OOTF for gamma correction? */
|
||||
/* TODO: try https://gitlab.freedesktop.org/pq/color-and-hdr/-/issues/45 */
|
||||
|
|
@ -1667,6 +1689,8 @@ init_parametric_to_parametric(struct cmlcms_color_transform *xform)
|
|||
weston_color_curve_set_from_params(&xform->base.post_curve,
|
||||
recipe->output_profile->params,
|
||||
WESTON_INVERSE_TF);
|
||||
xform->transformer.curve2 = xform->base.post_curve;
|
||||
xform->transformer.element_mask |= CMLCMS_TRANSFORMER_CURVE2;
|
||||
break;
|
||||
case CMLCMS_CATEGORY_BLEND_TO_OUTPUT:
|
||||
weston_assert_not_reached(xform->base.cm->compositor,
|
||||
|
|
@ -1678,6 +1702,261 @@ init_parametric_to_parametric(struct cmlcms_color_transform *xform)
|
|||
return true;
|
||||
}
|
||||
|
||||
static cmsCIExyY
|
||||
lcms_xyY_from(struct weston_CIExy p)
|
||||
{
|
||||
return (cmsCIExyY){ p.x, p.y, 1.0f };
|
||||
}
|
||||
|
||||
/** Create LittleCMS profile for an optical space
|
||||
*
|
||||
* \param cm The color manager, for the LittleCMS context.
|
||||
* \param gm The primaries and the white point.
|
||||
* \param rel_black_level The relative black level, when white maximum
|
||||
* luminance is 1.0.
|
||||
*
|
||||
* \return A LittleCMS RGB Display profile where the transfer characteristic
|
||||
* is linear from rel_black_level to 1.0.
|
||||
*/
|
||||
static struct lcmsProfilePtr
|
||||
optical_profile(struct weston_color_manager_lcms *cm,
|
||||
const struct weston_color_gamut *gm,
|
||||
float rel_black_level)
|
||||
{
|
||||
cmsCIExyY wp = lcms_xyY_from(gm->white_point);
|
||||
cmsCIExyYTRIPLE prim = {
|
||||
lcms_xyY_from(gm->primary[0]),
|
||||
lcms_xyY_from(gm->primary[1]),
|
||||
lcms_xyY_from(gm->primary[2])
|
||||
};
|
||||
cmsHPROFILE hnd;
|
||||
cmsToneCurve *trc[3];
|
||||
cmsFloat32Number points[2] = { rel_black_level, 1.0f };
|
||||
|
||||
trc[2] = trc[1] = trc[0] =
|
||||
cmsBuildTabulatedToneCurveFloat(cm->lcms_ctx, 2, points);
|
||||
abort_oom_if_null(trc[0]);
|
||||
|
||||
hnd = cmsCreateRGBProfileTHR(cm->lcms_ctx, &wp, &prim, trc);
|
||||
weston_assert_ptr_not_null(cm->base.compositor, hnd);
|
||||
|
||||
cmsFreeToneCurve(trc[0]);
|
||||
return (struct lcmsProfilePtr){ hnd };
|
||||
}
|
||||
|
||||
enum matrix_order {
|
||||
/** Add new matrix to the right of the existing matrix. */
|
||||
MATRIX_PREPEND,
|
||||
/** Add new matrix to the left of the existing matrix. */
|
||||
MATRIX_APPEND,
|
||||
};
|
||||
|
||||
static void
|
||||
patch_color_mapping_matrix(struct weston_color_mapping *mapping,
|
||||
struct weston_mat4f M, enum matrix_order order)
|
||||
{
|
||||
struct weston_mat4f cmap;
|
||||
struct weston_color_mapping_matrix *mapmat = NULL;
|
||||
|
||||
switch (mapping->type) {
|
||||
case WESTON_COLOR_MAPPING_TYPE_IDENTITY:
|
||||
weston_color_mapping_set_from_m4f(mapping, M);
|
||||
return;
|
||||
case WESTON_COLOR_MAPPING_TYPE_MATRIX:
|
||||
mapmat = &mapping->u.mat;
|
||||
break;
|
||||
}
|
||||
|
||||
cmap = weston_m4f_from_m3f_v3f(mapmat->matrix, mapmat->offset);
|
||||
switch (order) {
|
||||
case MATRIX_PREPEND:
|
||||
cmap = weston_m4f_mul_m4f(cmap, M);
|
||||
break;
|
||||
case MATRIX_APPEND:
|
||||
cmap = weston_m4f_mul_m4f(M, cmap);
|
||||
break;
|
||||
}
|
||||
|
||||
weston_color_mapping_set_from_m4f(mapping, cmap);
|
||||
}
|
||||
|
||||
static bool
|
||||
init_icc_to_parametric(struct cmlcms_color_transform *xform)
|
||||
{
|
||||
struct weston_color_manager_lcms *cm = to_cmlcms(xform->base.cm);
|
||||
struct cmlcms_color_profile *in_prof = xform->search_key.input_profile;
|
||||
struct cmlcms_color_profile *out_prof = xform->search_key.output_profile;
|
||||
const struct weston_color_profile_params *out = out_prof->params;
|
||||
const struct weston_render_intent_info *render_intent;
|
||||
struct color_transform_steps_mask allowed = {
|
||||
STEP_PRE_CURVE | STEP_MAPPING
|
||||
};
|
||||
struct lcmsProfilePtr chain[2];
|
||||
cmsHTRANSFORM icc_chain;
|
||||
struct weston_mat4f M;
|
||||
float v;
|
||||
|
||||
weston_assert_u32_eq(cm->base.compositor, in_prof->type, CMLCMS_PROFILE_TYPE_ICC);
|
||||
weston_assert_u32_eq(cm->base.compositor, out_prof->type, CMLCMS_PROFILE_TYPE_PARAMS);
|
||||
|
||||
render_intent = xform->search_key.render_intent;
|
||||
|
||||
/*
|
||||
* The ICC chain converts input device RGB to optical output RGB
|
||||
* with relative luminance. The input reference luminance
|
||||
* is 1.0, and implicitly it is also the input peak luminance.
|
||||
* The TRC adds target_min_luminance as necessary, meaning that
|
||||
* optical output RGB 0,0,0 corresponds to target_min_luminance.
|
||||
* Optical output RGB 1,1,1 corresponds to reference white luminance.
|
||||
*/
|
||||
chain[0] = in_prof->icc.profile;
|
||||
chain[1] = optical_profile(cm, &out->primaries,
|
||||
out->target_min_luminance / out->reference_white_luminance);
|
||||
|
||||
icc_chain = xform_realize_icc_chain(xform, chain, 2, render_intent, allowed);
|
||||
cmsCloseProfile(chain[1].p);
|
||||
if (!icc_chain)
|
||||
return false;
|
||||
|
||||
/* Map [0, 1] to output [target_min, reference]. */
|
||||
v = out->reference_white_luminance - out->target_min_luminance;
|
||||
M = weston_m4f_scaling(v, v, v);
|
||||
v = out->target_min_luminance; /* applied below */
|
||||
|
||||
/* Convert cd/m² to output [0, 1]. */
|
||||
v -= out->min_luminance;
|
||||
M = weston_m4f_mul_m4f(weston_m4f_translation(v, v, v), M);
|
||||
v = 1.0f / (out->max_luminance - out->min_luminance);
|
||||
M = weston_m4f_mul_m4f(weston_m4f_scaling(v, v, v), M);
|
||||
|
||||
/* TODO: Dynamic range adjustment */
|
||||
|
||||
if (xform->base.steps_valid) {
|
||||
weston_assert_u32_eq(cm->base.compositor,
|
||||
xform->base.post_curve.type,
|
||||
WESTON_COLOR_CURVE_TYPE_IDENTITY);
|
||||
|
||||
patch_color_mapping_matrix(&xform->base.mapping, M, MATRIX_APPEND);
|
||||
|
||||
if (xform->search_key.category == CMLCMS_CATEGORY_INPUT_TO_OUTPUT) {
|
||||
weston_color_curve_set_from_params(&xform->base.post_curve,
|
||||
out, WESTON_INVERSE_TF);
|
||||
}
|
||||
}
|
||||
|
||||
xform->transformer.icc_chain = icc_chain;
|
||||
xform->transformer.element_mask = CMLCMS_TRANSFORMER_ICC_CHAIN;
|
||||
|
||||
if (!matrix_is_identity(M, MATRIX_PRECISION_BITS)) {
|
||||
xform->transformer.lin2_matrix = weston_m3f_from_m4f_xyz(M);
|
||||
xform->transformer.lin2_offset = weston_v3f_from_v4f_xyz(M.col[3]);
|
||||
xform->transformer.element_mask |= CMLCMS_TRANSFORMER_LIN2;
|
||||
}
|
||||
|
||||
if (xform->search_key.category == CMLCMS_CATEGORY_INPUT_TO_OUTPUT) {
|
||||
weston_color_curve_set_from_params(&xform->transformer.curve2,
|
||||
out, WESTON_INVERSE_TF);
|
||||
xform->transformer.element_mask |= CMLCMS_TRANSFORMER_CURVE2;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
init_parametric_to_icc(struct cmlcms_color_transform *xform)
|
||||
{
|
||||
struct weston_color_manager_lcms *cm = to_cmlcms(xform->base.cm);
|
||||
struct cmlcms_color_profile *in_prof = xform->search_key.input_profile;
|
||||
struct cmlcms_color_profile *out_prof = xform->search_key.output_profile;
|
||||
const struct weston_color_profile_params *in = in_prof->params;
|
||||
const struct weston_render_intent_info *render_intent;
|
||||
struct color_transform_steps_mask allowed = {
|
||||
STEP_MAPPING | STEP_POST_CURVE
|
||||
};
|
||||
struct lcmsProfilePtr optical_prof;
|
||||
struct lcmsProfilePtr chain[5];
|
||||
cmsHTRANSFORM icc_chain;
|
||||
unsigned chain_len = 0;
|
||||
struct weston_mat4f M;
|
||||
float v;
|
||||
|
||||
weston_assert_u32_eq(cm->base.compositor, in_prof->type, CMLCMS_PROFILE_TYPE_PARAMS);
|
||||
weston_assert_u32_eq(cm->base.compositor, out_prof->type, CMLCMS_PROFILE_TYPE_ICC);
|
||||
|
||||
render_intent = xform->search_key.render_intent;
|
||||
|
||||
/*
|
||||
* Pre-curve shall have EOTF to convert electrical device RGB to
|
||||
* min-max relative optical device RGB.
|
||||
*/
|
||||
|
||||
/* TODO: Dynamic range adjustment */
|
||||
|
||||
/* Convert input [0, 1] to cd/m² */
|
||||
v = in->max_luminance - in->min_luminance;
|
||||
M = weston_m4f_scaling(v, v, v);
|
||||
v = in->min_luminance; /* applied below */
|
||||
|
||||
/* Map input [target_min_luminance, reference] to [0, 1] */
|
||||
v -= in->target_min_luminance;
|
||||
M = weston_m4f_mul_m4f(weston_m4f_translation(v, v, v), M);
|
||||
v = 1.0f / (in->reference_white_luminance - in->target_min_luminance);
|
||||
M = weston_m4f_mul_m4f(weston_m4f_scaling(v, v, v), M);
|
||||
|
||||
/* The above is the input to the ICC chain. */
|
||||
optical_prof = optical_profile(cm, &in->primaries,
|
||||
in->target_min_luminance / in->reference_white_luminance);
|
||||
|
||||
/* see init_icc_to_icc_chain() */
|
||||
chain[chain_len++] = optical_prof;
|
||||
switch (xform->search_key.category) {
|
||||
case CMLCMS_CATEGORY_INPUT_TO_BLEND:
|
||||
chain[chain_len++] = out_prof->icc.profile;
|
||||
chain[chain_len++] = out_prof->extract.eotf;
|
||||
break;
|
||||
case CMLCMS_CATEGORY_INPUT_TO_OUTPUT:
|
||||
chain[chain_len++] = out_prof->icc.profile;
|
||||
if (out_prof->extract.vcgt.p)
|
||||
chain[chain_len++] = out_prof->extract.vcgt;
|
||||
break;
|
||||
case CMLCMS_CATEGORY_BLEND_TO_OUTPUT:
|
||||
weston_assert_not_reached(xform->base.cm->compositor,
|
||||
"blend-to-output handled elsewhere");
|
||||
}
|
||||
|
||||
assert(chain_len <= ARRAY_LENGTH(chain));
|
||||
|
||||
icc_chain = xform_realize_icc_chain(xform, chain, chain_len, render_intent, allowed);
|
||||
cmsCloseProfile(optical_prof.p);
|
||||
if (!icc_chain)
|
||||
return false;
|
||||
|
||||
if (xform->base.steps_valid) {
|
||||
weston_assert_u32_eq(cm->base.compositor,
|
||||
xform->base.pre_curve.type,
|
||||
WESTON_COLOR_CURVE_TYPE_IDENTITY);
|
||||
|
||||
weston_color_curve_set_from_params(&xform->base.pre_curve,
|
||||
in, WESTON_FORWARD_TF);
|
||||
patch_color_mapping_matrix(&xform->base.mapping, M, MATRIX_PREPEND);
|
||||
}
|
||||
|
||||
weston_color_curve_set_from_params(&xform->transformer.curve1,
|
||||
in, WESTON_FORWARD_TF);
|
||||
xform->transformer.element_mask = CMLCMS_TRANSFORMER_CURVE1;
|
||||
|
||||
if (!matrix_is_identity(M, MATRIX_PRECISION_BITS)) {
|
||||
xform->transformer.lin1_matrix = weston_m3f_from_m4f_xyz(M);
|
||||
xform->transformer.lin1_offset = weston_v3f_from_v4f_xyz(M.col[3]);
|
||||
xform->transformer.element_mask |= CMLCMS_TRANSFORMER_LIN1;
|
||||
}
|
||||
|
||||
xform->transformer.icc_chain = icc_chain;
|
||||
xform->transformer.element_mask |= CMLCMS_TRANSFORMER_ICC_CHAIN;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum cmlcms_color_transform_type {
|
||||
CMLCMS_BLEND_TO_ICC = 0x0,
|
||||
CMLCMS_BLEND_TO_PARAM = 0x1,
|
||||
|
|
@ -1754,7 +2033,8 @@ cmlcms_color_transform_recipe_string(const struct cmlcms_color_transform_recipe
|
|||
}
|
||||
|
||||
static bool
|
||||
build_3d_lut(struct weston_compositor *compositor, cmsHTRANSFORM cmap_3dlut,
|
||||
build_3d_lut(struct weston_compositor *compositor,
|
||||
const struct cmlcms_color_transformer *transformer,
|
||||
unsigned int len_shaper, const float *shaper,
|
||||
unsigned int len_lut3d, float *lut3d)
|
||||
{
|
||||
|
|
@ -1836,7 +2116,9 @@ build_3d_lut(struct weston_compositor *compositor, cmsHTRANSFORM cmap_3dlut,
|
|||
|
||||
index_r = 0;
|
||||
i = 3 * (index_r + len_lut3d * (index_g + len_lut3d * index_b));
|
||||
cmsDoTransform(cmap_3dlut, rgb_in, &lut3d[i], len_lut3d);
|
||||
cmlcms_color_transformer_eval(compositor, transformer,
|
||||
(struct weston_vec3f *)&lut3d[i],
|
||||
rgb_in, len_lut3d);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1876,12 +2158,15 @@ is_monotonic(const float *lut, unsigned len)
|
|||
}
|
||||
|
||||
static bool
|
||||
build_shaper(cmsContext lcms_ctx, cmsHTRANSFORM cmap_3dlut,
|
||||
unsigned int len_shaper, float *shaper)
|
||||
build_shaper(struct weston_compositor *compositor,
|
||||
cmsContext lcms_ctx,
|
||||
const struct cmlcms_color_transformer *transformer,
|
||||
unsigned int len_shaper,
|
||||
float *shaper)
|
||||
{
|
||||
float *curves[3];
|
||||
float divider = len_shaper - 1;
|
||||
float rgb_in[3], rgb_out[3];
|
||||
struct weston_vec3f rgb_in, rgb_out;
|
||||
cmsToneCurve *tc[3] = { NULL };
|
||||
unsigned int ch, i;
|
||||
float smoothing_param;
|
||||
|
|
@ -1904,10 +2189,11 @@ build_shaper(cmsContext lcms_ctx, cmsHTRANSFORM cmap_3dlut,
|
|||
curves[2] = &shaper[2 * len_shaper];
|
||||
|
||||
for (i = 0; i < len_shaper; i++) {
|
||||
rgb_in[0] = rgb_in[1] = rgb_in[2] = (float)i / divider;
|
||||
cmsDoTransform(cmap_3dlut, rgb_in, rgb_out, 1);
|
||||
rgb_in.r = rgb_in.g = rgb_in.b = (float)i / divider;
|
||||
cmlcms_color_transformer_eval(compositor, transformer,
|
||||
&rgb_out, &rgb_in, 1);
|
||||
for (ch = 0; ch < 3; ch++)
|
||||
curves[ch][i] = ensure_unorm(rgb_out[ch]);
|
||||
curves[ch][i] = ensure_unorm(rgb_out.el[ch]);
|
||||
}
|
||||
|
||||
for (ch = 0; ch < 3; ch++) {
|
||||
|
|
@ -1944,10 +2230,10 @@ out:
|
|||
}
|
||||
|
||||
/**
|
||||
* Based on [1]. We get cmsHTRANSFORM cmap_3dlut and decompose into a shaper
|
||||
* Based on [1]. We get the transformer and decompose into a shaper
|
||||
* (3x1D LUT) + 3D LUT. With that, we can reduce the 3D LUT dimension size
|
||||
* without loosing precision. 3D LUT dimension size is problematic because it
|
||||
* demands n³ memory. In this function we construct such shaper.
|
||||
* without losing precision. 3D LUT dimension size is problematic because it
|
||||
* demands n³ memory.
|
||||
*
|
||||
* [1] https://www.littlecms.com/ASICprelinerization_CGIV08.pdf
|
||||
*/
|
||||
|
|
@ -1960,12 +2246,12 @@ xform_to_shaper_plus_3dlut(struct weston_color_transform *xform_base,
|
|||
struct weston_compositor *compositor = xform_base->cm->compositor;
|
||||
bool ret;
|
||||
|
||||
ret = build_shaper(xform->lcms_ctx, xform->cmap_3dlut,
|
||||
ret = build_shaper(compositor, xform->lcms_ctx, &xform->transformer,
|
||||
len_shaper, shaper);
|
||||
if (!ret)
|
||||
return false;
|
||||
|
||||
ret = build_3d_lut(compositor, xform->cmap_3dlut,
|
||||
ret = build_3d_lut(compositor, &xform->transformer,
|
||||
len_shaper, shaper, len_lut3d, lut3d);
|
||||
if (!ret)
|
||||
return false;
|
||||
|
|
@ -2019,8 +2305,10 @@ cmlcms_color_transform_create(struct weston_color_manager_lcms *cm,
|
|||
ret = init_icc_to_icc_chain(xform);
|
||||
break;
|
||||
case CMLCMS_ICC_TO_PARAM:
|
||||
ret = init_icc_to_parametric(xform);
|
||||
break;
|
||||
case CMLCMS_PARAM_TO_ICC:
|
||||
ret = init_parametric_to_icc(xform);
|
||||
break;
|
||||
case CMLCMS_PARAM_TO_PARAM:
|
||||
ret = init_parametric_to_parametric(xform);
|
||||
|
|
@ -2044,6 +2332,12 @@ cmlcms_color_transform_create(struct weston_color_manager_lcms *cm,
|
|||
free(str);
|
||||
}
|
||||
|
||||
str = cmlcms_color_transformer_string(4, &xform->transformer);
|
||||
if (str) {
|
||||
weston_log_scope_printf(cm->transforms_scope, "%s", str);
|
||||
free(str);
|
||||
}
|
||||
|
||||
return xform;
|
||||
|
||||
error:
|
||||
|
|
|
|||
183
libweston/color-lcms/color-transformer.c
Normal file
183
libweston/color-lcms/color-transformer.c
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright 2025 Collabora, Ltd.
|
||||
*
|
||||
* 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 <libweston/libweston.h>
|
||||
#include <libweston/linalg-3.h>
|
||||
#include <lcms2.h>
|
||||
|
||||
#include "color.h"
|
||||
#include "color-properties.h"
|
||||
#include "color-operations.h"
|
||||
#include "shared/xalloc.h"
|
||||
#include "shared/weston-assert.h"
|
||||
#include "color-lcms.h"
|
||||
|
||||
/** Release all transformer members. */
|
||||
void
|
||||
cmlcms_color_transformer_fini(struct cmlcms_color_transformer *t)
|
||||
{
|
||||
if (t->icc_chain) {
|
||||
cmsDeleteTransform(t->icc_chain);
|
||||
t->icc_chain = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/** Push the given points through the transformer
|
||||
*
|
||||
* \param compositor The compositor, for logging assertions.
|
||||
* \param t The transformer to execute.
|
||||
* \param[out] dst The destination array.
|
||||
* \param[in] src The source array.
|
||||
* \param len The length of both arrays.
|
||||
*/
|
||||
void
|
||||
cmlcms_color_transformer_eval(struct weston_compositor *compositor,
|
||||
const struct cmlcms_color_transformer *t,
|
||||
struct weston_vec3f *dst,
|
||||
const struct weston_vec3f *src,
|
||||
size_t len)
|
||||
{
|
||||
const struct weston_vec3f *in = src;
|
||||
struct weston_vec3f *end = dst + len;
|
||||
struct weston_vec3f *out;
|
||||
|
||||
weston_assert_u8_ne(NULL, t->element_mask, 0);
|
||||
|
||||
if (t->element_mask & CMLCMS_TRANSFORMER_CURVE1) {
|
||||
weston_color_curve_sample(compositor, &t->curve1, in, dst, len);
|
||||
in = dst;
|
||||
}
|
||||
|
||||
if (t->element_mask & CMLCMS_TRANSFORMER_LIN1) {
|
||||
for (out = dst; out < end; out++, in++) {
|
||||
*out = weston_v3f_add_v3f(weston_m3f_mul_v3f(t->lin1_matrix, *in),
|
||||
t->lin1_offset);
|
||||
}
|
||||
in = dst;
|
||||
}
|
||||
|
||||
if (t->element_mask & CMLCMS_TRANSFORMER_ICC_CHAIN) {
|
||||
cmsDoTransform(t->icc_chain, in, dst, len);
|
||||
in = dst;
|
||||
}
|
||||
|
||||
if (t->element_mask & CMLCMS_TRANSFORMER_LIN2) {
|
||||
for (out = dst; out < end; out++, in++) {
|
||||
*out = weston_v3f_add_v3f(weston_m3f_mul_v3f(t->lin2_matrix, *in),
|
||||
t->lin2_offset);
|
||||
}
|
||||
in = dst;
|
||||
}
|
||||
|
||||
if (t->element_mask & CMLCMS_TRANSFORMER_CURVE2) {
|
||||
weston_color_curve_sample(compositor, &t->curve2, in, dst, len);
|
||||
in = dst;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
transformer_curve_fprint(FILE *fp,
|
||||
int indent,
|
||||
const char *step,
|
||||
const struct weston_color_curve *curve)
|
||||
{
|
||||
const struct weston_color_curve_enum *en;
|
||||
unsigned i;
|
||||
|
||||
if (curve->type != WESTON_COLOR_CURVE_TYPE_ENUM) {
|
||||
fprintf(fp, "%*s[unexpectedly not enum]\n", indent, "");
|
||||
return;
|
||||
}
|
||||
en = &curve->u.enumerated;
|
||||
|
||||
fprintf(fp, "%*s%s, %s", indent, "", step, en->tf.info->desc);
|
||||
if (en->tf.info->count_parameters > 0) {
|
||||
fprintf(fp, ": ");
|
||||
for (i = 0; i < en->tf.info->count_parameters; i++)
|
||||
fprintf(fp, " % .4f", en->tf.params[i]);
|
||||
}
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
|
||||
static void
|
||||
transformer_linear_fprint(FILE *fp,
|
||||
int indent,
|
||||
const char *step,
|
||||
struct weston_mat3f matrix,
|
||||
struct weston_vec3f offset)
|
||||
{
|
||||
unsigned r, c;
|
||||
|
||||
fprintf(fp, "%*s%s\n", indent, "", step);
|
||||
for (r = 0; r < 3; r++) {
|
||||
fprintf(fp, "%*s", indent + 1, "");
|
||||
for (c = 0; c < 3; c++)
|
||||
fprintf(fp, " %8.4f", matrix.col[c].el[r]);
|
||||
fprintf(fp, " %8.4f\n", offset.el[r]);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cmlcms_color_transformer_details_fprint(FILE *fp,
|
||||
int indent,
|
||||
const struct cmlcms_color_transformer *t)
|
||||
{
|
||||
if (t->element_mask & CMLCMS_TRANSFORMER_CURVE1)
|
||||
transformer_curve_fprint(fp, indent, "curve1", &t->curve1);
|
||||
|
||||
if (t->element_mask & CMLCMS_TRANSFORMER_LIN1)
|
||||
transformer_linear_fprint(fp, indent, "lin1", t->lin1_matrix, t->lin1_offset);
|
||||
|
||||
if (t->element_mask & CMLCMS_TRANSFORMER_ICC_CHAIN)
|
||||
fprintf(fp, "%*sICC-to-ICC transform pipeline\n", indent, "");
|
||||
|
||||
if (t->element_mask & CMLCMS_TRANSFORMER_LIN2)
|
||||
transformer_linear_fprint(fp, indent, "lin2", t->lin2_matrix, t->lin2_offset);
|
||||
|
||||
if (t->element_mask & CMLCMS_TRANSFORMER_CURVE2)
|
||||
transformer_curve_fprint(fp, indent, "curve2", &t->curve2);
|
||||
}
|
||||
|
||||
char *
|
||||
cmlcms_color_transformer_string(int indent,
|
||||
const struct cmlcms_color_transformer *t)
|
||||
{
|
||||
FILE *fp;
|
||||
char *str = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
fp = open_memstream(&str, &size);
|
||||
abort_oom_if_null(fp);
|
||||
|
||||
fprintf(fp, "%*sColor transform sampler for 3D LUT\n", indent, "");
|
||||
cmlcms_color_transformer_details_fprint(fp, indent + 2, t);
|
||||
|
||||
fclose(fp);
|
||||
abort_oom_if_null(str);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ srcs_color_lcms = [
|
|||
'color-lcms.c',
|
||||
'color-profile.c',
|
||||
'color-transform.c',
|
||||
'color-transformer.c',
|
||||
]
|
||||
if (has_function_cmsGetToneCurveSegment)
|
||||
srcs_color_lcms += 'color-curve-segments.c'
|
||||
|
|
|
|||
|
|
@ -192,9 +192,9 @@ sample_pq(enum weston_tf_direction tf_direction,
|
|||
* @param out The output array of length @c len .
|
||||
* @param len The in and out arrays' length.
|
||||
*/
|
||||
void
|
||||
WL_EXPORT void
|
||||
weston_color_curve_sample(struct weston_compositor *compositor,
|
||||
struct weston_color_curve *curve,
|
||||
const struct weston_color_curve *curve,
|
||||
const struct weston_vec3f *in,
|
||||
struct weston_vec3f *out,
|
||||
size_t len)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
void
|
||||
weston_color_curve_sample(struct weston_compositor *compositor,
|
||||
struct weston_color_curve *curve,
|
||||
const struct weston_color_curve *curve,
|
||||
const struct weston_vec3f *in,
|
||||
struct weston_vec3f *out,
|
||||
size_t len);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue