color: introduce struct weston_color_tf

At first I wanted to wrap

	float tf_params[MAX_PARAMS_TF]

into a struct, so that it would become type-safe to pass into functions,
and it could be copied with a simple assignment. Then I noticed that for
tf_params to be operable, it must always be accompanied by struct
weston_color_tf_info. Hence, struct weston_color_tf was born.

The need for #define MAX_PARAMS_TF got eliminated.

Because struct weston_color_tf is a member of struct
weston_color_profile_params, now both need to not contain implicit
padding.

This patch makes the internal enumerated TF structures follow the
current protocol requests: all color channels use the same curve.

Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.com>
This commit is contained in:
Pekka Paalanen 2025-06-17 13:29:36 +03:00 committed by Pekka Paalanen
parent 7b5053f7fa
commit f3badf2c2d
7 changed files with 110 additions and 97 deletions

View file

@ -383,11 +383,15 @@ cmlcms_find_color_profile_by_params(const struct weston_color_manager_lcms *cm,
struct cmlcms_color_profile *cprof;
/* Ensure no uninitialized data inside struct to make memcmp work. */
static_assert(sizeof(params->tf) ==
sizeof(params->tf.info) +
sizeof(params->tf.params) +
sizeof(params->tf.padding),
"struct weston_color_transfer_function must not contain implicit padding");
static_assert(sizeof(*params) ==
2 * sizeof(float) * 2 * 4 + /* primaries, target_primaries */
sizeof(params->primaries_info) +
sizeof(params->tf_info) +
sizeof(params->tf_params) +
sizeof(params->tf) +
sizeof(params->min_luminance) +
sizeof(params->max_luminance) +
sizeof(params->reference_white_luminance) +
@ -708,7 +712,7 @@ cmlcms_get_color_profile_from_params(struct weston_color_manager *cm_base,
str_printf(&desc, "Parametric (%s): %s primaries, %s transfer function",
name_part,
params->primaries_info ? params->primaries_info->desc : "custom",
params->tf_info->desc);
params->tf.info->desc);
cprof = cmlcms_color_profile_alloc(cm, CMLCMS_PROFILE_TYPE_PARAMS, desc);
*cprof->params = *params;

View file

@ -450,20 +450,26 @@ init_curve_from_type_1(struct weston_compositor *compositor,
tf_info = lcms_curve_matches_any_tf(compositor, 1, clamped_input, type_1_params);
if (tf_info) {
curve->type = WESTON_COLOR_CURVE_TYPE_ENUM;
enumerated->tf = tf_info;
enumerated->tf = (struct weston_color_tf){
.info = tf_info,
.params = {}
};
enumerated->tf_direction = WESTON_FORWARD_TF;
return true;
}
/* This is a pure power-law with custom exp. If clamped_input == false,
* this matches WESTON_TF_POWER (parametric TF that is not clamped). */
if (!clamped_input) {
/* This is a pure power-law with custom exp. If clamped_input == false
* and all channels behave the same, this matches WESTON_TF_POWER. */
if (!clamped_input &&
type_1_params[0][0] == type_1_params[1][0] &&
type_1_params[0][0] == type_1_params[2][0]) {
tf_info = weston_color_tf_info_from(compositor, WESTON_TF_POWER);
curve->type = WESTON_COLOR_CURVE_TYPE_ENUM;
enumerated->tf = weston_color_tf_info_from(compositor,
WESTON_TF_POWER);
enumerated->tf = (struct weston_color_tf){
.info = tf_info,
.params = { type_1_params[0][0] }
};
enumerated->tf_direction = WESTON_FORWARD_TF;
for (i = 0; i < 3; i++)
enumerated->params[i][0] = type_1_params[i][0];
return true;
}
@ -519,29 +525,34 @@ init_curve_from_type_1_inverse(struct weston_compositor *compositor,
tf_info = lcms_curve_matches_any_tf(compositor, 1, clamped_input, type_1_params);
if (tf_info) {
curve->type = WESTON_COLOR_CURVE_TYPE_ENUM;
enumerated->tf = tf_info;
enumerated->tf = (struct weston_color_tf){
.info = tf_info,
.params = {}
};
enumerated->tf_direction = WESTON_INVERSE_TF;
return true;
}
/* This is the inverse of a pure power-law with custom exp. If
* clamped_input == false, this matches WESTON_TF_POWER (parametric TF
* that is not clamped). */
if (!clamped_input) {
curve->type = WESTON_COLOR_CURVE_TYPE_ENUM;
enumerated->tf = weston_color_tf_info_from(compositor,
WESTON_TF_POWER);
enumerated->tf_direction = WESTON_INVERSE_TF;
for (i = 0; i < 3; i++) {
g = type_1_params[i][0];
if (g == 0.0f) {
err_msg = "WARNING: xform has a LittleCMS type -1 curve " \
"(inverse of pure power-law) with exponent 1 " \
"divided by 0, which is invalid";
goto err;
}
enumerated->params[i][0] = g;
* clamped_input == false and all channels behave the same,
* this matches WESTON_TF_POWER. */
if (!clamped_input &&
type_1_params[0][0] == type_1_params[1][0] &&
type_1_params[0][0] == type_1_params[2][0]) {
if (type_1_params[0][0] == 0.0f) {
err_msg = "WARNING: xform has a LittleCMS type -1 curve " \
"(inverse of pure power-law) with exponent 1 " \
"divided by 0, which is invalid";
goto err;
}
tf_info = weston_color_tf_info_from(compositor, WESTON_TF_POWER);
curve->type = WESTON_COLOR_CURVE_TYPE_ENUM;
enumerated->tf = (struct weston_color_tf){
.info = tf_info,
.params = { type_1_params[0][0] }
};
enumerated->tf_direction = WESTON_INVERSE_TF;
return true;
}
@ -615,7 +626,10 @@ init_curve_from_type_4(struct weston_compositor *compositor,
tf_info = lcms_curve_matches_any_tf(compositor, 4, clamped_input, type_4_params);
if (tf_info) {
curve->type = WESTON_COLOR_CURVE_TYPE_ENUM;
enumerated->tf = tf_info;
enumerated->tf = (struct weston_color_tf){
.info = tf_info,
.params = {}
};
enumerated->tf_direction = WESTON_FORWARD_TF;
return true;
}
@ -695,7 +709,10 @@ init_curve_from_type_4_inverse(struct weston_compositor *compositor,
tf_info = lcms_curve_matches_any_tf(compositor, 4, clamped_input, type_4_params);
if (tf_info) {
curve->type = WESTON_COLOR_CURVE_TYPE_ENUM;
enumerated->tf = tf_info;
enumerated->tf = (struct weston_color_tf){
.info = tf_info,
.params = {}
};
enumerated->tf_direction = WESTON_INVERSE_TF;
return true;
}
@ -1370,14 +1387,9 @@ weston_color_curve_set_from_params(struct weston_color_curve *curve,
const struct weston_color_profile_params *p,
enum weston_tf_direction dir)
{
unsigned i;
curve->type = WESTON_COLOR_CURVE_TYPE_ENUM;
curve->u.enumerated.tf = p->tf_info;
curve->u.enumerated.tf = p->tf;
curve->u.enumerated.tf_direction = dir;
for (i = 0; i < 3; i++)
ARRAY_COPY(curve->u.enumerated.params[i], p->tf_params);
}
static void
@ -1961,9 +1973,9 @@ cmclms_adjust_recipe(struct cmlcms_color_transform_recipe *adjusted,
* to be targeting a display with the sRGB two-piece TF is likely mistaken.
*/
if (in_prof->type == CMLCMS_PROFILE_TYPE_PARAMS &&
in_prof->params->tf_info->tf == WESTON_TF_SRGB) {
in_prof->params->tf.info->tf == WESTON_TF_SRGB) {
tmp = *in_prof->params;
tmp.tf_info = weston_color_tf_info_from(cm->base.compositor, WESTON_TF_GAMMA22);
tmp.tf.info = weston_color_tf_info_from(cm->base.compositor, WESTON_TF_GAMMA22);
ret = cmlcms_get_color_profile_from_params(&cm->base,
&tmp, "override sRGB EOTF",
&replacement, &errmsg);
@ -1972,8 +1984,8 @@ cmclms_adjust_recipe(struct cmlcms_color_transform_recipe *adjusted,
"Replacing profile p%u (%s) with profile p%u (%s)"
"for color transformation.\n",
in_prof->base.id,
in_prof->params->tf_info->desc,
replacement->id, tmp.tf_info->desc);
in_prof->params->tf.info->desc,
replacement->id, tmp.tf.info->desc);
unref_cprof(adjusted->input_profile);
adjusted->input_profile = to_cmlcms_cprof(replacement);
} else {

View file

@ -195,7 +195,7 @@ weston_color_curve_sample(struct weston_compositor *compositor,
* Otherwise, fallback to a parametric curve and we'll handle
* that below.
*/
switch(curve->u.enumerated.tf->tf) {
switch (curve->u.enumerated.tf.info->tf) {
case WESTON_TF_ST2084_PQ:
sample_pq(curve->u.enumerated.tf_direction, ch, len, in, out);
return true;

View file

@ -310,9 +310,9 @@ weston_color_profile_param_builder_set_tf_named(struct weston_color_profile_para
if (!success)
return false;
builder->params.tf_info = weston_color_tf_info_from(compositor, tf);
builder->params.tf.info = weston_color_tf_info_from(compositor, tf);
weston_assert_uint_eq(builder->compositor,
builder->params.tf_info->count_parameters, 0);
builder->params.tf.info->count_parameters, 0);
builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_TF;
@ -368,8 +368,8 @@ weston_color_profile_param_builder_set_tf_power_exponent(struct weston_color_pro
if (!success)
return false;
builder->params.tf_info = weston_color_tf_info_from(compositor, WESTON_TF_POWER);
builder->params.tf_params[0] = power_exponent;
builder->params.tf.info = weston_color_tf_info_from(compositor, WESTON_TF_POWER);
builder->params.tf.params[0] = power_exponent;
builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_TF;
@ -757,7 +757,7 @@ builder_complete_params(struct weston_color_profile_param_builder *builder)
/* Some TF's override the default. Values comes from the CM&HDR
* protocol as well. */
if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TF) {
switch(builder->params.tf_info->tf) {
switch(builder->params.tf.info->tf) {
case WESTON_TF_BT1886:
builder->params.reference_white_luminance = 100.0;
builder->params.min_luminance = 0.01;
@ -781,7 +781,7 @@ builder_complete_params(struct weston_color_profile_param_builder *builder)
/* Primary luminance is set, but the CM&HDR protocol states that
* PQ TF should override max_lum with min_lum + 10000 cd/m². */
if ((builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TF) &&
builder->params.tf_info->tf == WESTON_TF_ST2084_PQ)
builder->params.tf.info->tf == WESTON_TF_ST2084_PQ)
builder->params.max_luminance =
builder->params.min_luminance + 10000.0;
}

View file

@ -166,12 +166,12 @@ weston_color_profile_params_to_str(struct weston_color_profile_params *params,
if (params->primaries_info)
fprintf(fp, "%sprimaries named: %s\n", ident, params->primaries_info->desc);
fprintf(fp, "%stransfer function: %s\n", ident, params->tf_info->desc);
fprintf(fp, "%stransfer function: %s\n", ident, params->tf.info->desc);
if (params->tf_info->count_parameters > 0) {
if (params->tf.info->count_parameters > 0) {
fprintf(fp, "%s params:", ident);
for (i = 0; i < params->tf_info->count_parameters; i++)
fprintf(fp, " %.4f", params->tf_params[i]);
for (i = 0; i < params->tf.info->count_parameters; i++)
fprintf(fp, " %.4f", params->tf.params[i]);
fprintf(fp, "\n");
}
@ -214,11 +214,11 @@ weston_color_curve_enum_get_parametric(struct weston_compositor *compositor,
memset(out, 0, sizeof(*out));
/* This one is special, the only parametric TF we currently have. */
if (curve->tf->tf == WESTON_TF_POWER) {
if (curve->tf.info->tf == WESTON_TF_POWER) {
out->type = WESTON_COLOR_CURVE_PARAMETRIC_TYPE_LINPOW;
out->clamped_input = false;
for (i = 0; i < 3; i++) {
float exp = curve->params[i][0];
float exp = curve->tf.params[0];
/* LINPOW with such params matches pure power-law */
out->params.chan[i].g = (curve->tf_direction == WESTON_FORWARD_TF) ?
exp : 1.0f / exp;
@ -231,15 +231,15 @@ weston_color_curve_enum_get_parametric(struct weston_compositor *compositor,
}
/* No other TF's have params. */
weston_assert_uint_eq(compositor, curve->tf->count_parameters, 0);
weston_assert_uint_eq(compositor, curve->tf.info->count_parameters, 0);
if (!curve->tf->curve_params_valid)
if (!curve->tf.info->curve_params_valid)
return false;
if (curve->tf_direction == WESTON_FORWARD_TF)
*out = curve->tf->curve;
*out = curve->tf.info->curve;
else
*out = curve->tf->inverse_curve;
*out = curve->tf.info->inverse_curve;
return true;
}
@ -254,9 +254,9 @@ curve_to_lut_has_good_precision(struct weston_color_curve *curve)
if (curve->type == WESTON_COLOR_CURVE_TYPE_ENUM) {
if (e->tf_direction == WESTON_INVERSE_TF) {
if (e->tf->tf == WESTON_TF_ST2084_PQ ||
e->tf->tf == WESTON_TF_GAMMA22 ||
e->tf->tf == WESTON_TF_GAMMA28) {
if (e->tf.info->tf == WESTON_TF_ST2084_PQ ||
e->tf.info->tf == WESTON_TF_GAMMA22 ||
e->tf.info->tf == WESTON_TF_GAMMA28) {
/**
* These have bad precision in the indirect
* direction.
@ -264,30 +264,26 @@ curve_to_lut_has_good_precision(struct weston_color_curve *curve)
return false;
}
if (e->tf->tf == WESTON_TF_POWER) {
if (e->tf.info->tf == WESTON_TF_POWER) {
/**
* Same as the above, but for parametric
* power-law transfer function. If g > 1.0
* it would result in bad precision.
*/
for (i = 0; i < 3; i++) {
g = e->params[i][0];
if (g > 1.0f)
return false;
}
g = e->tf.params[0];
if (g > 1.0f)
return false;
}
} else {
if (e->tf->tf == WESTON_TF_POWER) {
if (e->tf.info->tf == WESTON_TF_POWER) {
/**
* For parametric power-law transfer function
* in the forward direction, g < 1.0 would
* result in bad precision.
*/
for (i = 0; i < 3; i++) {
g = e->params[i][0];
if (g < 1.0f)
return false;
}
g = e->tf.params[0];
if (g < 1.0f)
return false;
}
}
} else if (curve->type == WESTON_COLOR_CURVE_TYPE_PARAMETRIC) {
@ -573,16 +569,14 @@ weston_color_curve_details_fprint(FILE *fp,
break;
case WESTON_COLOR_CURVE_TYPE_ENUM:
en = &curve->u.enumerated;
if (en->tf->count_parameters == 0)
if (en->tf.info->count_parameters == 0)
break;
fprintf(fp, "%*s%s, %s:\n", indent, "", step, en->tf->desc);
for (ch = 0; ch < 3; ch++) {
fprintf(fp, "%*s %s", indent, "", chan[ch]);
for (i = 0; i < en->tf->count_parameters; i++)
fprintf(fp, " % .4f", en->params[ch][i]);
fprintf(fp, "\n");
}
fprintf(fp, "%*s%s, %s:\n", indent, "", step, en->tf.info->desc);
fprintf(fp, "%*s R,G,B", indent, "");
for (i = 0; i < en->tf.info->count_parameters; i++)
fprintf(fp, " % .4f", en->tf.params[i]);
fprintf(fp, "\n");
break;
case WESTON_COLOR_CURVE_TYPE_PARAMETRIC:
par = &curve->u.parametric;
@ -674,7 +668,7 @@ weston_color_curve_fprint(FILE *fp, const struct weston_color_curve *curve)
case WESTON_COLOR_CURVE_TYPE_ENUM:
fprintf(fp, "(enum) %s%s",
curve->u.enumerated.tf_direction == WESTON_INVERSE_TF ? "inverse " : "",
curve->u.enumerated.tf->desc);
curve->u.enumerated.tf.info->desc);
break;
case WESTON_COLOR_CURVE_TYPE_PARAMETRIC:
fprintf(fp, "(parametric) %s",

View file

@ -33,11 +33,6 @@
#include "backend-drm/drm-kms-enums.h"
/**
* The only tf which is parametric (WESTON_TF_POWER) has a single parameter.
*/
#define MAX_PARAMS_TF 1
enum weston_hdr_metadata_type1_groups {
/** weston_hdr_metadata_type1::primary is set */
WESTON_HDR_METADATA_TYPE1_GROUP_PRIMARIES = 0x01,
@ -133,6 +128,21 @@ struct weston_color_profile {
uint32_t id;
};
/**
* Color transfer function
*
* Includes any parameter values the enumerated transfer function might need.
*/
struct weston_color_tf {
/** Encoding transfer characteristic by enumeration; always set. */
const struct weston_color_tf_info *info;
/** TF parameters, specific to TF. */
float params[1];
char padding[4];
};
/** Parameters that define a parametric color profile */
struct weston_color_profile_params {
/* Primary color volume; always set. */
@ -142,10 +152,7 @@ struct weston_color_profile_params {
const struct weston_color_primaries_info *primaries_info;
/* Encoding transfer characteristic by enumeration; always set. */
const struct weston_color_tf_info *tf_info;
/* Transfer characteristic's parameters; depends on tf_info. */
float tf_params[MAX_PARAMS_TF];
struct weston_color_tf tf;
/* Primary color volume luminance parameters cd/m²; always set. */
float min_luminance, max_luminance;
@ -158,7 +165,7 @@ struct weston_color_profile_params {
float target_min_luminance, target_max_luminance;
float maxCLL, maxFALL;
char padding[8];
char padding[4];
};
/** Type for parametric curves */
@ -257,14 +264,10 @@ enum weston_tf_direction {
/** Enumerated color curve */
struct weston_color_curve_enum {
const struct weston_color_tf_info *tf;
struct weston_color_tf tf;
/* Determines if the direct or inverse of the tf should be used. */
enum weston_tf_direction tf_direction;
/* Some tf are parametric, and we keep the params here. They may be
* different for each color channel, and channels are in RGB order. */
float params[3][MAX_PARAMS_TF];
};
/**

View file

@ -172,7 +172,7 @@ gl_color_curve_enum(struct gl_renderer *gr,
* Handle enum curve (if TF is implemented) or fallback to a parametric
* curve.
*/
switch(curve->u.enumerated.tf->tf) {
switch (curve->u.enumerated.tf.info->tf) {
case WESTON_TF_ST2084_PQ:
gl_curve->type = (curve->u.enumerated.tf_direction == WESTON_FORWARD_TF) ?
SHADER_COLOR_CURVE_PQ : SHADER_COLOR_CURVE_PQ_INVERSE;