From 26eb393d6dd7631dc5b7edd0df95342d606e907c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 5 Dec 2025 21:51:54 +0000 Subject: [PATCH] sway/commands/output: add color_profile "--device-primaries" When a display is connected, create a color transform from its self-reported color characteristics --- include/sway/config.h | 8 +++- sway/commands/output/color_profile.c | 18 +++++++-- sway/config/output.c | 59 +++++++++++++++++++++++++--- sway/sway-output.5.scd | 16 ++++++-- 4 files changed, 88 insertions(+), 13 deletions(-) diff --git a/include/sway/config.h b/include/sway/config.h index 3c380933e..16b822fea 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -267,6 +267,12 @@ enum render_bit_depth { RENDER_BIT_DEPTH_10, }; +enum color_profile { + COLOR_PROFILE_DEFAULT, // default is Transform with NULL color_transform + COLOR_PROFILE_TRANSFORM, // use color_transform from output_config + COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES, // create transform from wlr_output +}; + /** * Size and position configuration for a particular output. * @@ -288,7 +294,7 @@ struct output_config { int max_render_time; // In milliseconds int adaptive_sync; enum render_bit_depth render_bit_depth; - bool set_color_transform; + enum color_profile color_profile; struct wlr_color_transform *color_transform; int allow_tearing; int hdr; diff --git a/sway/commands/output/color_profile.c b/sway/commands/output/color_profile.c index b145d449b..0456b19b7 100644 --- a/sway/commands/output/color_profile.c +++ b/sway/commands/output/color_profile.c @@ -55,6 +55,14 @@ struct cmd_results *output_cmd_color_profile(int argc, char **argv) { if (!config->handler_context.output_config) { return cmd_results_new(CMD_FAILURE, "Missing output config"); } + + enum color_profile new_mode = COLOR_PROFILE_TRANSFORM; + if (argc >= 2 && strcmp(*argv, "--device-primaries") == 0) { + new_mode = COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES; + argc--; + argv++; + } + if (!argc) { return cmd_results_new(CMD_INVALID, "Missing color_profile first argument."); } @@ -62,7 +70,7 @@ struct cmd_results *output_cmd_color_profile(int argc, char **argv) { if (strcmp(*argv, "gamma22") == 0) { wlr_color_transform_unref(config->handler_context.output_config->color_transform); config->handler_context.output_config->color_transform = NULL; - config->handler_context.output_config->set_color_transform = true; + config->handler_context.output_config->color_profile = new_mode; config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; @@ -70,7 +78,7 @@ struct cmd_results *output_cmd_color_profile(int argc, char **argv) { wlr_color_transform_unref(config->handler_context.output_config->color_transform); config->handler_context.output_config->color_transform = wlr_color_transform_init_linear_to_inverse_eotf(WLR_COLOR_TRANSFER_FUNCTION_SRGB); - config->handler_context.output_config->set_color_transform = true; + config->handler_context.output_config->color_profile = new_mode; config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; @@ -79,6 +87,10 @@ struct cmd_results *output_cmd_color_profile(int argc, char **argv) { return cmd_results_new(CMD_INVALID, "Invalid color profile specification: icc type requires a file"); } + if (new_mode != COLOR_PROFILE_TRANSFORM) { + return cmd_results_new(CMD_INVALID, + "Invalid color profile specification: --device-primaries cannot be used with icc"); + } char *icc_path = strdup(argv[1]); if (!expand_path(&icc_path)) { @@ -108,7 +120,7 @@ struct cmd_results *output_cmd_color_profile(int argc, char **argv) { wlr_color_transform_unref(config->handler_context.output_config->color_transform); config->handler_context.output_config->color_transform = tmp; - config->handler_context.output_config->set_color_transform = true; + config->handler_context.output_config->color_profile = COLOR_PROFILE_TRANSFORM; config->handler_context.leftovers.argc = argc - 2; config->handler_context.leftovers.argv = argv + 2; diff --git a/sway/config/output.c b/sway/config/output.c index b884a785d..3d25b46c7 100644 --- a/sway/config/output.c +++ b/sway/config/output.c @@ -75,7 +75,7 @@ struct output_config *new_output_config(const char *name) { oc->max_render_time = -1; oc->adaptive_sync = -1; oc->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT; - oc->set_color_transform = false; + oc->color_profile = COLOR_PROFILE_DEFAULT; oc->color_transform = NULL; oc->power = -1; oc->allow_tearing = -1; @@ -130,12 +130,12 @@ static void supersede_output_config(struct output_config *dst, struct output_con if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { dst->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT; } - if (src->set_color_transform) { + if (src->color_profile != COLOR_PROFILE_DEFAULT) { if (dst->color_transform) { wlr_color_transform_unref(dst->color_transform); dst->color_transform = NULL; } - dst->set_color_transform = false; + dst->color_profile = COLOR_PROFILE_DEFAULT; } if (src->background) { free(dst->background); @@ -207,12 +207,12 @@ static void merge_output_config(struct output_config *dst, struct output_config if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { dst->render_bit_depth = src->render_bit_depth; } - if (src->set_color_transform) { + if (src->color_profile != COLOR_PROFILE_DEFAULT) { if (src->color_transform) { wlr_color_transform_ref(src->color_transform); } wlr_color_transform_unref(dst->color_transform); - dst->set_color_transform = true; + dst->color_profile = src->color_profile; dst->color_transform = src->color_transform; } if (src->background) { @@ -570,13 +570,60 @@ static void config_output_state_finish(struct config_output_state *state) { wlr_color_transform_unref(state->color_transform); } +static struct wlr_color_transform *color_profile_from_device(struct wlr_output *wlr_output, + struct wlr_color_transform *transfer_function) { + struct wlr_color_primaries srgb_primaries; + wlr_color_primaries_from_named(&srgb_primaries, WLR_COLOR_NAMED_PRIMARIES_SRGB); + + const struct wlr_color_primaries *primaries = wlr_output->default_primaries; + if (primaries == NULL) { + sway_log(SWAY_INFO, "output has no reported color information"); + if (transfer_function) { + wlr_color_transform_ref(transfer_function); + } + return transfer_function; + } else if (memcmp(primaries, &srgb_primaries, sizeof(*primaries)) == 0) { + sway_log(SWAY_INFO, "output reports sRGB colors, no correction needed"); + if (transfer_function) { + wlr_color_transform_ref(transfer_function); + } + return transfer_function; + } else { + sway_log(SWAY_INFO, "Creating color profile from reported color primaries: " + "R(%f, %f) G(%f, %f) B(%f, %f) W(%f, %f)", + primaries->red.x, primaries->red.y, primaries->green.x, primaries->green.y, + primaries->blue.x, primaries->blue.y, primaries->white.x, primaries->white.y); + float matrix[9]; + wlr_color_primaries_transform_absolute_colorimetric(&srgb_primaries, primaries, matrix); + struct wlr_color_transform *matrix_transform = wlr_color_transform_init_matrix(matrix); + if (matrix_transform == NULL) { + return NULL; + } + struct wlr_color_transform *resolved_tf = transfer_function ? + wlr_color_transform_ref(transfer_function) : + wlr_color_transform_init_linear_to_inverse_eotf(WLR_COLOR_TRANSFER_FUNCTION_GAMMA22); + if (resolved_tf == NULL) { + wlr_color_transform_unref(matrix_transform); + return NULL; + } + struct wlr_color_transform *transforms[] = { matrix_transform, resolved_tf }; + size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]); + struct wlr_color_transform *result = wlr_color_transform_init_pipeline(transforms, transforms_len); + wlr_color_transform_unref(matrix_transform); + wlr_color_transform_unref(resolved_tf); + return result; + } +} + static struct wlr_color_transform *get_color_profile(struct wlr_output *output, struct output_config *oc) { - if (oc && oc->set_color_transform) { + if (oc && oc->color_profile == COLOR_PROFILE_TRANSFORM) { if (oc->color_transform) { wlr_color_transform_ref(oc->color_transform); } return oc->color_transform; + } else if (oc && oc->color_profile == COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES) { + return color_profile_from_device(output, oc->color_transform); } else { return NULL; } diff --git a/sway/sway-output.5.scd b/sway/sway-output.5.scd index afe213643..faf59a1f2 100644 --- a/sway/sway-output.5.scd +++ b/sway/sway-output.5.scd @@ -178,9 +178,19 @@ must be separated by one space. For example: updated to work with different bit depths. This command is experimental, and may be removed or changed in the future. -*output* color_profile gamma22|srgb|[icc ] - Sets the color profile for an output. The default is _gamma22_. should be a - path to a display ICC profile. +*output* color_profile [--device-primaries] gamma22|srgb + Sets the color profile for an output. The default is _gamma22_. + + _--device-primaries_ will use the output's self-reported color primaries + when available (e.g. from display EDID). + + Not all renderers support this feature; currently it only works with the + the Vulkan renderer. It is not compatible with HDR support features. + +*output* color_profile icc + Sets the color profile for an output. + + should be a path to a display ICC profile. Not all renderers support this feature; currently it only works with the the Vulkan renderer. Even where supported, the application of the color