Merge branch 'mr/stmeta' into 'main'

Remove weston_color_characteristics

See merge request wayland/weston!1956
This commit is contained in:
Pekka Paalanen 2026-02-03 11:37:09 +02:00
commit dced8ecfeb
24 changed files with 895 additions and 1745 deletions

View file

@ -2104,13 +2104,7 @@ wet_output_set_color_profile(struct weston_output *output,
} else if (parent_winsys_profile) {
cprof = weston_color_profile_ref(parent_winsys_profile);
} else {
/*
* TODO: Once parametric color profiles are fully supported
* and interoperable with ICC profiles, the default profile
* would be created like this:
* cprof = wet_create_output_color_profile(output, wc, "auto:");
*/
return 0;
cprof = wet_create_output_color_profile(output, wc, "auto:");
}
if (!cprof)
@ -2248,156 +2242,6 @@ wet_output_set_colorimetry_mode(struct weston_output *output,
return 0;
}
struct wet_color_characteristics_keys {
const char *name;
enum weston_color_characteristics_groups group;
float minval;
float maxval;
};
#define COLOR_CHARAC_NAME "color_characteristics"
static int
parse_color_characteristics(struct weston_color_characteristics *cc_out,
struct weston_config_section *section)
{
static const struct wet_color_characteristics_keys keys[] = {
{ "red_x", WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES, 0.0f, 1.0f },
{ "red_y", WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES, 0.0f, 1.0f },
{ "green_x", WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES, 0.0f, 1.0f },
{ "green_y", WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES, 0.0f, 1.0f },
{ "blue_x", WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES, 0.0f, 1.0f },
{ "blue_y", WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES, 0.0f, 1.0f },
{ "white_x", WESTON_COLOR_CHARACTERISTICS_GROUP_WHITE, 0.0f, 1.0f },
{ "white_y", WESTON_COLOR_CHARACTERISTICS_GROUP_WHITE, 0.0f, 1.0f },
{ "max_L", WESTON_COLOR_CHARACTERISTICS_GROUP_MAXL, 0.0f, 1e5f },
{ "min_L", WESTON_COLOR_CHARACTERISTICS_GROUP_MINL, 0.0f, 1e5f },
{ "maxFALL", WESTON_COLOR_CHARACTERISTICS_GROUP_MAXFALL, 0.0f, 1e5f },
};
static const char *msgpfx = "Config error in weston.ini [" COLOR_CHARAC_NAME "]";
struct weston_color_characteristics cc = {};
float *const keyvalp[ARRAY_LENGTH(keys)] = {
/* These must be in the same order as keys[]. */
&cc.primary[0].x, &cc.primary[0].y,
&cc.primary[1].x, &cc.primary[1].y,
&cc.primary[2].x, &cc.primary[2].y,
&cc.white.x, &cc.white.y,
&cc.max_luminance,
&cc.min_luminance,
&cc.maxFALL,
};
bool found[ARRAY_LENGTH(keys)] = {};
uint32_t missing_group_mask = 0;
unsigned i;
char *section_name;
int ret = 0;
weston_config_section_get_string(section, "name",
&section_name, "<unnamed>");
if (strchr(section_name, ':') != NULL) {
ret = -1;
weston_log("%s name=%s is a reserved name. Do not use ':' character in the name.\n",
msgpfx, section_name);
}
/* Parse keys if they exist */
for (i = 0; i < ARRAY_LENGTH(keys); i++) {
double value;
if (weston_config_section_get_double(section, keys[i].name,
&value, NAN) == 0) {
float f = value;
found[i] = true;
/* Range check, NaN shall not pass. */
if (f >= keys[i].minval && f <= keys[i].maxval) {
/* Key found, parsed, and good value. */
*keyvalp[i] = f;
continue;
}
ret = -1;
weston_log("%s name=%s: %s value %f is outside of the range %f - %f.\n",
msgpfx, section_name, keys[i].name, value,
keys[i].minval, keys[i].maxval);
continue;
}
if (errno == EINVAL) {
found[i] = true;
ret = -1;
weston_log("%s name=%s: failed to parse the value of key %s.\n",
msgpfx, section_name, keys[i].name);
}
}
/* Collect set and unset groups */
for (i = 0; i < ARRAY_LENGTH(keys); i++) {
uint32_t group = keys[i].group;
if (found[i])
cc.group_mask |= group;
else
missing_group_mask |= group;
}
/* Ensure groups are given fully or not at all. */
for (i = 0; i < ARRAY_LENGTH(keys); i++) {
uint32_t group = keys[i].group;
if ((cc.group_mask & group) && (missing_group_mask & group)) {
ret = -1;
weston_log("%s name=%s: group %d key %s is %s. "
"You must set either none or all keys of a group.\n",
msgpfx, section_name, ffs(group), keys[i].name,
found[i] ? "set" : "missing");
}
}
free(section_name);
if (ret == 0)
*cc_out = cc;
return ret;
}
WESTON_EXPORT_FOR_TESTS int
wet_output_set_color_characteristics(struct weston_output *output,
struct weston_config *wc,
struct weston_config_section *section)
{
char *cc_name = NULL;
struct weston_config_section *cc_section;
struct weston_color_characteristics cc;
weston_config_section_get_string(section, COLOR_CHARAC_NAME,
&cc_name, NULL);
if (!cc_name)
return 0;
cc_section = weston_config_get_section(wc, COLOR_CHARAC_NAME,
"name", cc_name);
if (!cc_section) {
weston_log("Config error in weston.ini, output %s: "
"no [" COLOR_CHARAC_NAME "] section with 'name=%s' found.\n",
output->name, cc_name);
goto out_error;
}
if (parse_color_characteristics(&cc, cc_section) < 0)
goto out_error;
weston_output_set_color_characteristics(output, &cc);
free(cc_name);
return 0;
out_error:
free(cc_name);
return -1;
}
static int
wet_output_set_color_format(struct weston_output *output,
struct weston_config_section *section)
@ -3203,10 +3047,6 @@ drm_backend_output_configure(struct weston_output *output,
if (wet_output_set_color_format(output, section) < 0)
return -1;
if (wet_output_set_color_characteristics(output,
wet->config, section) < 0)
return -1;
if (wet_output_set_vrr_mode(output, section) < 0)
return -1;
@ -4287,9 +4127,6 @@ headless_backend_output_configure(struct weston_output *output)
if (wet_output_set_colorimetry_mode(output, section, wet->use_color_manager) < 0)
return -1;
if (wet_output_set_color_characteristics(output, wc, section) < 0)
return -1;
return wet_configure_windowed_output_from_config(output, &defaults,
WESTON_WINDOWED_OUTPUT_HEADLESS);
}

View file

