sway/commands/output: add color_profile "--device-primaries"

When a display is connected, create a color transform from its
self-reported color characteristics
This commit is contained in:
Félix Poisot 2025-12-05 21:51:54 +00:00 committed by Simon Ser
parent 776d445ec5
commit 26eb393d6d
4 changed files with 88 additions and 13 deletions

View file

@ -267,6 +267,12 @@ enum render_bit_depth {
RENDER_BIT_DEPTH_10, 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. * Size and position configuration for a particular output.
* *
@ -288,7 +294,7 @@ struct output_config {
int max_render_time; // In milliseconds int max_render_time; // In milliseconds
int adaptive_sync; int adaptive_sync;
enum render_bit_depth render_bit_depth; enum render_bit_depth render_bit_depth;
bool set_color_transform; enum color_profile color_profile;
struct wlr_color_transform *color_transform; struct wlr_color_transform *color_transform;
int allow_tearing; int allow_tearing;
int hdr; int hdr;

View file

@ -55,6 +55,14 @@ struct cmd_results *output_cmd_color_profile(int argc, char **argv) {
if (!config->handler_context.output_config) { if (!config->handler_context.output_config) {
return cmd_results_new(CMD_FAILURE, "Missing 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) { if (!argc) {
return cmd_results_new(CMD_INVALID, "Missing color_profile first argument."); 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) { if (strcmp(*argv, "gamma22") == 0) {
wlr_color_transform_unref(config->handler_context.output_config->color_transform); wlr_color_transform_unref(config->handler_context.output_config->color_transform);
config->handler_context.output_config->color_transform = NULL; 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.argc = argc - 1;
config->handler_context.leftovers.argv = argv + 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); wlr_color_transform_unref(config->handler_context.output_config->color_transform);
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); 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.argc = argc - 1;
config->handler_context.leftovers.argv = argv + 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, return cmd_results_new(CMD_INVALID,
"Invalid color profile specification: icc type requires a file"); "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]); char *icc_path = strdup(argv[1]);
if (!expand_path(&icc_path)) { 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); wlr_color_transform_unref(config->handler_context.output_config->color_transform);
config->handler_context.output_config->color_transform = tmp; 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.argc = argc - 2;
config->handler_context.leftovers.argv = argv + 2; config->handler_context.leftovers.argv = argv + 2;

View file

@ -75,7 +75,7 @@ struct output_config *new_output_config(const char *name) {
oc->max_render_time = -1; oc->max_render_time = -1;
oc->adaptive_sync = -1; oc->adaptive_sync = -1;
oc->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT; oc->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT;
oc->set_color_transform = false; oc->color_profile = COLOR_PROFILE_DEFAULT;
oc->color_transform = NULL; oc->color_transform = NULL;
oc->power = -1; oc->power = -1;
oc->allow_tearing = -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) { if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) {
dst->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) { if (dst->color_transform) {
wlr_color_transform_unref(dst->color_transform); wlr_color_transform_unref(dst->color_transform);
dst->color_transform = NULL; dst->color_transform = NULL;
} }
dst->set_color_transform = false; dst->color_profile = COLOR_PROFILE_DEFAULT;
} }
if (src->background) { if (src->background) {
free(dst->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) { if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) {
dst->render_bit_depth = src->render_bit_depth; dst->render_bit_depth = src->render_bit_depth;
} }
if (src->set_color_transform) { if (src->color_profile != COLOR_PROFILE_DEFAULT) {
if (src->color_transform) { if (src->color_transform) {
wlr_color_transform_ref(src->color_transform); wlr_color_transform_ref(src->color_transform);
} }
wlr_color_transform_unref(dst->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; dst->color_transform = src->color_transform;
} }
if (src->background) { 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); 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, static struct wlr_color_transform *get_color_profile(struct wlr_output *output,
struct output_config *oc) { struct output_config *oc) {
if (oc && oc->set_color_transform) { if (oc && oc->color_profile == COLOR_PROFILE_TRANSFORM) {
if (oc->color_transform) { if (oc->color_transform) {
wlr_color_transform_ref(oc->color_transform); wlr_color_transform_ref(oc->color_transform);
} }
return 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 { } else {
return NULL; return NULL;
} }

View file

@ -178,9 +178,19 @@ must be separated by one space. For example:
updated to work with different bit depths. This command is experimental, updated to work with different bit depths. This command is experimental,
and may be removed or changed in the future. and may be removed or changed in the future.
*output* <name> color_profile gamma22|srgb|[icc <file>] *output* <name> color_profile [--device-primaries] gamma22|srgb
Sets the color profile for an output. The default is _gamma22_. <file> should be a Sets the color profile for an output. The default is _gamma22_.
path to a display ICC profile.
_--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* <name> color_profile icc <file>
Sets the color profile for an output.
<file> should be a path to a display ICC profile.
Not all renderers support this feature; currently it only works with the Not all renderers support this feature; currently it only works with the
the Vulkan renderer. Even where supported, the application of the color the Vulkan renderer. Even where supported, the application of the color