From 854457524c6b78cf54a2c0708f56ec0be243d7f6 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Tue, 6 Aug 2024 14:20:59 -0300 Subject: [PATCH] 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 Co-authored-by: Pekka Paalanen Signed-off-by: Pekka Paalanen --- frontend/main.c | 378 ++++++++++++++++++++++++++++++++++++++++++++- man/weston.ini.man | 238 +++++++++++++++++++++++++++- 2 files changed, 610 insertions(+), 6 deletions(-) diff --git a/frontend/main.c b/frontend/main.c index fa62a50a3..d396a2df0 100644 --- a/frontend/main.c +++ b/frontend/main.c @@ -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 #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, + ¶ms->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, + ¶ms->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(¶ms, section, section_name, + msgpfx, output->compositor)) { + return NULL; + } + + builder = weston_color_profile_param_builder_create(output->compositor); + config_color_params_to_builder(builder, ¶ms); + + 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 diff --git a/man/weston.ini.man b/man/weston.ini.man index 2989b90ab..1a79e358c 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -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),