@ -36,11 +36,6 @@ bool
get_renderer_from_string(const char *name,
enum weston_renderer_type *renderer);
int
wet_output_set_color_characteristics(struct weston_output *output,
struct weston_config *wc,
struct weston_config_section *section);
int
wet_output_set_eotf_mode(struct weston_output *output,
struct weston_config_section *section,

View file

@ -286,57 +286,6 @@ weston_color_profile_param_builder_create_color_profile(struct weston_color_prof
enum weston_color_profile_param_builder_error *err,
char **err_msg);
enum weston_color_characteristics_groups {
/** weston_color_characteristics::primary is set */
WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES = 0x01,
/** weston_color_characteristics::white is set */
WESTON_COLOR_CHARACTERISTICS_GROUP_WHITE = 0x02,
/** weston_color_characteristics::max_luminance is set */
WESTON_COLOR_CHARACTERISTICS_GROUP_MAXL = 0x04,
/** weston_color_characteristics::min_luminance is set */
WESTON_COLOR_CHARACTERISTICS_GROUP_MINL = 0x08,
/** weston_color_characteristics::maxFALL is set */
WESTON_COLOR_CHARACTERISTICS_GROUP_MAXFALL = 0x10,
/** all valid bits */
WESTON_COLOR_CHARACTERISTICS_GROUP_ALL_MASK = 0x1f
};
/** Basic display color characteristics
*
* This is a simple description of a display or output (monitor) color
* characteristics. The parameters can be found in EDID, with caveats. They
* are particularly useful with HDR monitors.
*/
struct weston_color_characteristics {
/** Which fields are valid
*
* A bitmask of values from enum weston_color_characteristics_groups.
*/
uint32_t group_mask;
/* EOTF is tracked externally with enum weston_eotf_mode */
/** Chromaticities of the primaries */
struct weston_CIExy primary[3];
/** White point chromaticity */
struct weston_CIExy white;
/** Display's desired maximum content peak luminance, cd/m² */
float max_luminance;
/** Display's desired minimum content luminance, cd/m² */
float min_luminance;
/** Display's desired maximum frame-average light level, cd/m² */
float maxFALL;
};
struct weston_color_profile *
weston_color_profile_ref(struct weston_color_profile *cprof);

View file

@ -520,7 +520,6 @@ struct weston_output {
bool from_blend_to_output_by_backend;
enum weston_eotf_mode eotf_mode;
enum weston_colorimetry_mode colorimetry_mode;
struct weston_color_characteristics color_characteristics;
struct weston_output_color_outcome *color_outcome;
uint64_t color_outcome_serial;
@ -2695,13 +2694,6 @@ weston_output_set_colorimetry_mode(struct weston_output *output,
enum weston_colorimetry_mode
weston_output_get_colorimetry_mode(const struct weston_output *output);
void
weston_output_set_color_characteristics(struct weston_output *output,
const struct weston_color_characteristics *cc);
const struct weston_color_characteristics *
weston_output_get_color_characteristics(struct weston_output *output);
void
weston_output_init(struct weston_output *output,
struct weston_compositor *compositor,

View file

@ -531,7 +531,7 @@ join_powerlaw_curvesets(cmsContext context_id,
ret = cmsStageAllocToneCurves(context_id, ARRAY_LENGTH(arr), arr);
abort_oom_if_null(ret);
cmsFreeToneCurveTriple(arr);
return ret;
return ret;
}
void
@ -573,6 +573,56 @@ curveset_print(cmsStage *stage, struct weston_log_scope *scope)
}
}
static const cmsCurveSegment *
get_defining_curve_segment(cmsToneCurve *from, bool *clamped_input)
{
const cmsCurveSegment *seg0, *seg1, *seg2;
/* We handle curves with 1 or 3 segments. No more, no less. */
seg0 = cmsGetToneCurveSegment(0, from);
seg1 = cmsGetToneCurveSegment(1, from);
seg2 = cmsGetToneCurveSegment(2, from);
if (seg0 && !seg1) {
/* Case 1: we have a single segment (seg0). */
/* If the domain is (-inf, inf), the curve is unbounded. */
if (are_segment_breaks_equal(seg0->x0, -INFINITY) &&
are_segment_breaks_equal(seg0->x1, INFINITY)) {
*clamped_input = false;
return seg0;
}
/* If the domain is [0.0, 1.0], the curve is bounded. */
if (are_segment_breaks_equal(seg0->x0, 0.0) &&
are_segment_breaks_equal(seg0->x1, 1.0)) {
*clamped_input = true;
return seg0;
}
/* We don't handle anything else. */
return NULL;
} else if (seg0 && seg1 && seg2) {
/* Case 2: we have three segments. Clamped input.
*
* Ensure that the domain breaks are (-inf, 0.0],
* (0.0, 1.0] and (1.0, inf].
*/
if (!are_segment_breaks_equal(seg0->x0, -INFINITY) ||
!are_segment_breaks_equal(seg0->x1, 0.0) ||
!are_segment_breaks_equal(seg1->x0, 0.0) ||
!are_segment_breaks_equal(seg1->x1, 1.0) ||
!are_segment_breaks_equal(seg2->x0, 1.0) ||
!are_segment_breaks_equal(seg2->x1, INFINITY))
return NULL;
*clamped_input = true;
return seg1;
} else {
/* Neither 1 or 3 segments. So we don't define the
* curveset as parametric. */
return NULL;
}
}
bool
get_parametric_curveset_params(struct weston_compositor *compositor,
_cmsStageToneCurvesData *trc_data,
@ -580,7 +630,7 @@ get_parametric_curveset_params(struct weston_compositor *compositor,
float curveset_params[3][MAX_PARAMS_LCMS_PARAM_CURVE],
bool *clamped_input)
{
const cmsCurveSegment *seg, *seg0, *seg1, *seg2;
const cmsCurveSegment *seg;
cmsInt32Number curve_types[3];
unsigned int i, j;
@ -596,44 +646,18 @@ get_parametric_curveset_params(struct weston_compositor *compositor,
*clamped_input = false;
for (i = 0; i < 3; i++) {
/* We handle curves with 1 or 3 segments. No more, no less. */
seg0 = cmsGetToneCurveSegment(0, trc_data->TheCurves[i]);
seg1 = cmsGetToneCurveSegment(1, trc_data->TheCurves[i]);
seg2 = cmsGetToneCurveSegment(2, trc_data->TheCurves[i]);
bool clamp_this;
if (seg0 && !seg1) {
/* Case 1: we have a single segment (seg0).
*
* Ensure that the domain is (-inf, inf) and that the
* seg type is not 0 (the type of sampled segments).
*/
if (!are_segment_breaks_equal(seg0->x0, -INFINITY) ||
!are_segment_breaks_equal(seg0->x1, INFINITY) ||
seg0->Type == 0)
return false;
seg = seg0;
} else if (seg0 && seg1 && seg2) {
/* Case 2: we have three segments. Clamped input.
*
* Ensure that the domain breaks are (-inf, 0.0],
* (0.0, 1.0] and (1.0, inf] and that the 2nd segment
* type is not 0 (the type of sampled segments).
*/
if (!are_segment_breaks_equal(seg0->x0, -INFINITY) ||
!are_segment_breaks_equal(seg0->x1, 0.0) ||
!are_segment_breaks_equal(seg1->x0, 0.0) ||
!are_segment_breaks_equal(seg1->x1, 1.0) ||
!are_segment_breaks_equal(seg2->x0, 1.0) ||
!are_segment_breaks_equal(seg2->x1, INFINITY) ||
seg1->Type == 0)
return false;
seg = seg1;
*clamped_input = true;
} else {
/* Neither 1 or 3 segments. So we don't define the
* curveset as parametric. */
seg = get_defining_curve_segment(trc_data->TheCurves[i], &clamp_this);
if (!seg)
return false;
}
/* Reject tabulated (LUT) segments. */
if (seg->Type == 0)
return false;
if (clamp_this)
*clamped_input = true;
/* Copy the type and params from the segment that matters. We
* don't use memcpy because we need to cast each cmsFloat64Number
@ -652,3 +676,58 @@ get_parametric_curveset_params(struct weston_compositor *compositor,
return true;
}
/** Create a matrix stage equivalent to the CurveSet stage.
*
* A tabulated curve segment with 2 samples is equivalent to a matrix
* (scaling and offset), ignoring possible input clamping and allowing
* extrapolation.
*
* \param context_id The matrix stage is created in this context.
* \param stage An arbitrary stage, can be NULL.
* \return A new matrix stage equivalent to the given (CurveSet) stage, or
* NULL otherwise.
*/
cmsStage *
lcms_matrix_stage_from_curve(cmsContext context_id, cmsStage *stage)
{
const _cmsStageToneCurvesData *data;
cmsFloat64Number Matrix[3 * 3] = {}; /* row-major */
cmsFloat64Number Offset[3];
unsigned i;
if (!stage || cmsStageType(stage) != cmsSigCurveSetElemType)
return NULL;
data = cmsStageData(stage);
if (data->nCurves != 3)
return NULL;
for (i = 0; i < 3; i++) {
const cmsCurveSegment *seg;
bool clamped_input;
double y0, y1, k;
seg = get_defining_curve_segment(data->TheCurves[i], &clamped_input);
if (!seg)
return NULL;
/* Type 0 is tabulated. */
if (seg->Type != 0)
return NULL;
if (seg->nGridPoints != 2)
return NULL;
y0 = seg->SampledPoints[0];
y1 = seg->SampledPoints[1];
/* y = k * x + Offset */
k = (y1 - y0) / (seg->x1 - seg->x0);
Offset[i] = y0 - k * seg->x0;
Matrix[3 * i + i] = k;
}
return cmsStageAllocMatrix(context_id, 3, 3, Matrix, Offset);
}

View file

@ -39,6 +39,9 @@ get_parametric_curveset_params(struct weston_compositor *compositor,
float curveset_params[3][MAX_PARAMS_LCMS_PARAM_CURVE],
bool *clamped_input);
cmsStage *
lcms_matrix_stage_from_curve(cmsContext context_id, cmsStage *stage);
void
curveset_print(cmsStage *stage, struct weston_log_scope *scope);
@ -64,6 +67,12 @@ get_parametric_curveset_params(struct weston_compositor *compositor,
return false;
}
cmsStage *
lcms_matrix_stage_from_curve(cmsContext ContextID, cmsStage *stage)
{
return NULL;
}
static inline void
curveset_print(cmsStage *stage, struct weston_log_scope *scope)
{

View file

@ -195,7 +195,7 @@ cmlcms_get_sRGB_to_blend_color_transform(struct weston_color_manager_lcms *cm,
static float
meta_clamp(float value, const char *valname, float min, float max,
struct weston_output *output)
const struct cmlcms_color_profile *cprof)
{
float ret = value;
@ -207,78 +207,71 @@ meta_clamp(float value, const char *valname, float min, float max,
ret = max;
if (ret != value) {
weston_log("output '%s' clamping %s value from %f to %f.\n",
output->name, valname, value, ret);
weston_log("Output profile p%u '%s': clamping %s value from %f to %f.\n",
cprof->base.id, cprof->base.description, valname, value, ret);
}
return ret;
}
static bool
cmlcms_get_hdr_meta(struct weston_output *output,
struct weston_hdr_metadata_type1 *hdr_meta)
static void
cmlcms_get_hdr_meta(struct weston_hdr_metadata_type1 *hdr_meta,
const struct cmlcms_color_profile *cprof)
{
const struct weston_color_characteristics *cc;
/* TODO: get color characteristics from color profiles instead. */
const struct weston_color_profile_params *cpp = NULL;
unsigned i;
hdr_meta->group_mask = 0;
/* Only SMPTE ST 2084 mode uses HDR Static Metadata Type 1 */
if (weston_output_get_eotf_mode(output) != WESTON_EOTF_MODE_ST2084)
return true;
switch (cprof->type) {
case CMLCMS_PROFILE_TYPE_ICC:
/* No HDR metadata */
return;
cc = weston_output_get_color_characteristics(output);
/* Target content chromaticity */
if (cc->group_mask & WESTON_COLOR_CHARACTERISTICS_GROUP_PRIMARIES) {
unsigned i;
for (i = 0; i < 3; i++) {
hdr_meta->primary[i].x = meta_clamp(cc->primary[i].x,
"primary", 0.0, 1.0,
output);
hdr_meta->primary[i].y = meta_clamp(cc->primary[i].y,
"primary", 0.0, 1.0,
output);
}
hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_PRIMARIES;
case CMLCMS_PROFILE_TYPE_PARAMS:
cpp = cprof->params;
break;
}
weston_assert_ptr_not_null(cprof->base.cm->compositor, cpp);
/* Target content white point */
if (cc->group_mask & WESTON_COLOR_CHARACTERISTICS_GROUP_WHITE) {
hdr_meta->white.x = meta_clamp(cc->white.x, "white",
0.0, 1.0, output);
hdr_meta->white.y = meta_clamp(cc->white.y, "white",
0.0, 1.0, output);
hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_WHITE;
for (i = 0; i < 3; i++) {
hdr_meta->primary[i].x = meta_clamp(cpp->target_primaries.primary[i].x,
"primary", 0.0, 1.0, cprof);
hdr_meta->primary[i].y = meta_clamp(cpp->target_primaries.primary[i].y,
"primary", 0.0, 1.0, cprof);
}
hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_PRIMARIES;
/* Target content peak and max mastering luminance */
if (cc->group_mask & WESTON_COLOR_CHARACTERISTICS_GROUP_MAXL) {
hdr_meta->maxDML = meta_clamp(cc->max_luminance, "maxDML",
1.0, 65535.0, output);
hdr_meta->maxCLL = meta_clamp(cc->max_luminance, "maxCLL",
1.0, 65535.0, output);
hdr_meta->white.x = meta_clamp(cpp->target_primaries.white_point.x,
"white", 0.0, 1.0, cprof);
hdr_meta->white.y = meta_clamp(cpp->target_primaries.white_point.y,
"white", 0.0, 1.0, cprof);
hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_WHITE;
if (cpp->target_max_luminance > 0.0f) {
hdr_meta->maxDML = meta_clamp(cpp->target_max_luminance,
"maxDML", 1.0, 65535.0, cprof);
hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_MAXDML;
hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_MAXCLL;
}
/* Target content min mastering luminance */
if (cc->group_mask & WESTON_COLOR_CHARACTERISTICS_GROUP_MINL) {
hdr_meta->minDML = meta_clamp(cc->min_luminance, "minDML",
0.0001, 6.5535, output);
if (cpp->target_min_luminance > 0.0f) {
hdr_meta->minDML = meta_clamp(cpp->target_min_luminance,
"minDML", 0.0001, 6.5535, cprof);
hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_MINDML;
}
/* Target content max frame-average luminance */
if (cc->group_mask & WESTON_COLOR_CHARACTERISTICS_GROUP_MAXFALL) {
hdr_meta->maxFALL = meta_clamp(cc->maxFALL, "maxFALL",
1.0, 65535.0, output);
if (cpp->maxFALL > 0.0f) {
hdr_meta->maxFALL = meta_clamp(cpp->maxFALL,
"maxFALL", 1.0, 65535.0, cprof);
hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_MAXFALL;
}
return true;
if (cpp->maxCLL > 0.0f) {
hdr_meta->maxCLL = meta_clamp(cpp->maxCLL,
"maxCLL", 1.0, 65535.0, cprof);
hdr_meta->group_mask |= WESTON_HDR_METADATA_TYPE1_GROUP_MAXCLL;
}
}
static struct weston_output_color_outcome *
@ -288,14 +281,13 @@ cmlcms_create_output_color_outcome(struct weston_color_manager *cm_base,
struct weston_color_manager_lcms *cm = to_cmlcms(cm_base);
struct weston_output_color_outcome *co;
weston_assert_ptr_not_null(cm->base.compositor, output->color_profile);
co = zalloc(sizeof *co);
if (!co)
return NULL;
if (!cmlcms_get_hdr_meta(output, &co->hdr_meta))
goto out_fail;
assert(output->color_profile);
cmlcms_get_hdr_meta(&co->hdr_meta, to_cmlcms_cprof(output->color_profile));
/* TODO: take container color space into account */
@ -345,6 +337,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);
}
}
}
@ -423,19 +421,13 @@ 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");
goto out_err;
}
cm->sRGB_profile = cmlcms_create_stock_profile(cm);
weston_log("LittleCMS %d initialized.\n", cmsGetEncodedCMMversion());
return true;
out_err:
if (cm->lcms_ctx)
cmsDeleteContext(cm->lcms_ctx);
cm->lcms_ctx = NULL;
weston_log_scope_destroy(cm->transforms_scope);
cm->transforms_scope = NULL;
weston_log_scope_destroy(cm->optimizer_scope);
@ -514,7 +506,6 @@ weston_color_manager_create(struct weston_compositor *compositor)
cm->base.get_surface_color_transform = cmlcms_get_surface_color_transform;
cm->base.create_output_color_outcome = cmlcms_create_output_color_outcome;
/* We support all color features. */
cm->base.supported_color_features = (1 << WESTON_COLOR_FEATURE_ICC) |
(1 << WESTON_COLOR_FEATURE_PARAMETRIC) |
(1 << WESTON_COLOR_FEATURE_SET_PRIMARIES) |
@ -523,14 +514,12 @@ weston_color_manager_create(struct weston_compositor *compositor)
(1 << WESTON_COLOR_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES) |
(1 << WESTON_COLOR_FEATURE_EXTENDED_TARGET_VOLUME);
/* We support all rendering intents. */
cm->base.supported_rendering_intents = (1 << WESTON_RENDER_INTENT_PERCEPTUAL) |
(1 << WESTON_RENDER_INTENT_SATURATION) |
/* (1 << WESTON_RENDER_INTENT_SATURATION) | */
(1 << WESTON_RENDER_INTENT_ABSOLUTE) |
(1 << WESTON_RENDER_INTENT_RELATIVE) |
(1 << WESTON_RENDER_INTENT_RELATIVE_BPC);
/* We support all primaries named. */
cm->base.supported_primaries_named = (1 << WESTON_PRIMARIES_CICP_SRGB) |
(1 << WESTON_PRIMARIES_CICP_PAL_M) |
(1 << WESTON_PRIMARIES_CICP_PAL) |
@ -542,18 +531,10 @@ weston_color_manager_create(struct weston_compositor *compositor)
(1 << WESTON_PRIMARIES_CICP_DISPLAY_P3) |
(1 << WESTON_PRIMARIES_ADOBE_RGB);
/**
* TODO: this is a lie just to make the color-management-parametric
* tests to work. Without this the tests would be much more limited. We
* actually need to implement such TF's. There's no problem doing that,
* as parametric color profiles themselves are still unsupported.
*/
/* We need to implement each tf, and we support only a few of them. */
cm->base.supported_tf_named = (1 << WESTON_TF_GAMMA22) |
(1 << WESTON_TF_GAMMA28) |
(1 << WESTON_TF_SRGB) |
(1 << WESTON_TF_ST2084_PQ);
(1 << WESTON_TF_ST2084_PQ) |
(1 << WESTON_TF_EXT_LINEAR);
wl_list_init(&cm->color_transform_list);
wl_list_init(&cm->color_profile_list);

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,26 @@ 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_color_mapping_matrix lin1;
cmsHTRANSFORM icc_chain;
struct weston_color_mapping_matrix lin2;
struct weston_color_curve curve2;
};
struct cmlcms_color_transform_recipe {
enum cmlcms_category category;
struct cmlcms_color_profile *input_profile;
@ -228,13 +249,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
@ -273,7 +292,7 @@ ref_cprof(struct cmlcms_color_profile *cprof);
void
unref_cprof(struct cmlcms_color_profile *cprof);
bool
struct cmlcms_color_profile *
cmlcms_create_stock_profile(struct weston_color_manager_lcms *cm);
void
@ -298,4 +317,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

@ -558,57 +558,36 @@ make_icc_file_description(struct lcmsProfilePtr profile,
* BT.709 primaries with gamma-2.2 transfer characteristic. This is the
* expected sRGB display response.
*/
bool
struct cmlcms_color_profile *
cmlcms_create_stock_profile(struct weston_color_manager_lcms *cm)
{
static const cmsCIExyY D65 = { 0.3127, 0.3290, 1.0 };
static const cmsCIExyYTRIPLE bt709 = {
{ 0.6400, 0.3300, 1.0 },
{ 0.3000, 0.6000, 1.0 },
{ 0.1500, 0.0600, 1.0 }
};
cmsToneCurve *gamma22[3];
struct lcmsProfilePtr profile = { NULL };
struct cmlcms_md5_sum md5sum;
struct weston_compositor *compositor = cm->base.compositor;
struct weston_color_profile_params p = {};
struct cmlcms_color_profile *stock;
char *desc = NULL;
const char *err_msg = NULL;
gamma22[0] = gamma22[1] = gamma22[2] = cmsBuildGamma(cm->lcms_ctx, 2.2);
if (gamma22[0])
profile.p = cmsCreateRGBProfileTHR(cm->lcms_ctx, &D65, &bt709, gamma22);
cmsFreeToneCurve(gamma22[0]);
if (!profile.p) {
weston_log("color-lcms: error: failed to create stock sRGB profile.\n");
return false;
}
if (!cmsMD5computeID(profile.p)) {
weston_log("Failed to compute MD5 for stock sRGB profile.\n");
goto err_close;
}
p.primaries_info = weston_color_primaries_info_from(compositor,
WESTON_PRIMARIES_CICP_SRGB);
p.primaries = p.primaries_info->color_gamut;
p.tf.info = weston_color_tf_info_from(compositor, WESTON_TF_GAMMA22);
p.reference_white_luminance = 80.0;
p.min_luminance = 0.2;
p.max_luminance = 80.0;
p.target_primaries = p.primaries;
p.target_min_luminance = p.min_luminance;
p.target_max_luminance = p.max_luminance;
p.maxCLL = -1.0f;
p.maxFALL = -1.0f;
cmsGetHeaderProfileID(profile.p, md5sum.bytes);
desc = make_icc_file_description(profile, &md5sum, "sRGB stock");
if (!desc)
goto err_close;
str_printf(&desc, "default sRGB: %s primaries, %s transfer function",
p.primaries_info->desc, p.tf.info->desc);
abort_oom_if_null(desc);
cm->sRGB_profile = cmlcms_color_profile_alloc(cm, CMLCMS_PROFILE_TYPE_ICC, desc);
cm->sRGB_profile->icc.profile = profile;
cm->sRGB_profile->icc.md5sum = md5sum;
stock = cmlcms_color_profile_alloc(cm, CMLCMS_PROFILE_TYPE_PARAMS, desc);
*stock->params = p;
cmlcms_color_profile_register(stock);
if (!ensure_output_profile_extract(cm->sRGB_profile, cm->lcms_ctx,
cmlcms_reasonable_1D_points(), &err_msg))
goto err_close;
cmlcms_color_profile_register(cm->sRGB_profile);
return true;
err_close:
if (err_msg)
weston_log("%s\n", err_msg);
cmlcms_color_profile_destroy(cm->sRGB_profile);
return false;
return stock;
}
struct weston_color_profile *
@ -719,84 +698,47 @@ cmlcms_get_color_profile_from_params(struct weston_color_manager *cm_base,
return true;
}
static bool
cmlcms_send_icc_info(struct cm_image_desc_info *cm_image_desc_info,
const struct cmlcms_color_profile *cprof)
{
int32_t fd;
uint32_t len;
/* ICC-based color profile, so just send the ICC file fd. If we
* get an error (negative fd), the helper will send the proper
* error to the client. */
fd = os_ro_anonymous_file_get_fd(cprof->icc.prof_rofile,
RO_ANONYMOUS_FILE_MAPMODE_PRIVATE);
if (fd < 0) {
weston_cm_send_icc_file(cm_image_desc_info, -1, 0);
return false;
}
len = os_ro_anonymous_file_size(cprof->icc.prof_rofile);
weston_assert_u32_gt(cprof->base.cm->compositor, len, 0);
weston_cm_send_icc_file(cm_image_desc_info, fd, len);
os_ro_anonymous_file_put_fd(fd);
return true;
}
bool
cmlcms_send_image_desc_info(struct cm_image_desc_info *cm_image_desc_info,
struct weston_color_profile *cprof_base)
{
struct weston_color_manager_lcms *cm = to_cmlcms(cprof_base->cm);
struct weston_compositor *compositor = cm->base.compositor;
struct cmlcms_color_profile *cprof = to_cmlcms_cprof(cprof_base);
const struct weston_color_primaries_info *primaries_info;
const struct weston_color_tf_info *tf_info;
int32_t fd;
uint32_t len;
/**
* TODO: when we convert the stock sRGB profile to a parametric profile
* instead of an ICC one, we'll be able to change the if/else below to
* a switch/case.
*/
if (cprof->type == CMLCMS_PROFILE_TYPE_ICC && cprof != cm->sRGB_profile) {
/* ICC-based color profile, so just send the ICC file fd. If we
* get an error (negative fd), the helper will send the proper
* error to the client. */
fd = os_ro_anonymous_file_get_fd(cprof->icc.prof_rofile,
RO_ANONYMOUS_FILE_MAPMODE_PRIVATE);
if (fd < 0) {
weston_cm_send_icc_file(cm_image_desc_info, -1, 0);
return false;
}
len = os_ro_anonymous_file_size(cprof->icc.prof_rofile);
weston_assert_u32_gt(compositor, len, 0);
weston_cm_send_icc_file(cm_image_desc_info, fd, len);
os_ro_anonymous_file_put_fd(fd);
} else {
/* TODO: we still don't support parametric color profiles that
* are not the stock one. This should change when we start
* advertising parametric image description support in our
* color-management protocol implementation. */
if (cprof != cm->sRGB_profile)
weston_assert_not_reached(compositor, "we don't support parametric " \
"cprof's that are not the stock sRGB one");
/* Stock sRGB color profile. TODO: when we add support for
* parametric color profiles, the stock sRGB will be crafted
* using parameters, instead of cmsCreate_sRGBProfileTHR()
* (which we currently use). So we'll get the parameters
* directly from it, instead of hardcoding as we are doing here.
* We don't get the parameters from the stock sRGB color profile
* because it is not trivial to retrieve that from LittleCMS. */
/* Send the H.273 ColourPrimaries code point that matches the
* Rec709 primaries and the D65 white point. */
primaries_info = weston_color_primaries_info_from(compositor,
WESTON_PRIMARIES_CICP_SRGB);
weston_cm_send_primaries_named(cm_image_desc_info, primaries_info);
/* These are the Rec709 primaries and D65 white point. */
weston_cm_send_primaries(cm_image_desc_info,
&primaries_info->color_gamut);
/* Target primaries, equal to the primary primaries. */
weston_cm_send_target_primaries(cm_image_desc_info,
&primaries_info->color_gamut);
/* sRGB transfer function. */
tf_info = weston_color_tf_info_from(compositor, WESTON_TF_GAMMA22);
weston_cm_send_tf_named(cm_image_desc_info, tf_info);
/* Primary luminance, default values from the protocol. */
weston_cm_send_luminances(cm_image_desc_info, 0.2, 80.0, 80.0);
/* Target luminance, min/max equals primary luminance min/max. */
weston_cm_send_target_luminances(cm_image_desc_info, 0.2, 80.0);
switch (cprof->type) {
case CMLCMS_PROFILE_TYPE_ICC:
return cmlcms_send_icc_info(cm_image_desc_info, cprof);
case CMLCMS_PROFILE_TYPE_PARAMS:
weston_cm_send_parametric_info(cm_image_desc_info, cprof->params);
return true;
}
return true;
return false;
}
struct weston_color_profile *

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);
@ -390,6 +387,30 @@ merge_curvesets(cmsPipeline **lut, cmsContext context_id)
return modified;
}
static void
linear_curvesets_to_matrices(cmsPipeline **lut, cmsContext context_id)
{
cmsPipeline *pipe;
cmsStage *elem;
cmsStage *matrix;
pipe = cmsPipelineAlloc(context_id, 3, 3);
abort_oom_if_null(pipe);
elem = cmsPipelineGetPtrToFirstStage(*lut);
for (; elem; elem = cmsStageNext(elem)) {
matrix = lcms_matrix_stage_from_curve(context_id, elem);
if (matrix) {
cmsPipelineInsertStage(pipe, cmsAT_END, matrix);
} else {
cmsPipelineInsertStage(pipe, cmsAT_END, cmsStageDup(elem));
}
}
cmsPipelineFree(*lut);
*lut = pipe;
}
static const struct weston_color_tf_info *
lcms_curve_matches_any_tf(struct weston_compositor *compositor,
uint32_t lcms_curve_type, bool clamped_input,
@ -1016,6 +1037,8 @@ lcms_optimize_pipeline(cmsPipeline **lut, cmsContext context_id)
{
bool cont_opt;
linear_curvesets_to_matrices(lut, context_id);
/**
* This optimization loop will delete identity stages. Deleting
* identity matrix stages is harmless, but deleting identity
@ -1378,11 +1401,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 +1416,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 +1460,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 +1680,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 +1694,14 @@ 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 = xform->base.mapping.u.mat;
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 +1714,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 +1727,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 +2058,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 +2141,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 +2183,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 +2214,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 +2255,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 +2271,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 +2330,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 +2357,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,182 @@
/*
* 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(compositor, 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,
const struct weston_color_mapping_matrix *lin)
{
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", lin->matrix.col[c].el[r]);
fprintf(fp, " %8.4f\n", lin->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);
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);
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

@ -209,15 +209,34 @@ weston_cm_send_target_primaries(struct cm_image_desc_info *cm_image_desc_info,
* This is a helper function that should be used by the color plugin
* that owns the color profile and has information about it.
*
* \param cm_image_desc_info The image description info object
* \param tf_info The tf_info object
* \param cm_image_desc_info The image description info object.
* \param tf The color transfer function to send.
*/
WL_EXPORT void
weston_cm_send_tf_named(struct cm_image_desc_info *cm_image_desc_info,
const struct weston_color_tf_info *tf_info)
weston_cm_send_tf(struct cm_image_desc_info *cm_image_desc_info,
const struct weston_color_tf *tf)
{
wp_image_description_info_v1_send_tf_named(cm_image_desc_info->owner,
tf_info->protocol_tf);
switch (tf->info->tf) {
case WESTON_TF_BT1886:
case WESTON_TF_GAMMA22:
case WESTON_TF_GAMMA28:
case WESTON_TF_SRGB:
case WESTON_TF_EXT_SRGB:
case WESTON_TF_ST240:
case WESTON_TF_ST428:
case WESTON_TF_ST2084_PQ:
case WESTON_TF_EXT_LINEAR:
case WESTON_TF_LOG_100:
case WESTON_TF_LOG_316:
case WESTON_TF_XVYCC:
case WESTON_TF_HLG:
wp_image_description_info_v1_send_tf_named(cm_image_desc_info->owner,
tf->info->protocol_tf);
break;
case WESTON_TF_POWER:
wp_image_description_info_v1_send_tf_power(cm_image_desc_info->owner,
tf->params[0]);
}
}
/**
@ -261,6 +280,37 @@ weston_cm_send_target_luminances(struct cm_image_desc_info *cm_image_desc_info,
max_lum);
}
/**
* Send complete parametric image description information to the client.
*/
WL_EXPORT void
weston_cm_send_parametric_info(struct cm_image_desc_info *cm_image_desc_info,
const struct weston_color_profile_params *par)
{
if (par->primaries_info)
weston_cm_send_primaries_named(cm_image_desc_info, par->primaries_info);
weston_cm_send_primaries(cm_image_desc_info, &par->primaries);
weston_cm_send_target_primaries(cm_image_desc_info, &par->target_primaries);
weston_cm_send_tf(cm_image_desc_info, &par->tf);
weston_cm_send_luminances(cm_image_desc_info, par->min_luminance,
par->max_luminance, par->reference_white_luminance);
weston_cm_send_target_luminances(cm_image_desc_info,
par->target_min_luminance,
par->target_max_luminance);
if (par->maxCLL > 0.0f) {
wp_image_description_info_v1_send_target_max_cll(cm_image_desc_info->owner,
par->maxCLL);
}
if (par->maxFALL > 0.0f) {
wp_image_description_info_v1_send_target_max_fall(cm_image_desc_info->owner,
par->maxFALL);
}
}
/**
* Destroy an image description info object.
*/

View file

@ -56,8 +56,8 @@ weston_cm_send_target_primaries(struct cm_image_desc_info *cm_image_desc_info,
const struct weston_color_gamut *color_gamut);
void
weston_cm_send_tf_named(struct cm_image_desc_info *cm_image_desc_info,
const struct weston_color_tf_info *tf_info);
weston_cm_send_tf(struct cm_image_desc_info *cm_image_desc_info,
const struct weston_color_tf *tf);
void
weston_cm_send_luminances(struct cm_image_desc_info *cm_image_desc_info,
@ -67,4 +67,8 @@ void
weston_cm_send_target_luminances(struct cm_image_desc_info *cm_image_desc_info,
float min_lum, float max_lum);
void
weston_cm_send_parametric_info(struct cm_image_desc_info *cm_image_desc_info,
const struct weston_color_profile_params *par);
#endif /* WESTON_COLOR_MANAGEMENT_H */

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

View file

@ -90,7 +90,7 @@ struct weston_hdr_metadata_type1 {
float maxFALL;
};
/** Output properties derived from its color characteristics and profile
/** Output properties derived from its color profile
*
* These are constructed by a color manager.
*

View file

@ -8150,50 +8150,6 @@ weston_output_get_hdr_metadata_type1(const struct weston_output *output)
return &output->color_outcome->hdr_meta;
}
/** Set display or monitor basic color characteristics
*
* \param output The output to modify, must be in disabled state.
* \param cc The new characteristics to set, or NULL to unset everything.
*
* This sets the metadata that describes the color characteristics of the
* output in a very simple manner. If a non-NULL color profile is set for the
* output, that will always take precedence.
*
* The initial value has everything unset.
*
* This function is meant to be used by compositor frontends.
*
* \ingroup output
* \sa weston_output_set_color_profile
*/
WL_EXPORT void
weston_output_set_color_characteristics(struct weston_output *output,
const struct weston_color_characteristics *cc)
{
assert(!output->enabled);
if (cc)
output->color_characteristics = *cc;
else
output->color_characteristics.group_mask = 0;
}
/** Get display or monitor basic color characteristics
*
* \param output The output to query.
* \return Pointer to the metadata stored in weston_output.
*
* This function is meant to be used by color manager modules.
*
* \ingroup output
* \sa weston_output_set_color_characteristics
*/
WL_EXPORT const struct weston_color_characteristics *
weston_output_get_color_characteristics(struct weston_output *output)
{
return &output->color_characteristics;
}
WL_EXPORT void
weston_output_set_single_mode(struct weston_output *output,
struct weston_mode *target)

View file

@ -597,11 +597,6 @@ on DRM, headless, wayland, and x11 backends, and for remoting and pipewire
outputs.
.TP 7
.BI "color-profile=" name
.B NOTE:
This feature is not fully implemented yet, and therefore it is not expected to
work.
Color profiles describe how the display is assumed to behave. Knowing the
display color behavior, Weston can adapt window contents to produce the
presumed appearance. The option
@ -619,7 +614,7 @@ section by referring to it by its
value. Alternatively this key can use one of the special profile names that
contain a colon (:).
The default, once implemented, will be
The default is
.BR "color-profile=auto:" .
The following special names are accepted:
@ -786,13 +781,6 @@ The mode can be one of the following strings:
Defaults to
.BR sdr ". Non-SDR modes require " "color-management=true" .
.TP 7
.BI "color_characteristics=" name
Sets the basic output color characteristics by loading the parameters from the
.B color_characteristics
section with the key
.BI "name=" name
\&. If an ICC profile is also set, the ICC profile takes precedence.
.TP 7
.BI "mirror-of=" ouput_name
Makes the remote output overlap (mirror) the native output identified by the
mirror-of value. This is useful for sharing or mirroring a native DRM output.
@ -924,69 +912,6 @@ parallel to Weston, so it does not have to immediately exit. Defaults to empty.
If set to true, quit Weston after the auto-launched executable exits. Set to false
by default.
.\"---------------------------------------------------------------------
.SH "COLOR_CHARACTERISTICS SECTION"
Each
.B color_characteristics
section records one set of basic display or monitor color characterisation
parameters. The parameters are defined in CTA-861-H specification as Static
Metadata Type 1, and they can also be found in EDID. The parameters are
divided into groups. Each group must be given either fully or not at all.
.PP
Each section should be named with
.B name
key by which it can be referenced from other sections. A metadata section is
just a collection of parameter values and does nothing on its own. It has an
effect only when referenced from elsewhere.
.PP
See
.BR output " section key " color_characteristics .
.TP 7
.BI "name=" name
An arbitrary name for this section. You can choose any name you want as long as
it does not contain the colon
.RB ( : )
character. Names with at least one colon are reserved.
.SS Primaries group
.TP 7
.BI "red_x=" x
.TQ
.BI "red_y=" y
.TQ
.BI "green_x=" x
.TQ
.BI "green_y=" y
.TQ
.BI "blue_x=" x
.TQ
.BI "blue_y=" y
The CIE 1931 xy chromaticity coordinates of the display primaries.
These floating point values must reside between 0.0 and 1.0, inclusive.
.SS White point group
.TP 7
.BI "white_x=" x
.TQ
.BI "white_y=" y
The CIE 1931 xy chromaticity coordinates of the display white point.
These floating point values must reside between 0.0 and 1.0, inclusive.
.SS Independent parameters
Each parameter listed here has its own group and therefore can be given
alone.
.TP 7
.BI "max_L=" L
Display's desired maximum content luminance (peak)
.IR L \~cd/m²,
a floating point value in the range 0.0\(en100000.0.
.TP 7
.BI "min_L=" L
Display's desired minimum content luminance
.IR L \~cd/m²,
a floating point value in the range 0.0\(en100000.0.
.TP 7
.BI "maxFALL=" L
Display's desired maximum frame-average light level
.IR L \~cd/m²,
a floating point value in the range 0.0\(en100000.0.
.\"---------------------------------------------------------------------
.SH "COLOR-PROFILE SECTION"
Each
.B color-profile

View file

@ -130,10 +130,10 @@ static const struct weston_color_gamut color_gamut_invalid_white_point = {
static const struct test_case good_test_cases[] = {
{
/* sRGB primaries with sRGB TF; succeeds. */
/* sRGB primaries with gamma22; succeeds. */
.primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
.primaries = NULL,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22,
.tf_power = NOT_SET,
.primaries_min_lum = NOT_SET,
.primaries_max_lum = NOT_SET,
@ -147,10 +147,10 @@ static const struct test_case good_test_cases[] = {
.error_point = ERROR_POINT_NONE,
},
{
/* Custom primaries with sRGB TF; succeeds. */
/* Custom primaries with gamma22; succeeds. */
.primaries_named = NOT_SET,
.primaries = &color_gamut_sRGB,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22,
.tf_power = NOT_SET,
.primaries_min_lum = NOT_SET,
.primaries_max_lum = NOT_SET,
@ -164,10 +164,10 @@ static const struct test_case good_test_cases[] = {
.error_point = ERROR_POINT_NONE,
},
{
/* sRGB primaries, sRGB TF and valid luminance values; succeeds. */
/* sRGB primaries, gamma22 and valid luminance values; succeeds. */
.primaries_named = NOT_SET,
.primaries = &color_gamut_sRGB,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22,
.tf_power = NOT_SET,
.primaries_min_lum = 0.5,
.primaries_max_lum = 2000,
@ -198,10 +198,10 @@ static const struct test_case good_test_cases[] = {
.error_point = ERROR_POINT_NONE,
},
{
/* sRGB primaries, sRGB TF and valid target primaries; succeeds. */
/* sRGB primaries, gamma22 and valid target primaries; succeeds. */
.primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
.primaries = NULL,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22,
.tf_power = NOT_SET,
.primaries_min_lum = NOT_SET,
.primaries_max_lum = NOT_SET,
@ -344,7 +344,7 @@ static const struct test_case bad_test_cases[] = {
* protocol error. */
.primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
.primaries = NULL,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22,
.tf_power = NOT_SET,
.primaries_min_lum = NOT_SET,
.primaries_max_lum = NOT_SET,
@ -362,7 +362,7 @@ static const struct test_case bad_test_cases[] = {
* protocol error. */
.primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
.primaries = NULL,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22,
.tf_power = NOT_SET,
.primaries_min_lum = NOT_SET,
.primaries_max_lum = NOT_SET,
@ -416,7 +416,7 @@ static const struct test_case bad_test_cases[] = {
* defined range); graceful failure. */
.primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
.primaries = NULL,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22,
.tf_power = NOT_SET,
.primaries_min_lum = NOT_SET,
.primaries_max_lum = NOT_SET,
@ -434,7 +434,7 @@ static const struct test_case bad_test_cases[] = {
* graceful failure. */
.primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB,
.primaries = NULL,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB,
.tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22,
.tf_power = NOT_SET,
.primaries_min_lum = NOT_SET,
.primaries_max_lum = NOT_SET,
@ -609,7 +609,7 @@ color_manager_init(struct color_manager *cm, struct client *client)
test_assert_u32_eq(cm->supported_rendering_intents,
(1 << WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL) |
(1 << WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE) |
(1 << WP_COLOR_MANAGER_V1_RENDER_INTENT_SATURATION) |
/* (1 << WP_COLOR_MANAGER_V1_RENDER_INTENT_SATURATION) | */
(1 << WP_COLOR_MANAGER_V1_RENDER_INTENT_ABSOLUTE) |
(1 << WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE_BPC));
@ -631,7 +631,7 @@ color_manager_init(struct color_manager *cm, struct client *client)
test_assert_u32_eq(cm->supported_tf,
(1 << WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22) |
(1 << WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA28) |
(1 << WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB) |
(1 << WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR) |
(1 << WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ));
test_assert_true(cm->done);
@ -996,10 +996,10 @@ TEST(set_tf_named_twice)
image_desc_creator_param =
wp_color_manager_v1_create_parametric_creator(cm.manager);
wp_image_description_creator_params_v1_set_tf_named(image_desc_creator_param,
WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB);
WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22);
client_roundtrip(client); /* make sure connection is still valid */
wp_image_description_creator_params_v1_set_tf_named(image_desc_creator_param,
WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB);
WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22);
expect_protocol_error(client, &wp_image_description_creator_params_v1_interface,
WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET);
wp_image_description_creator_params_v1_destroy(image_desc_creator_param);
@ -1025,7 +1025,7 @@ TEST(set_tf_power_then_tf_named)
2.4 * 10000);
client_roundtrip(client); /* make sure connection is still valid */
wp_image_description_creator_params_v1_set_tf_named(image_desc_creator_param,
WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB);
WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22);
expect_protocol_error(client, &wp_image_description_creator_params_v1_interface,
WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET);
wp_image_description_creator_params_v1_destroy(image_desc_creator_param);
@ -1048,7 +1048,7 @@ TEST(set_tf_named_then_tf_power)
image_desc_creator_param =
wp_color_manager_v1_create_parametric_creator(cm.manager);
wp_image_description_creator_params_v1_set_tf_named(image_desc_creator_param,
WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB);
WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22);
client_roundtrip(client); /* make sure connection is still valid */
wp_image_description_creator_params_v1_set_tf_power(image_desc_creator_param,
2.4 * 10000);

View file

@ -608,7 +608,7 @@ color_manager_init(struct color_manager *cm, struct client *client)
test_assert_u32_eq(cm->supported_rendering_intents,
(1 << WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL) |
(1 << WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE) |
(1 << WP_COLOR_MANAGER_V1_RENDER_INTENT_SATURATION) |
/* (1 << WP_COLOR_MANAGER_V1_RENDER_INTENT_SATURATION) | */
(1 << WP_COLOR_MANAGER_V1_RENDER_INTENT_ABSOLUTE) |
(1 << WP_COLOR_MANAGER_V1_RENDER_INTENT_RELATIVE_BPC));

View file

@ -1,971 +0,0 @@
/*
* Copyright 2022 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 <string.h>
#include <math.h>
#include "weston-test-client-helper.h"
#include "weston-test-fixture-compositor.h"
#include "weston-test-assert.h"
#include "weston-private.h"
#include "libweston-internal.h"
#include "backend.h"
#include "color.h"
#include "id-number-allocator.h"
#include "shared/string-helpers.h"
#include "shared/xalloc.h"
struct config_testcase {
bool has_characteristics_key;
const char *output_characteristics_name;
const char *characteristics_name;
const char *red_x;
const char *green_y;
const char *white_y;
const char *min_L;
int expected_retval;
const char *expected_error;
};
static const struct config_testcase config_cases[] = {
{
false, "fred", "fred", "red_x=0.9", "green_y=0.8", "white_y=0.323", "min_L=1e-4", 0,
""
},
{
true, "fred", "fred", "red_x=0.9", "green_y= 0.8 ", "white_y=0.323", "min_L=1e-4", 0,
""
},
{
true, "fred", "fred", "red_x=0.9", "green_y= 0.8 ", "white_y=0.323", "", 0,
""
},
{
true, "notexisting", "fred", "red_x=0.9", "green_y=0.8", "white_y=0.323", "min_L=1e-4", -1,
"Config error in weston.ini, output mockoutput: no [color_characteristics] section with 'name=notexisting' found.\n"
},
{
true, "fr:ed", "fr:ed", "red_x=0.9", "green_y=0.8", "white_y=0.323", "min_L=1e-4", -1,
"Config error in weston.ini [color_characteristics] name=fr:ed is a reserved name. Do not use ':' character in the name.\n"
},
{
true, "fred", "fred", "red_x=-5", "green_y=1.01", "white_y=0.323", "min_L=1e-4", -1,
"Config error in weston.ini [color_characteristics] name=fred: red_x value -5.000000 is outside of the range 0.000000 - 1.000000.\n"
"Config error in weston.ini [color_characteristics] name=fred: green_y value 1.010000 is outside of the range 0.000000 - 1.000000.\n"
},
{
true, "fred", "fred", "red_x=haahaa", "green_y=-", "white_y=0.323", "min_L=1e-4", -1,
"Config error in weston.ini [color_characteristics] name=fred: failed to parse the value of key red_x.\n"
"Config error in weston.ini [color_characteristics] name=fred: failed to parse the value of key green_y.\n"
},
{
true, "fred", "fred", "", "", "white_y=0.323", "min_L=1e-4", -1,
"Config error in weston.ini [color_characteristics] name=fred: group 1 key red_x is missing. You must set either none or all keys of a group.\n"
"Config error in weston.ini [color_characteristics] name=fred: group 1 key red_y is set. You must set either none or all keys of a group.\n"
"Config error in weston.ini [color_characteristics] name=fred: group 1 key green_x is set. You must set either none or all keys of a group.\n"
"Config error in weston.ini [color_characteristics] name=fred: group 1 key green_y is missing. You must set either none or all keys of a group.\n"
"Config error in weston.ini [color_characteristics] name=fred: group 1 key blue_x is set. You must set either none or all keys of a group.\n"
"Config error in weston.ini [color_characteristics] name=fred: group 1 key blue_y is set. You must set either none or all keys of a group.\n"
},
{
true, "fred", "fred", "red_x=0.9", "green_y=0.8", "", "min_L=1e-4", -1,
"Config error in weston.ini [color_characteristics] name=fred: group 2 key white_x is set. You must set either none or all keys of a group.\n"
"Config error in weston.ini [color_characteristics] name=fred: group 2 key white_y is missing. You must set either none or all keys of a group.\n"
},
};
static FILE *logfile;
static int
logger(const char *fmt, va_list arg)
{
return vfprintf(logfile, fmt, arg);
}
static int
no_logger(const char *fmt, va_list arg)
{
return 0;
}
static struct weston_config *
create_config(const struct config_testcase *t)
{
struct compositor_setup setup;
struct weston_config *wc;
compositor_setup_defaults(&setup);
weston_ini_setup(&setup,
cfgln("[output]"),
cfgln("name=mockoutput"),
t->has_characteristics_key ?
cfgln("color_characteristics=%s", t->output_characteristics_name) :
cfgln(""),
cfgln("eotf-mode=st2084"),
cfgln("[color_characteristics]"),
cfgln("name=%s", t->characteristics_name),
cfgln("maxFALL=1000"),
cfgln("%s", t->red_x),
cfgln("red_y=0.3"),
cfgln("blue_x=0.1"),
cfgln("blue_y=0.11"),
cfgln("green_x=0.1771"),
cfgln("%s", t->green_y),
cfgln("white_x=0.313"),
cfgln("%s", t->white_y),
cfgln("%s", t->min_L),
cfgln("max_L=65535.0"),
cfgln("[core]"),
cfgln("color-management=true"));
wc = weston_config_parse(setup.config_file);
free(setup.config_file);
return wc;
}
struct mock_color_manager {
struct weston_color_manager base;
struct weston_hdr_metadata_type1 *test_hdr_meta;
};
static struct weston_output_color_outcome *
mock_create_output_color_outcome(struct weston_color_manager *cm_base,
struct weston_output *output)
{
struct mock_color_manager *cm = container_of(cm_base, typeof(*cm), base);
struct weston_output_color_outcome *co;
co = xzalloc(sizeof *co);
co->hdr_meta = *cm->test_hdr_meta;
return co;
}
static struct weston_color_profile *
mock_cm_ref_stock_sRGB_color_profile(struct weston_color_manager *mock_cm)
{
struct weston_color_profile *mock_cprof;
mock_cprof = xzalloc(sizeof(*mock_cprof));
weston_color_profile_init(mock_cprof, mock_cm);
str_printf(&mock_cprof->description, "mock cprof");
return mock_cprof;
}
static bool
mock_cm_get_color_profile_from_params(struct weston_color_manager *cm,
const struct weston_color_profile_params *params,
const char *name_part,
struct weston_color_profile **cprof_out,
char **errmsg)
{
test_assert_not_reached("This cannot be a valid parametric profile.");
}
static void
mock_cm_destroy_color_profile(struct weston_color_profile *mock_cprof)
{
free(mock_cprof->description);
free(mock_cprof);
}
/*
* Manufacture various weston.ini and check what
* wet_output_set_color_characteristics() says. Tests for the return value and
* the error messages logged.
*/
TEST_P(color_characteristics_config_error, config_cases)
{
const struct config_testcase *t = data;
struct weston_config *wc;
struct weston_config_section *section;
int retval;
char *logbuf;
size_t logsize;
struct mock_color_manager mock_cm = {
.base.create_output_color_outcome = mock_create_output_color_outcome,
.base.ref_stock_sRGB_color_profile = mock_cm_ref_stock_sRGB_color_profile,
.base.destroy_color_profile = mock_cm_destroy_color_profile,
};
struct weston_compositor mock_compositor = {
.color_manager = &mock_cm.base,
.color_profile_id_generator = weston_idalloc_create(&mock_compositor),
};
struct weston_output mock_output = {};
mock_cm.base.compositor = &mock_compositor;
wl_list_init(&mock_compositor.plane_list);
weston_output_init(&mock_output, &mock_compositor, "mockoutput");
logfile = open_memstream(&logbuf, &logsize);
weston_log_set_handler(logger, logger);
wc = create_config(t);
section = weston_config_get_section(wc, "output", "name", "mockoutput");
test_assert_ptr_not_null(section);
retval = wet_output_set_color_characteristics(&mock_output, wc, section);
test_assert_int_eq(fclose(logfile), 0);
logfile = NULL;
testlog("retval %d, logs:\n%s\n", retval, logbuf);
test_assert_int_eq(retval, t->expected_retval);
test_assert_int_eq(strcmp(logbuf, t->expected_error), 0);
weston_config_destroy(wc);
free(logbuf);
weston_output_release(&mock_output);
weston_idalloc_destroy(mock_compositor.color_profile_id_generator);
return RESULT_OK;
}
/* Setting NULL resets group_mask */
TEST(weston_output_set_color_characteristics_null)
{
struct mock_color_manager mock_cm = {
.base.create_output_color_outcome = mock_create_output_color_outcome,
.base.ref_stock_sRGB_color_profile = mock_cm_ref_stock_sRGB_color_profile,
.base.destroy_color_profile = mock_cm_destroy_color_profile,
};
struct weston_compositor mock_compositor = {
.color_manager = &mock_cm.base,
.color_profile_id_generator = weston_idalloc_create(&mock_compositor),
};
struct weston_output mock_output = {};
mock_cm.base.compositor = &mock_compositor;
wl_list_init(&mock_compositor.plane_list);
weston_output_init(&mock_output, &mock_compositor, "mockoutput");
mock_output.color_characteristics.group_mask = 1;
weston_output_set_color_characteristics(&mock_output, NULL);
test_assert_u32_eq(mock_output.color_characteristics.group_mask, 0);
weston_output_release(&mock_output);
weston_idalloc_destroy(mock_compositor.color_profile_id_generator);
return RESULT_OK;
}
struct value_testcase {
unsigned field_index;
float value;
bool retval;
};
static const struct value_testcase value_cases[] = {
{ 0, 0.0, true },
{ 0, 1.0, true },
{ 0, -0.001, false },
{ 0, 1.01, false },
{ 0, NAN, false },
{ 0, HUGE_VALF, false },
{ 0, -HUGE_VALF, false },
{ 1, -1.0, false },
{ 2, 2.0, false },
{ 3, 2.0, false },
{ 4, 2.0, false },
{ 5, 2.0, false },
{ 6, 2.0, false },
{ 7, 2.0, false },
{ 8, 0.99, false },
{ 8, 65535.1, false },
{ 9, 0.000099, false },
{ 9, 6.55351, false },
{ 10, 0.99, false },
{ 10, 65535.1, false },
{ 11, 0.99, false },
{ 11, 65535.1, false },
};
/*
* Modify one value in a known good metadata structure, and see how
* validation reacts to it.
*/
TEST_P(hdr_metadata_type1_errors, value_cases)
{
struct value_testcase *t = data;
struct weston_hdr_metadata_type1 meta = {
.group_mask = WESTON_HDR_METADATA_TYPE1_GROUP_ALL_MASK,
.primary[0] = { 0.6650, 0.3261 },
.primary[1] = { 0.2890, 0.6435 },
.primary[2] = { 0.1491, 0.0507 },
.white = { 0.3134, 0.3291 },
.maxDML = 600.0,
.minDML = 0.0001,
.maxCLL = 600.0,
.maxFALL = 400.0,
};
float *fields[] = {
&meta.primary[0].x, &meta.primary[0].y,
&meta.primary[1].x, &meta.primary[1].y,
&meta.primary[2].x, &meta.primary[2].y,
&meta.white.x, &meta.white.y,
&meta.maxDML, &meta.minDML,
&meta.maxCLL, &meta.maxFALL,
};
struct mock_color_manager mock_cm = {
.base.create_output_color_outcome = mock_create_output_color_outcome,
.base.ref_stock_sRGB_color_profile = mock_cm_ref_stock_sRGB_color_profile,
.base.destroy_color_profile = mock_cm_destroy_color_profile,
.test_hdr_meta = &meta,
};
struct weston_compositor mock_compositor = {
.color_manager = &mock_cm.base,
.color_profile_id_generator = weston_idalloc_create(&mock_compositor),
};
struct weston_output mock_output = {};
bool ret;
weston_log_set_handler(no_logger, no_logger);
mock_cm.base.compositor = &mock_compositor;
wl_list_init(&mock_compositor.plane_list);
weston_output_init(&mock_output, &mock_compositor, "mockoutput");
test_assert_uint_lt(t->field_index, ARRAY_LENGTH(fields));
*fields[t->field_index] = t->value;
ret = weston_output_set_color_outcome(&mock_output);
test_assert_int_eq(ret, t->retval);
weston_output_color_outcome_destroy(&mock_output.color_outcome);
weston_output_release(&mock_output);
weston_idalloc_destroy(mock_compositor.color_profile_id_generator);
return RESULT_OK;
}
/* Unflagged members are ignored in validity check */
TEST(hdr_metadata_type1_ignore_unflagged)
{
/* All values invalid, but also empty mask so none actually used. */
struct weston_hdr_metadata_type1 meta = {
.group_mask = 0,
.primary[0] = { -1.0, -1.0 },
.primary[1] = { -1.0, -1.0 },
.primary[2] = { -1.0, -1.0 },
.white = { -1.0, -1.0 },
.maxDML = -1.0,
.minDML = -1.0,
.maxCLL = -1.0,
.maxFALL = -1.0,
};
struct mock_color_manager mock_cm = {
.base.create_output_color_outcome = mock_create_output_color_outcome,
.base.ref_stock_sRGB_color_profile = mock_cm_ref_stock_sRGB_color_profile,
.base.destroy_color_profile = mock_cm_destroy_color_profile,
.test_hdr_meta = &meta,
};
struct weston_compositor mock_compositor = {
.color_manager = &mock_cm.base,
.color_profile_id_generator = weston_idalloc_create(&mock_compositor),
};
struct weston_output mock_output = {};
bool ret;
mock_cm.base.compositor = &mock_compositor;
wl_list_init(&mock_compositor.plane_list);
weston_log_set_handler(no_logger, no_logger);
weston_output_init(&mock_output, &mock_compositor, "mockoutput");
ret = weston_output_set_color_outcome(&mock_output);
test_assert_true(ret);
weston_output_color_outcome_destroy(&mock_output.color_outcome);
weston_output_release(&mock_output);
weston_idalloc_destroy(mock_compositor.color_profile_id_generator);
return RESULT_OK;
}
struct mode_testcase {
bool color_management;
uint32_t supported_eotf_mask;
uint32_t supported_colorimetry_mask;
const char *eotf_mode;
const char *colorimetry_mode;
enum weston_eotf_mode expected_eotf_mode;
enum weston_colorimetry_mode expected_colorimetry_mode;
int expected_retval;
const char *expected_error;
};
static const struct mode_testcase mode_config_cases[] = {
/* Defaults */
{
false, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT, NULL, NULL,
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
0, ""
},
/* Color management off, EOTF modes */
{
false, WESTON_EOTF_MODE_ALL_MASK, WESTON_COLORIMETRY_MODE_DEFAULT, "sdr", NULL,
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
0, ""
},
{
false, WESTON_EOTF_MODE_ALL_MASK, WESTON_COLORIMETRY_MODE_DEFAULT, "hdr-gamma", NULL,
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error: EOTF mode hdr-gamma on output 'mockoutput' requires color-management=true in weston.ini\n"
},
{
false, WESTON_EOTF_MODE_ALL_MASK, WESTON_COLORIMETRY_MODE_DEFAULT, "st2084", NULL,
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error: EOTF mode st2084 on output 'mockoutput' requires color-management=true in weston.ini\n"
},
{
false, WESTON_EOTF_MODE_ALL_MASK, WESTON_COLORIMETRY_MODE_DEFAULT, "hlg", NULL,
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error: EOTF mode hlg on output 'mockoutput' requires color-management=true in weston.ini\n"
},
{
false, WESTON_EOTF_MODE_ALL_MASK, WESTON_COLORIMETRY_MODE_DEFAULT, "nonosense", NULL,
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error in config for output 'mockoutput': 'nonosense' is not a valid EOTF mode. Try one of: sdr hdr-gamma st2084 hlg\n"
},
/* Color management on, EOTF modes */
{
true, WESTON_EOTF_MODE_ALL_MASK, WESTON_COLORIMETRY_MODE_DEFAULT, "sdr", NULL,
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
0, ""
},
{
true, WESTON_EOTF_MODE_ALL_MASK, WESTON_COLORIMETRY_MODE_DEFAULT, "hdr-gamma", NULL,
WESTON_EOTF_MODE_TRADITIONAL_HDR, WESTON_COLORIMETRY_MODE_DEFAULT,
0, ""
},
{
true, WESTON_EOTF_MODE_ALL_MASK, WESTON_COLORIMETRY_MODE_DEFAULT, "st2084", NULL,
WESTON_EOTF_MODE_ST2084, WESTON_COLORIMETRY_MODE_DEFAULT,
0, ""
},
{
true, WESTON_EOTF_MODE_ALL_MASK, WESTON_COLORIMETRY_MODE_DEFAULT, "hlg", NULL,
WESTON_EOTF_MODE_HLG, WESTON_COLORIMETRY_MODE_DEFAULT,
0, ""
},
{
true, WESTON_EOTF_MODE_ALL_MASK, WESTON_COLORIMETRY_MODE_DEFAULT, "nonosense", NULL,
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error in config for output 'mockoutput': 'nonosense' is not a valid EOTF mode. Try one of: sdr hdr-gamma st2084 hlg\n"
},
/* unsupported EOTF mode */
{
true,
WESTON_EOTF_MODE_SDR | WESTON_EOTF_MODE_TRADITIONAL_HDR | WESTON_EOTF_MODE_ST2084,
WESTON_COLORIMETRY_MODE_DEFAULT, "hlg", NULL,
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error: output 'mockoutput' does not support EOTF mode hlg.\n"
},
/* Color management off, colorimetry modes */
{
false, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "default",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
0, ""
},
{
false, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "bt2020cycc",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error: Colorimetry mode bt2020cycc on output 'mockoutput' requires color-management=true in weston.ini\n"
},
{
false, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "bt2020ycc",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error: Colorimetry mode bt2020ycc on output 'mockoutput' requires color-management=true in weston.ini\n"
},
{
false, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "bt2020rgb",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error: Colorimetry mode bt2020rgb on output 'mockoutput' requires color-management=true in weston.ini\n"
},
{
false, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "p3d65",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error: Colorimetry mode p3d65 on output 'mockoutput' requires color-management=true in weston.ini\n"
},
{
false, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "p3dci",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error: Colorimetry mode p3dci on output 'mockoutput' requires color-management=true in weston.ini\n"
},
{
false, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "ictcp",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error: Colorimetry mode ictcp on output 'mockoutput' requires color-management=true in weston.ini\n"
},
{
false, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "imagine that",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error in config for output 'mockoutput': 'imagine that' is not a valid colorimetry mode. Try one of: default bt2020cycc bt2020ycc bt2020rgb p3d65 p3dci ictcp\n"
},
/* Color management on, colorimetry modes */
{
true, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "default",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
0, ""
},
{
true, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "bt2020cycc",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_BT2020_CYCC,
0, ""
},
{
true, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "bt2020ycc",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_BT2020_YCC,
0, ""
},
{
true, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "bt2020rgb",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_BT2020_RGB,
0, ""
},
{
true, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "p3d65",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_P3D65,
0, ""
},
{
true, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "p3dci",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_P3DCI,
0, ""
},
{
true, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "ictcp",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ICTCP,
0, ""
},
{
true, WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_ALL_MASK, NULL, "imagine that",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error in config for output 'mockoutput': 'imagine that' is not a valid colorimetry mode. Try one of: default bt2020cycc bt2020ycc bt2020rgb p3d65 p3dci ictcp\n"
},
/* Unsupported colorimetry mode */
{
true, WESTON_EOTF_MODE_SDR,
WESTON_COLORIMETRY_MODE_DEFAULT | WESTON_COLORIMETRY_MODE_BT2020_RGB | WESTON_COLORIMETRY_MODE_BT2020_CYCC | WESTON_COLORIMETRY_MODE_P3D65,
NULL, "ictcp",
WESTON_EOTF_MODE_SDR, WESTON_COLORIMETRY_MODE_DEFAULT,
-1, "Error: output 'mockoutput' does not support colorimetry mode ictcp.\n"
},
};
static struct weston_config *
create_mode_config(const struct mode_testcase *t)
{
struct compositor_setup setup;
struct weston_config *wc;
compositor_setup_defaults(&setup);
weston_ini_setup(&setup,
cfgln("[output]"),
cfgln("name=mockoutput"),
t->eotf_mode ?
cfgln("eotf-mode=%s", t->eotf_mode) :
cfgln(""),
t->colorimetry_mode ?
cfgln("colorimetry-mode=%s", t->colorimetry_mode) :
cfgln("")
);
wc = weston_config_parse(setup.config_file);
free(setup.config_file);
return wc;
}
/*
* Manufacture various weston.ini and check what
* wet_output_set_eotf_mode() and wet_output_set_colorimetry_mode() says.
* Tests for the return value and the error messages logged.
*/
TEST_P(mode_config_error, mode_config_cases)
{
const struct mode_testcase *t = data;
struct mock_color_manager mock_cm = {
.base.create_output_color_outcome = mock_create_output_color_outcome,
.base.ref_stock_sRGB_color_profile = mock_cm_ref_stock_sRGB_color_profile,
.base.destroy_color_profile = mock_cm_destroy_color_profile,
};
struct weston_compositor mock_compositor = {
.color_manager = &mock_cm.base,
.color_profile_id_generator = weston_idalloc_create(&mock_compositor),
};
struct weston_config *wc;
struct weston_config_section *section;
int retval;
int attached;
char *logbuf;
size_t logsize;
struct weston_head mock_head = {};
struct weston_output mock_output = {};
mock_cm.base.compositor = &mock_compositor;
wl_list_init(&mock_compositor.plane_list);
weston_output_init(&mock_output, &mock_compositor, "mockoutput");
weston_head_init(&mock_head, "mockhead");
weston_head_set_supported_eotf_mask(&mock_head, t->supported_eotf_mask);
weston_head_set_supported_colorimetry_mask(&mock_head, t->supported_colorimetry_mask);
attached = weston_output_attach_head(&mock_output, &mock_head);
test_assert_int_eq(attached, 0);
logfile = open_memstream(&logbuf, &logsize);
weston_log_set_handler(logger, logger);
wc = create_mode_config(t);
section = weston_config_get_section(wc, "output", "name", "mockoutput");
test_assert_ptr_not_null(section);
retval = wet_output_set_eotf_mode(&mock_output, section, t->color_management);
if (retval == 0) {
retval = wet_output_set_colorimetry_mode(&mock_output, section,
t->color_management);
}
test_assert_int_eq(fclose(logfile), 0);
logfile = NULL;
testlog("retval %d, logs:\n%s\n", retval, logbuf);
test_assert_int_eq(retval, t->expected_retval);
test_assert_int_eq(strcmp(logbuf, t->expected_error), 0);
test_assert_enum(weston_output_get_eotf_mode(&mock_output), t->expected_eotf_mode);
test_assert_enum(weston_output_get_colorimetry_mode(&mock_output), t->expected_colorimetry_mode);
weston_config_destroy(wc);
free(logbuf);
weston_output_release(&mock_output);
weston_head_release(&mock_head);
weston_idalloc_destroy(mock_compositor.color_profile_id_generator);
return RESULT_OK;
}
static void
test_creating_output_color_profile(struct weston_config *wc,
const char *profile_name,
uint32_t supported_color_features,
uint32_t supported_primaries_named,
uint32_t supported_tf_named,
const char *expected_error)
{
struct weston_color_profile *cprof;
char *logbuf;
size_t logsize;
struct mock_color_manager mock_cm = {
.base.ref_stock_sRGB_color_profile = mock_cm_ref_stock_sRGB_color_profile,
.base.get_color_profile_from_params = mock_cm_get_color_profile_from_params,
.base.destroy_color_profile = mock_cm_destroy_color_profile,
.base.supported_color_features = supported_color_features,
.base.supported_primaries_named = supported_primaries_named,
.base.supported_tf_named = supported_tf_named,
};
struct weston_compositor mock_compositor = {
.color_manager = &mock_cm.base,
.color_profile_id_generator = weston_idalloc_create(&mock_compositor),
};
struct weston_output mock_output = {};
mock_cm.base.compositor = &mock_compositor;
wl_list_init(&mock_compositor.plane_list);
logfile = open_memstream(&logbuf, &logsize);
weston_log_set_handler(logger, logger);
weston_output_init(&mock_output, &mock_compositor, "mockoutput");
cprof = wet_create_output_color_profile(&mock_output, wc, profile_name);
test_assert_ptr_null(cprof);
test_assert_int_eq(fclose(logfile), 0);
logfile = NULL;
testlog("logs:\n%s\n------\n", logbuf);
test_assert_str_eq(logbuf, expected_error);
free(logbuf);
weston_output_release(&mock_output);
weston_idalloc_destroy(mock_compositor.color_profile_id_generator);
}
struct color_profile_name_testcase {
const char *profile_name;
const char *expected_error;
};
static const struct color_profile_name_testcase color_profile_name_cases[] = {
{
"notexists",
"Config error in weston.ini, output mockoutput: no [color-profile] section with 'name=notexists' found.\n",
},
{
"boo:faa",
"Config error in weston.ini, output mockoutput, color-profile=boo:faa is illegal. The ':' character is legal only for 'srgb:' and 'auto:'.\n",
},
{
"auto:kek",
"Config error in weston.ini, output mockoutput, key color-profile=auto: invalid flag 'kek'.\n",
},
};
/*
* Manufacture various weston.ini and check the error messages that
* wet_create_output_color_profile() generates for bad color-profile names.
*/
TEST_P(parametric_color_profile_name_errors, color_profile_name_cases)
{
const struct color_profile_name_testcase *t = data;
test_creating_output_color_profile(NULL, t->profile_name,
0xffffffff, 0xffffffff, 0xffffffff,
t->expected_error);
return RESULT_OK;
}
struct parameters_testcase {
const char *profile_string;
const char *expected_error;
};
static const struct parameters_testcase param_config_cases[] = {
{
"",
"Config error in weston.ini [color-profile] name=mydisp, invalid parameter set:\n"
" primaries not set\n"
" transfer function not set\n",
},
{
"tf_named=gamma22\n",
"Config error in weston.ini [color-profile] name=mydisp, invalid parameter set:\n"
" primaries not set\n"
},
{
"prim_named=srgb\n",
"Config error in weston.ini [color-profile] name=mydisp, invalid parameter set:\n"
" transfer function not set\n",
},
{
"tf_named=kukkuu\n"
"prim_named=jeejee\n",
"Config error in weston.ini [color-profile] name=mydisp, prim_named has unknown value 'jeejee'.\n"
"Config error in weston.ini [color-profile] name=mydisp, tf_named has unknown value 'kukkuu'.\n",
},
{
"prim_named=pal\n"
"tf_named=gamma28\n"
"tf_power=2.4\n",
"Config error in weston.ini [color-profile] name=mydisp, invalid parameter set:\n"
" tf was already set\n",
},
{
"prim_named=pal_m\n"
"prim_red=0.67 0.33\n"
"prim_green=0.21 0.71\n"
"prim_blue=0.14 0.08\n"
"prim_white=0.31 0.32\n"
"tf_power=2.4\n",
"Config error in weston.ini [color-profile] name=mydisp, invalid parameter set:\n"
" primaries were already set\n",
},
{
"prim_red=0.6 0.3\n"
"prim_blue=0.1 0.05\n"
"min_lum=0\n"
"target_white=0.33 0.33\n"
"target_max_lum=1200\n",
"Config error in weston.ini [color-profile] name=mydisp:\n"
" group: signaling primaries\n"
" prim_red is set.\n"
" prim_green is missing.\n"
" prim_blue is set.\n"
" prim_white is missing.\n"
" group: signaling luminances\n"
" min_lum is set.\n"
" max_lum is missing.\n"
" ref_lum is missing.\n"
" group: target primaries\n"
" target_red is missing.\n"
" target_green is missing.\n"
" target_blue is missing.\n"
" target_white is set.\n"
" group: target luminances\n"
" target_min_lum is missing.\n"
" target_max_lum is set.\n"
"You must set either none or all keys of a group.\n",
},
{
"prim_red=0.67 0.33 0.4\n"
"prim_green=0.21\n"
"prim_blue=0,14 k\n"
"prim_white=\n"
"tf_power=xx\n",
"Config error in weston.ini [color-profile] name=mydisp, parsing prim_red: Needed exactly 2 numbers separated by whitespace, got 3.\n"
"Config error in weston.ini [color-profile] name=mydisp, parsing prim_green: Needed exactly 2 numbers separated by whitespace, got 1.\n"
"Config error in weston.ini [color-profile] name=mydisp, parsing prim_blue: '0,14' is not a number.\n"
"Config error in weston.ini [color-profile] name=mydisp, parsing prim_white: Needed exactly 2 numbers separated by whitespace, got 0.\n"
"Config error in weston.ini [color-profile] name=mydisp, parsing tf_power: 'xx' is not a number.\n",
},
{
"tf_power=50\n",
"Config error in weston.ini [color-profile] name=mydisp, invalid parameter set:\n"
" tf power exponent 50.000000 is not in the range [1.0, 10.0]\n"
" primaries not set\n"
" transfer function not set\n",
},
{
"prim_red=Inf 0.33\n"
"prim_green=0.21 7\n"
"prim_blue=-1 NaN\n"
"prim_white=0 -2\n"
"tf_power=3\n",
"Config error in weston.ini [color-profile] name=mydisp, invalid parameter set:\n"
" invalid primary color volume, the red primary CIE x value inf is out of range [-1.0, 2.0]\n"
" invalid primary color volume, the green primary CIE y value 7.000000 is out of range [-1.0, 2.0]\n"
" invalid primary color volume, the blue primary CIE y value nan is out of range [-1.0, 2.0]\n"
" invalid primary color volume, the white point CIE y value -2.000000 is out of range [-1.0, 2.0]\n"
" white point out of primary volume\n"
},
{
"prim_named=bt2020\n"
"tf_named=bt1886\n"
"min_lum=10\n"
"ref_lum=5\n"
"max_lum=2\n"
"target_min_lum=55\n"
"target_max_lum=1\n"
"max_fall=-7\n"
"max_cll=0\n",
"Config error in weston.ini [color-profile] name=mydisp, invalid parameter set:\n"
" reference luminance (5.000000) must be greater than primary minimum luminance (10.000000)\n"
" primary minimum luminance (10.000000) must be less than primary maximum luminance (2.000000)\n"
" target min luminance (55.000000) must be less than target max luminance (1.000000)\n"
" maxCLL (0.000000) must be in the range (0.0, 1e+6]\n"
" maxCLL (0.000000) should be greater than target min luminance (0.010000)\n"
" maxFALL (-7.000000) must be in the range (0.0, 1e+6]\n"
" maxFALL (-7.000000) must be greater than min luminance (0.010000)\n",
},
};
/*
* Manufacture various weston.ini and check the error messages that
* wet_create_output_color_profile() generates for invalid
* color-profile sections.
*/
TEST_P(parametric_color_profile_parsing_errors, param_config_cases)
{
const struct parameters_testcase *t = data;
struct compositor_setup setup;
struct weston_config *wc;
compositor_setup_defaults(&setup);
weston_ini_setup(&setup,
cfgln("[color-profile]"),
cfgln("name=mydisp"),
cfgln("%s", t->profile_string));
wc = weston_config_parse(setup.config_file);
test_assert_ptr_not_null(wc);
free(setup.config_file);
test_creating_output_color_profile(wc, "mydisp",
0xffffffff, 0xffffffff, 0xffffffff,
t->expected_error);
weston_config_destroy(wc);
return RESULT_OK;
}
static const struct parameters_testcase param_unsupported_cases[] = {
{
"prim_named=ntsc\n"
"tf_named=log100\n",
"Config error in weston.ini [color-profile] name=mydisp, invalid parameter set:\n"
" primaries named NTSC (BT.601) not supported by the color manager\n"
" logarithmic 100:1 not supported by the color manager\n"
" primaries not set\n"
" transfer function not set\n",
},
{
"prim_named=srgb\n"
"tf_power=2.3\n",
"Config error in weston.ini [color-profile] name=mydisp, invalid parameter set:\n"
" set_tf_power not supported by the color manager\n"
" transfer function not set\n",
},
};
/*
* Manufacture various weston.ini and check the error messages that
* wet_create_output_color_profile() generates for valid
* color-profile sections that use things the color manager does not
* support.
*/
TEST_P(parametric_color_profile_parsing_unsupported, param_unsupported_cases)
{
const struct parameters_testcase *t = data;
struct compositor_setup setup;
struct weston_config *wc;
compositor_setup_defaults(&setup);
weston_ini_setup(&setup,
cfgln("[color-profile]"),
cfgln("name=mydisp"),
cfgln("%s", t->profile_string));
wc = weston_config_parse(setup.config_file);
test_assert_ptr_not_null(wc);
free(setup.config_file);
test_creating_output_color_profile(wc, "mydisp",
0, (1u << WESTON_PRIMARIES_CICP_SRGB), 0,
t->expected_error);
weston_config_destroy(wc);
return RESULT_OK;
}

View file

@ -1,128 +0,0 @@
/*
* Copyright 2022 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 <string.h>
#include "weston-test-client-helper.h"
#include "weston-test-fixture-compositor.h"
#include "weston-test-assert.h"
#include "backend.h"
#include "color.h"
static enum test_result_code
fixture_setup(struct weston_test_harness *harness)
{
struct compositor_setup setup;
compositor_setup_defaults(&setup);
setup.renderer = WESTON_RENDERER_GL;
setup.shell = SHELL_TEST_DESKTOP;
weston_ini_setup(&setup,
cfgln("[output]"),
cfgln("name=headless"),
cfgln("color_characteristics=my-awesome-color"),
cfgln("colorimetry-mode=bt2020rgb"),
cfgln("eotf-mode=st2084"),
cfgln("[color_characteristics]"),
cfgln("name=my-awesome-color"),
cfgln("maxFALL=1000"),
cfgln("red_x=0.9999"),
cfgln("red_y=0.3"),
cfgln("blue_x=0.1"),
cfgln("blue_y=0.11"),
cfgln("green_x=0.1771"),
cfgln("green_y=0.80001"),
cfgln("white_x=0.313"),
cfgln("white_y=0.323"),
cfgln("min_L=0.0001"),
cfgln("max_L=65535.0"),
cfgln("[core]"),
cfgln("color-management=true"));
return weston_test_harness_execute_as_plugin(harness, &setup);
}
DECLARE_FIXTURE_SETUP(fixture_setup);
PLUGIN_TEST(color_characteristics_from_weston_ini)
{
struct weston_output *output = NULL;
struct weston_output *it;
enum weston_eotf_mode mode;
enum weston_colorimetry_mode colorimetry_mode;
const struct weston_color_characteristics *cc;
const struct weston_hdr_metadata_type1 *hdr_meta;
wl_list_for_each(it, &compositor->output_list, link) {
if (strcmp(it->name, "headless") == 0) {
output = it;
break;
}
}
test_assert_ptr_not_null(output);
mode = weston_output_get_eotf_mode(output);
test_assert_enum(mode, WESTON_EOTF_MODE_ST2084);
colorimetry_mode = weston_output_get_colorimetry_mode(output);
test_assert_enum(colorimetry_mode, WESTON_COLORIMETRY_MODE_BT2020_RGB);
cc = weston_output_get_color_characteristics(output);
test_assert_enum(cc->group_mask, WESTON_COLOR_CHARACTERISTICS_GROUP_ALL_MASK);
test_assert_f32_eq(cc->primary[0].x, 0.9999f);
test_assert_f32_eq(cc->primary[0].y, 0.3f);
test_assert_f32_eq(cc->primary[1].x, 0.1771f);
test_assert_f32_eq(cc->primary[1].y, 0.80001f);
test_assert_f32_eq(cc->primary[2].x, 0.1f);
test_assert_f32_eq(cc->primary[2].y, 0.11f);
test_assert_f32_eq(cc->white.x, 0.313f);
test_assert_f32_eq(cc->white.y, 0.323f);
test_assert_f32_eq(cc->min_luminance, 0.0001f);
test_assert_f32_eq(cc->max_luminance, 65535.0f);
test_assert_f32_eq(cc->maxFALL, 1000.0f);
/* The below is color manager policy. */
hdr_meta = weston_output_get_hdr_metadata_type1(output);
test_assert_enum(hdr_meta->group_mask, WESTON_HDR_METADATA_TYPE1_GROUP_ALL_MASK);
test_assert_f32_eq(hdr_meta->primary[0].x, 0.9999f);
test_assert_f32_eq(hdr_meta->primary[0].y, 0.3f);
test_assert_f32_eq(hdr_meta->primary[1].x, 0.1771f);
test_assert_f32_eq(hdr_meta->primary[1].y, 0.80001f);
test_assert_f32_eq(hdr_meta->primary[2].x, 0.1f);
test_assert_f32_eq(hdr_meta->primary[2].y, 0.11f);
test_assert_f32_eq(hdr_meta->white.x, 0.313f);
test_assert_f32_eq(hdr_meta->white.y, 0.323f);
test_assert_f32_eq(hdr_meta->minDML, 0.0001f);
test_assert_f32_eq(hdr_meta->maxDML, 65535.0f);
test_assert_f32_eq(hdr_meta->maxCLL, 65535.0f);
test_assert_f32_eq(hdr_meta->maxFALL, 1000.0f);
return RESULT_OK;
}

View file

@ -131,10 +131,6 @@ tests = [
'dep_objs': dep_libm,
},
{ 'name': 'color-effects', },
{
'name': 'color-metadata-errors',
'dep_objs': dep_libexec_weston,
},
{
'name': 'color-output-parsing',
'dep_objs': [
@ -311,7 +307,6 @@ if get_option('color-management-lcms')
color_management_v1_protocol_c,
],
},
{ 'name': 'color-metadata-parsing' },
{
'name': 'lcms-util',
'dep_objs': [ dep_lcms_util ]