Merge branch 'mr/paraicc2' into 'main'

Basics for ICC<->parametric color transformations

See merge request wayland/weston!1916
This commit is contained in:
Pekka Paalanen 2025-12-17 14:33:14 +02:00
commit 69efa1aa89
8 changed files with 562 additions and 36 deletions

View file

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

View file

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

View file

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

View file

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

View 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;
}

View file

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

View file

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

View file

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