frontend: allow creating custom param color profiles from .ini

This allow users to specify a fully custom parametric color profile in
weston.ini for a certain output.

Signed-off-by: Leandro Ribeiro <leandro.ribeiro@collabora.com>
Co-authored-by: Pekka Paalanen <pekka.paalanen@collabora.com>
Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.com>
This commit is contained in:
Leandro Ribeiro 2024-08-06 14:20:59 -03:00
parent 360eef9663
commit 854457524c
2 changed files with 610 additions and 6 deletions

View file

@ -55,6 +55,7 @@
#include "shared/process-util.h"
#include "shared/string-helpers.h"
#include "shared/xalloc.h"
#include "shared/weston-assert.h"
#include "git-version.h"
#include <libweston/version.h>
#include "weston.h"
@ -1354,6 +1355,357 @@ weston_log_indent_multiline(int indent, const char *multiline)
}
}
static bool
parse_float_array(float *values, size_t len_values, const char *str, char **errmsg)
{
struct weston_string_array elems;
size_t i;
elems = weston_parse_space_separated_list(str);
if (elems.len != len_values) {
if (len_values == 1) {
str_printf(errmsg, "Needed only one number, got %zu numbers.",
elems.len);
} else {
str_printf(errmsg,
"Needed exactly %zu numbers separated by whitespace, got %zu.",
len_values, elems.len);
}
weston_string_array_fini(&elems);
return false;
}
for (i = 0; i < len_values; i++) {
if (!safe_strtofloat(elems.array[i], &values[i])) {
str_printf(errmsg, "'%s' is not a number.", elems.array[i]);
weston_string_array_fini(&elems);
return false;
}
}
weston_string_array_fini(&elems);
return true;
}
static bool
parse_CIExy(struct weston_CIExy *chrom, const char *str, char **errmsg)
{
float val[2];
if (!parse_float_array(val, ARRAY_LENGTH(val), str, errmsg))
return false;
chrom->x = val[0];
chrom->y = val[1];
return true;
}
static const struct weston_enum_map config_primaries_map[] = {
{ "srgb", WESTON_PRIMARIES_CICP_SRGB },
{ "pal_m", WESTON_PRIMARIES_CICP_PAL_M },
{ "pal", WESTON_PRIMARIES_CICP_PAL },
{ "ntsc", WESTON_PRIMARIES_CICP_NTSC },
{ "generic_film", WESTON_PRIMARIES_CICP_GENERIC_FILM },
{ "bt2020", WESTON_PRIMARIES_CICP_BT2020 },
{ "cie1931_xyz", WESTON_PRIMARIES_CICP_CIE1931_XYZ },
{ "dci_p3", WESTON_PRIMARIES_CICP_DCI_P3 },
{ "display_p3", WESTON_PRIMARIES_CICP_DISPLAY_P3 },
{ "adobe_rgb", WESTON_PRIMARIES_ADOBE_RGB },
};
static const struct weston_enum_map config_tf_map[] = {
{ "bt1886", WESTON_TF_BT1886 },
{ "gamma22", WESTON_TF_GAMMA22 },
{ "gamma28", WESTON_TF_GAMMA28 },
{ "st240", WESTON_TF_ST240 },
{ "st428", WESTON_TF_ST428 },
{ "st2084", WESTON_TF_ST2084_PQ },
{ "linear", WESTON_TF_EXT_LINEAR },
{ "log100", WESTON_TF_LOG_100 },
{ "log316", WESTON_TF_LOG_316 },
{ "xvycc", WESTON_TF_XVYCC },
{ "hlg", WESTON_TF_HLG },
};
enum profile_parameter_group {
PARAMS_GROUP_PRIMARIES = 1 << 0,
PARAMS_GROUP_PRIMARIES_NAMED = 1 << 1,
PARAMS_GROUP_TF_NAMED = 1 << 2,
PARAMS_GROUP_TF_POWER = 1 << 3,
PARAMS_GROUP_LUMINANCE = 1 << 4,
PARAMS_GROUP_TARGET_PRIMARIES = 1 << 5,
PARAMS_GROUP_TARGET_PRIMARIES_NAMED = 1 << 6,
PARAMS_GROUP_TARGET_LUMINANCE = 1 << 7,
PARAMS_GROUP_MAX_FALL = 1 << 8,
PARAMS_GROUP_MAX_CLL = 1 << 9,
};
static const struct weston_enum_map profile_parameter_group_names[] = {
{ "signaling primaries", PARAMS_GROUP_PRIMARIES },
{ "signaling luminances", PARAMS_GROUP_LUMINANCE },
{ "target primaries", PARAMS_GROUP_TARGET_PRIMARIES },
{ "target luminances", PARAMS_GROUP_TARGET_LUMINANCE },
};
struct config_color_params_type {
enum {
TYPE_FLOAT,
TYPE_CIEXY,
TYPE_ENUM,
} data_type;
const struct weston_enum_map *map;
size_t map_len;
};
/* A scalar floating-point value (float) */
static const struct config_color_params_type type_float = {
TYPE_FLOAT, NULL, 0,
};
/* A CIE 1931 xy floating-point value pair (struct weston_CIExy) */
static const struct config_color_params_type type_ciexy = {
TYPE_CIEXY, NULL, 0,
};
/* Enumerated primaries and white point (uint32_t) */
static const struct config_color_params_type type_prims = {
TYPE_ENUM, config_primaries_map, ARRAY_LENGTH(config_primaries_map),
};
/* Enumerated transfer functions (uint32_t) */
static const struct config_color_params_type type_tfs = {
TYPE_ENUM, config_tf_map, ARRAY_LENGTH(config_tf_map),
};
struct config_color_params {
uint32_t group_mask;
struct weston_color_gamut prim;
uint32_t prim_named;
uint32_t tf_named;
float tf_power;
float prim_lum[2];
float ref_lum;
struct weston_color_gamut target_prim;
uint32_t target_named;
float target_lum[2];
float max_cll, max_fall;
};
static bool
config_color_params_parse(struct config_color_params *dst,
struct weston_config_section *section,
const char *section_name,
const char *msgpfx,
struct weston_compositor *compositor)
{
const struct {
const char *name;
const struct config_color_params_type *type;
void *data;
enum profile_parameter_group group;
} keys[] = {
{ "prim_red", &type_ciexy, &dst->prim.primary[0], PARAMS_GROUP_PRIMARIES },
{ "prim_green", &type_ciexy, &dst->prim.primary[1], PARAMS_GROUP_PRIMARIES },
{ "prim_blue", &type_ciexy, &dst->prim.primary[2], PARAMS_GROUP_PRIMARIES },
{ "prim_white", &type_ciexy, &dst->prim.white_point, PARAMS_GROUP_PRIMARIES },
{ "prim_named", &type_prims, &dst->prim_named, PARAMS_GROUP_PRIMARIES_NAMED },
{ "tf_named", &type_tfs, &dst->tf_named, PARAMS_GROUP_TF_NAMED },
{ "tf_power", &type_float, &dst->tf_power, PARAMS_GROUP_TF_POWER },
{ "min_lum", &type_float, &dst->prim_lum[0], PARAMS_GROUP_LUMINANCE },
{ "max_lum", &type_float, &dst->prim_lum[1], PARAMS_GROUP_LUMINANCE },
{ "ref_lum", &type_float, &dst->ref_lum, PARAMS_GROUP_LUMINANCE },
{ "target_red", &type_ciexy, &dst->target_prim.primary[0], PARAMS_GROUP_TARGET_PRIMARIES },
{ "target_green", &type_ciexy, &dst->target_prim.primary[1], PARAMS_GROUP_TARGET_PRIMARIES },
{ "target_blue", &type_ciexy, &dst->target_prim.primary[2], PARAMS_GROUP_TARGET_PRIMARIES },
{ "target_white", &type_ciexy, &dst->target_prim.white_point, PARAMS_GROUP_TARGET_PRIMARIES },
{ "target_named", &type_prims, &dst->target_named, PARAMS_GROUP_TARGET_PRIMARIES_NAMED },
{ "target_min_lum", &type_float, &dst->target_lum[0], PARAMS_GROUP_TARGET_LUMINANCE },
{ "target_max_lum", &type_float, &dst->target_lum[1], PARAMS_GROUP_TARGET_LUMINANCE },
{ "max_cll", &type_float, &dst->max_cll, PARAMS_GROUP_MAX_CLL },
{ "max_fall", &type_float, &dst->max_fall, PARAMS_GROUP_MAX_FALL },
};
bool success = true;
bool found[ARRAY_LENGTH(keys)] = { 0 };
uint32_t missing_group_mask = 0;
enum profile_parameter_group last_error_group;
unsigned i;
/* Let's parse the keys and get the values given by users. */
for (i = 0; i < ARRAY_LENGTH(keys); i++) {
const struct config_color_params_type *mytype = keys[i].type;
char *val_str = NULL;
uint32_t *data_enum;
float *data_float;
struct weston_CIExy *data_ciexy;
const struct weston_enum_map *entry;
char *err_msg;
int ret;
ret = weston_config_section_get_string(section, keys[i].name,
&val_str, NULL);
if (ret < 0) {
/* Key not present on the ini file, not an error. */
missing_group_mask |= keys[i].group;
continue;
}
dst->group_mask |= keys[i].group;
found[i] = true;
switch (mytype->data_type) {
case TYPE_FLOAT:
data_float = keys[i].data;
if (!parse_float_array(data_float, 1, val_str, &err_msg)) {
weston_log("%s name=%s, parsing %s: %s\n",
msgpfx, section_name, keys[i].name, err_msg);
free(err_msg);
success = false;
}
break;
case TYPE_CIEXY:
data_ciexy = keys[i].data;
if (!parse_CIExy(data_ciexy, val_str, &err_msg)) {
weston_log("%s name=%s, parsing %s: %s\n",
msgpfx, section_name, keys[i].name, err_msg);
free(err_msg);
success = false;
}
break;
case TYPE_ENUM:
data_enum = keys[i].data;
entry = weston_enum_map_find_name_(mytype->map, mytype->map_len, val_str);
if (entry) {
*data_enum = entry->value;
} else {
weston_log("%s name=%s, %s has unknown value '%s'.\n",
msgpfx, section_name, keys[i].name, val_str);
success = false;
}
break;
}
free(val_str);
}
if (!success)
return false;
last_error_group = 0;
/* Ensure groups are given fully or not at all. */
for (i = 0; i < ARRAY_LENGTH(keys); i++) {
uint32_t group = keys[i].group;
if ((dst->group_mask & group) && (missing_group_mask & group)) {
success = false;
if (last_error_group == 0)
weston_log("%s name=%s:\n", msgpfx, section_name);
if (group != last_error_group) {
const struct weston_enum_map *e;
e = weston_enum_map_find_value(profile_parameter_group_names, group);
weston_assert_ptr_not_null(compositor, e);
weston_log_continue(" group: %s\n", e->name);
}
last_error_group = group;
weston_log_continue(" %s is %s.\n", keys[i].name,
found[i] ? "set" : "missing");
}
}
if (last_error_group != 0)
weston_log_continue("You must set either none or all keys of a group.\n");
return success;
}
static void
config_color_params_to_builder(struct weston_color_profile_param_builder *builder,
const struct config_color_params *params)
{
if (params->group_mask & PARAMS_GROUP_PRIMARIES) {
weston_color_profile_param_builder_set_primaries(builder,
&params->prim);
}
if (params->group_mask & PARAMS_GROUP_PRIMARIES_NAMED) {
weston_color_profile_param_builder_set_primaries_named(builder,
params->prim_named);
}
if (params->group_mask & PARAMS_GROUP_TF_POWER) {
weston_color_profile_param_builder_set_tf_power_exponent(builder,
params->tf_power);
}
if (params->group_mask & PARAMS_GROUP_TF_NAMED) {
weston_color_profile_param_builder_set_tf_named(builder,
params->tf_named);
}
if (params->group_mask & PARAMS_GROUP_LUMINANCE) {
weston_color_profile_param_builder_set_primary_luminance(builder,
params->ref_lum,
params->prim_lum[0],
params->prim_lum[1]);
}
if (params->group_mask & PARAMS_GROUP_TARGET_PRIMARIES) {
weston_color_profile_param_builder_set_target_primaries(builder,
&params->target_prim);
}
if (params->group_mask & PARAMS_GROUP_TARGET_LUMINANCE) {
weston_color_profile_param_builder_set_target_luminance(builder,
params->target_lum[0],
params->target_lum[1]);
}
if (params->group_mask & PARAMS_GROUP_TARGET_PRIMARIES_NAMED) {
weston_color_profile_param_builder_set_target_primaries_named(builder,
params->target_named);
}
if (params->group_mask & PARAMS_GROUP_MAX_CLL)
weston_color_profile_param_builder_set_maxCLL(builder, params->max_cll);
if (params->group_mask & PARAMS_GROUP_MAX_FALL)
weston_color_profile_param_builder_set_maxFALL(builder, params->max_fall);
}
static struct weston_color_profile *
wet_create_config_color_profile(struct weston_output *output,
struct weston_config_section *section,
const char *section_name,
const char *msgpfx)
{
struct config_color_params params = {};
struct weston_color_profile_param_builder *builder;
struct weston_color_profile *cprof = NULL;
enum weston_color_profile_param_builder_error error;
char *err_msg;
if (!config_color_params_parse(&params, section, section_name,
msgpfx, output->compositor)) {
return NULL;
}
builder = weston_color_profile_param_builder_create(output->compositor);
config_color_params_to_builder(builder, &params);
cprof = weston_color_profile_param_builder_create_color_profile(builder,
"frontend custom from ini",
&error, &err_msg);
if (!cprof) {
weston_log("%s name=%s, invalid parameter set:\n", msgpfx, section_name);
weston_log_indent_multiline(0, err_msg);
free(err_msg);
}
return cprof;
}
static struct weston_color_profile *
wet_create_sRGB_profile(struct weston_compositor *compositor)
{
@ -1597,16 +1949,34 @@ wet_create_output_color_profile(struct weston_output *output,
struct weston_config *wc,
const char *prof_name)
{
struct weston_config_section *prof_section;
static const char *msgpfx = "Config error in weston.ini [" COLOR_PROF_NAME "]";
if (strcmp(prof_name, "srgb:") == 0)
return wet_create_sRGB_profile(output->compositor);
if (strncmp(prof_name, "auto:", 5) == 0)
return wet_parse_auto_profile(output, prof_name + 5);
weston_log("Config error in weston.ini, output %s, key "
COLOR_PROF_NAME ": invalid value (%s)\n.",
output->name, prof_name);
return NULL;
if (strchr(prof_name, ':') != NULL) {
weston_log("Config error in weston.ini, output %s, "
COLOR_PROF_NAME "=%s is illegal. "
"The ':' character is legal only for 'srgb:' and 'auto:'.\n",
output->name, prof_name);
return NULL;
}
prof_section = weston_config_get_section(wc, COLOR_PROF_NAME,
"name", prof_name);
if (!prof_section) {
weston_log("Config error in weston.ini, output %s: "
"no [" COLOR_PROF_NAME "] section with 'name=%s' found.\n",
output->name, prof_name);
return NULL;
}
return wet_create_config_color_profile(output, prof_section,
prof_name, msgpfx);
}
static int

View file

@ -1,6 +1,6 @@
.\" shorthand for double quote that works everywhere.
.ds q \N'34'
.TH weston.ini 5 "2024-08-07" "Weston @version@"
.TH weston.ini 5 "2025-07-29" "Weston @version@"
.\"---------------------------------------------------------------------
.SH NAME
weston.ini \- configuration file for
@ -602,7 +602,11 @@ wayland, and x11 backends, and for remoting and pipewire outputs. If
is also set, that is used instead.
This key creates a parametric profile to be used as the output color profile.
This key can use one of the special profile names that
The profile can be created from parameters recorded in a
.B color-profile
section by referring to it by its
.B name
value. Alternatively this key can use one of the special profile names that
contain a colon (:).
The default, once implemented, will be
@ -652,9 +656,16 @@ Examples:
.B color-profile=auto:
.B color-profile=auto:edid-tf edid-dr
.B color-profile=auto: edid-primaries
.B color-profile=my-awesome-display
.EE
.RE
.IP
The last example causes Weston to read the
.B color-profile
section that has
.BR name=my-awesome-display .
.IP
Notably, setting
@ -966,6 +977,229 @@ 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
section records one set of basic display color characterisation parameters.
Some parameters are collected into groups. Each group must be given either fully
or not at all. A usable section must contain at least the signaling primaries
.RB ( "Signaling primaries group" " or " prim_named )
and the transfer function
.RB ( tf_named " or " tf_power ).
.PP
Each section must be named with
.B name
key by which it can be referenced from other sections. A
.B color-profile
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-profile .
.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.
.TP 7
.BI "prim_named=" name
A standardised set of primaries and white point for signaling.
The following names are recognized:
.RS 11
.TP
.B srgb
sRGB (IEC 61966-2-1) and ITU-R BT.709
.TP
.B pal_m
PAL-M (ITU-R BT.470-6)
.TP
.B pal
PAL (ITU-R BT.470-6), ITU-R BT.601-7 625-line
.TP
.B ntsc
NTSC (SMPTE 170M-2004), ITU-R BT.601-7 525-line
.TP
.B generic_film
generic film with color filters using Illuminant C (ITU-T H.273)
.TP
.B bt2020
ITU-R BT.2020 and BT.2100
.TP
.B cie1931_xyz
CIE 1931 XYZ (ITU-T H.273)
.TP
.B dci_p3
DCI P3 (SMPTE RP 431-2)
.TP
.B display_p3
Apple Display P3, SMPTE EG 432-1
.TP
.B adobe_rgb
Adobe RGB (ISO 12640)
.RE
.IP
Mutually exclusive with the
.B Signaling primaries group
with which one can define custom primaries and white point.
.TP 7
.BI "target_named=" name
A standardised set of primaries and white point for the targeted color volume.
The same set of names are recognized as for
.BR prim_named .
.IP
Mutually exclusive with the
.B Target primaries group
with which one can define custom primaries and white point.
.TP 7
.BI "tf_named=" name
A standard transfer function or characteristic. The following names are
recognized:
.PP
.RS 11
.TP
.B bt1886
Rec. ITU-R BT.1886 display transfer characteristic.
Defaults to min_lum=0.01. ref_lum=100, max_lum=100.
.TP
.B gamma22
Assumed display gamma 2.2 transfer function.
An sRGB display (IEC 61966-2-1) uses this transfer function.
.TP
.B gamma28
Assumed display gamma 2.8 transfer function.
.TP
.B st240
SMPTE ST 240 (1999)
.TP
.B st428
SMPTE ST 428-1 (2019)
.TP
.B st2084
SMPTE ST 2084 Perceptual Quantizer (PQ) transfer characteristic.
Defaults to min_lum=0.005, ref_lum=203.
.TP
.B linear
Linear transfer function defined over all real numbers.
Normalised electrical values are equal the normalised optical values.
.TP
.B log100
Logarithmic transfer characteristic (100:1 range).
.TP
.B log316
Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range).
.TP
.B xvycc
IEC 61966-2-4
.TP
.B hlg
Rec. ITU-R BT.2100 HLG transfer characteristic.
Defaults to min_lum=0.005, ref_lum=203, max_lum=1000.
.RE
.IP
Unless otherwise stated above, the default signaling luminance values are
min_lum=0.2, ref_lum=80, max_lum=80. See
.BR "Signaling luminances group" .
.IP
Mutually exclusive with
.B tf_power.
.TP 7
.BI "tf_power=" E
Mutually exclusive with
.B tf_named.
Set the transfer function as the power-law curve with the given exponent
.IR E ,
a real number in the range [1.0, 10.0].
Defaults to min_lum=0.2, ref_lum=80, max_lum=80.
.TP 7
.BI "max_fall=" L
Display's desired maximum frame-average light level
.IR L " cd/m²."
Undefined when not set.
.TP 7
.BI "max_cll=" L
Display's maximum content light level
.IR L " cd/m²."
Undefined when not set.
.SS Signaling primaries group
.TP 7
.BI "prim_red=" "x y"
.TQ
.BI "prim_green=" "x y"
.TQ
.BI "prim_blue=" "x y"
.TQ
.BI "prim_white=" "x y"
The signaling primaries group defines the primaries and the white point used
for encoding tristimulus into RGB values, also known as the primary color
volume.
.RI "The " x " and " y
are the CIE 1931 2-degree observer chromaticity coordinates. The values must be
in the range [-1.0, 2.0].
For an easier way to define standard primaries, see
.B prim_named
which is mutually exclusive with the signaling primaries group.
.SS Signaling luminances group
.TP 7
.BI "min_lum=" Lmin
.TQ
.BI "ref_lum=" Lref
.TQ
.BI "max_lum=" Lmax
.IR Lmin \(en Lmax
is the dynamic range of the display corresponding to 0%\(en100% signal levels.
For the
.B tf_named=st2084
setting the
.I Lmax
value is overwritten with 10'000 +
.IR Lmin .
.I Lref
is the reference luminance level to which all application contents are matched,
also referred to as graphics white luminance or standard dynamic range peak
luminance. All values are positive real numbers in cd/m².
.IR Lmin " can also be zero."
.IR Lmin " must be less than " Lref " and " Lmax .
When not set, the default values depend on the chosen transfer function or
characteristic.
.SS Target primaries group
.TP 7
.BI "target_red=" "x y"
.TQ
.BI "target_green=" "x y"
.TQ
.BI "target_blue=" "x y"
.TQ
.BI "target_white=" "x y"
The target color volume is similar to the Mastering Display Color Volume
(MDCV) in that any encoded stimulus outside of it has an undefined presentation.
This is particularly useful with a display that uses a wide gamut signal
encoding (e.g. BT.2020) but can only produce a subset of that color gamut.
.RI "The " x " and " y
are the CIE 1931 2-degree observer chromaticity coordinates. The values must be
in the range [-1.0, 2.0].
When not set, defaults to the primary color volume.
For an easier way to define standard targets, see
.B target_named
which is mutually exclusive with the target primaries group.
.SS Target luminances group
.TP 7
.BI "target_min_lum=" Lmin
.TQ
.BI "target_max_lum=" Lmax
Luminance range of the target color volume.
.IR Lmin " and " Lmax
are positive real numbers in cd/m².
.I Lmin
may also be zero, and it must be less than
.IR Lmax .
When not set, defaults to the signaling luminance range.
.\"---------------------------------------------------------------------
.SH "SEE ALSO"
.BR weston (1),
.BR weston-bindings (7),