aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2026-07-21 15:54:27 +0200
committerdec05eba <dec05eba@protonmail.com>2026-07-21 15:54:27 +0200
commit40f5b54158b8aed05137fd63dab06b73edf96ebe (patch)
treef2c35910c51619024644c4e7d50d9c617b09abbd /src
parent2891f2b637058d7fcf4651059cd88dc03e62e68c (diff)
Improve hdr and night color capture (tested on kde)
Diffstat (limited to 'src')
-rw-r--r--src/capture/kms.c152
-rw-r--r--src/kde_night_light.c182
-rw-r--r--src/window/wayland.c31
3 files changed, 289 insertions, 76 deletions
diff --git a/src/capture/kms.c b/src/capture/kms.c
index 31d4b17..0f7c37c 100644
--- a/src/capture/kms.c
+++ b/src/capture/kms.c
@@ -53,10 +53,14 @@ typedef struct {
bool tone_mapping_message_shown;
int drm_card_fd;
- uint32_t gamma_lut_connector_id;
- uint32_t gamma_lut_crtc_id;
+ uint32_t crtc_connector_id;
+ uint32_t crtc_id;
uint32_t gamma_lut_property_id;
+ uint32_t ctm_property_id;
+ uint32_t degamma_lut_property_id;
uint64_t gamma_lut_blob_id;
+ uint64_t gamma_lut_uploaded_blob_id;
+ bool crtc_color_ops_active;
bool night_light_message_shown;
bool hdr_luminance_message_shown;
@@ -292,10 +296,12 @@ static bool drm_plane_is_hdr(const gsr_kms_response_item *drm_fd) {
return drm_fd->has_hdr_metadata && hdr_metadata_is_supported_format(&drm_fd->hdr_metadata);
}
-static void gsr_capture_kms_resolve_gamma_lut_property(gsr_capture_kms *self, uint32_t connector_id) {
- self->gamma_lut_connector_id = connector_id;
- self->gamma_lut_crtc_id = 0;
+static void gsr_capture_kms_resolve_crtc_color_properties(gsr_capture_kms *self, uint32_t connector_id) {
+ self->crtc_connector_id = connector_id;
+ self->crtc_id = 0;
self->gamma_lut_property_id = 0;
+ self->ctm_property_id = 0;
+ self->degamma_lut_property_id = 0;
drmModeConnector *connector = drmModeGetConnectorCurrent(self->drm_card_fd, connector_id);
if(!connector)
@@ -304,16 +310,16 @@ static void gsr_capture_kms_resolve_gamma_lut_property(gsr_capture_kms *self, ui
if(connector->encoder_id) {
drmModeEncoder *encoder = drmModeGetEncoder(self->drm_card_fd, connector->encoder_id);
if(encoder) {
- self->gamma_lut_crtc_id = encoder->crtc_id;
+ self->crtc_id = encoder->crtc_id;
drmModeFreeEncoder(encoder);
}
}
drmModeFreeConnector(connector);
- if(self->gamma_lut_crtc_id == 0)
+ if(self->crtc_id == 0)
return;
- drmModeObjectProperties *properties = drmModeObjectGetProperties(self->drm_card_fd, self->gamma_lut_crtc_id, DRM_MODE_OBJECT_CRTC);
+ drmModeObjectProperties *properties = drmModeObjectGetProperties(self->drm_card_fd, self->crtc_id, DRM_MODE_OBJECT_CRTC);
if(!properties)
return;
@@ -324,53 +330,62 @@ static void gsr_capture_kms_resolve_gamma_lut_property(gsr_capture_kms *self, ui
if(strcmp(property->name, "GAMMA_LUT") == 0)
self->gamma_lut_property_id = property->prop_id;
+ else if(strcmp(property->name, "CTM") == 0)
+ self->ctm_property_id = property->prop_id;
+ else if(strcmp(property->name, "DEGAMMA_LUT") == 0)
+ self->degamma_lut_property_id = property->prop_id;
drmModeFreeProperty(property);
- if(self->gamma_lut_property_id)
- break;
}
drmModeFreeObjectProperties(properties);
}
-static uint64_t gsr_capture_kms_get_gamma_lut_blob_id(gsr_capture_kms *self) {
- if(self->gamma_lut_crtc_id == 0 || self->gamma_lut_property_id == 0)
- return 0;
+static void gsr_capture_kms_read_crtc_color_ops(gsr_capture_kms *self) {
+ self->gamma_lut_blob_id = 0;
+ self->crtc_color_ops_active = false;
+
+ if(self->crtc_id == 0)
+ return;
- uint64_t blob_id = 0;
- drmModeObjectProperties *properties = drmModeObjectGetProperties(self->drm_card_fd, self->gamma_lut_crtc_id, DRM_MODE_OBJECT_CRTC);
+ drmModeObjectProperties *properties = drmModeObjectGetProperties(self->drm_card_fd, self->crtc_id, DRM_MODE_OBJECT_CRTC);
if(!properties)
- return 0;
+ return;
for(uint32_t i = 0; i < properties->count_props; ++i) {
+ if(properties->prop_values[i] == 0)
+ continue;
+
if(properties->props[i] == self->gamma_lut_property_id) {
- blob_id = properties->prop_values[i];
- break;
+ self->gamma_lut_blob_id = properties->prop_values[i];
+ self->crtc_color_ops_active = true;
+ } else if(properties->props[i] == self->ctm_property_id || properties->props[i] == self->degamma_lut_property_id) {
+ self->crtc_color_ops_active = true;
}
}
drmModeFreeObjectProperties(properties);
- return blob_id;
}
-static void gsr_capture_kms_update_gamma_lut(gsr_capture_kms *self, gsr_color_conversion *color_conversion) {
+static void gsr_capture_kms_update_crtc_color_ops(gsr_capture_kms *self, gsr_color_conversion *color_conversion) {
if(self->drm_card_fd <= 0)
return;
- if(self->drm_fd->connector_id != self->gamma_lut_connector_id)
- gsr_capture_kms_resolve_gamma_lut_property(self, self->drm_fd->connector_id);
+ if(self->drm_fd->connector_id != self->crtc_connector_id)
+ gsr_capture_kms_resolve_crtc_color_properties(self, self->drm_fd->connector_id);
+
+ gsr_capture_kms_read_crtc_color_ops(self);
- const uint64_t blob_id = gsr_capture_kms_get_gamma_lut_blob_id(self);
- if(blob_id == self->gamma_lut_blob_id)
+ if(self->gamma_lut_blob_id == self->gamma_lut_uploaded_blob_id)
return;
- self->gamma_lut_blob_id = blob_id;
- if(blob_id == 0) {
+ self->gamma_lut_uploaded_blob_id = self->gamma_lut_blob_id;
+ if(self->gamma_lut_uploaded_blob_id == 0) {
gsr_color_conversion_set_gamma_lut(color_conversion, NULL, 0);
return;
}
- drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(self->drm_card_fd, blob_id);
+ drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(self->drm_card_fd, self->gamma_lut_uploaded_blob_id);
if(!blob) {
- self->gamma_lut_blob_id = 0;
+ self->gamma_lut_uploaded_blob_id = 0;
gsr_color_conversion_set_gamma_lut(color_conversion, NULL, 0);
return;
}
@@ -386,7 +401,7 @@ static void gsr_capture_kms_update_gamma_lut(gsr_capture_kms *self, gsr_color_co
}
gsr_color_conversion_set_gamma_lut(color_conversion, rgb_values, num_entries);
} else {
- self->gamma_lut_blob_id = 0;
+ self->gamma_lut_uploaded_blob_id = 0;
gsr_color_conversion_set_gamma_lut(color_conversion, NULL, 0);
}
@@ -394,42 +409,57 @@ static void gsr_capture_kms_update_gamma_lut(gsr_capture_kms *self, gsr_color_co
drmModeFreePropertyBlob(blob);
}
-static void gsr_capture_kms_get_monitor_luminances(gsr_capture_kms *self, const gsr_kms_response_item *drm_fd, float *sdr_white_luminance, float *hdr_peak_luminance) {
- *sdr_white_luminance = 0.0f;
- *hdr_peak_luminance = hdr_metadata_get_max_luminance(&drm_fd->hdr_metadata);
+static void gsr_capture_kms_get_monitor_hdr_info(gsr_capture_kms *self, const gsr_kms_response_item *drm_fd, gsr_monitor_hdr_info *monitor_hdr_info) {
+ if(!gsr_window_get_monitor_hdr_info(self->params.egl->window, self->params.display_to_capture, monitor_hdr_info))
+ memset(monitor_hdr_info, 0, sizeof(*monitor_hdr_info));
- gsr_monitor_hdr_info monitor_hdr_info;
- if(gsr_window_get_monitor_hdr_info(self->params.egl->window, self->params.display_to_capture, &monitor_hdr_info)) {
- if(monitor_hdr_info.sdr_white_luminance > 0.0f)
- *sdr_white_luminance = monitor_hdr_info.sdr_white_luminance;
- if(monitor_hdr_info.max_peak_luminance > 0.0f)
- *hdr_peak_luminance = monitor_hdr_info.max_peak_luminance;
- }
+ if(monitor_hdr_info->max_peak_luminance <= 0.0f)
+ monitor_hdr_info->max_peak_luminance = hdr_metadata_get_max_luminance(&drm_fd->hdr_metadata);
- if(*hdr_peak_luminance <= 0.0f)
- *hdr_peak_luminance = HDR_PEAK_LUMINANCE_FALLBACK;
+ if(monitor_hdr_info->max_peak_luminance <= 0.0f)
+ monitor_hdr_info->max_peak_luminance = HDR_PEAK_LUMINANCE_FALLBACK;
- if(*sdr_white_luminance <= 0.0f)
- *sdr_white_luminance = SDR_WHITE_LUMINANCE_REFERENCE;
+ if(monitor_hdr_info->sdr_white_luminance <= 0.0f)
+ monitor_hdr_info->sdr_white_luminance = SDR_WHITE_LUMINANCE_REFERENCE;
- if(*sdr_white_luminance > *hdr_peak_luminance)
- *sdr_white_luminance = *hdr_peak_luminance;
+ if(monitor_hdr_info->sdr_white_luminance > monitor_hdr_info->max_peak_luminance)
+ monitor_hdr_info->sdr_white_luminance = monitor_hdr_info->max_peak_luminance;
+}
+
+static void mat3_multiply(const float a[9], const float b[9], float result[9]) {
+ for(int row = 0; row < 3; ++row) {
+ for(int column = 0; column < 3; ++column) {
+ result[row*3 + column] =
+ a[row*3 + 0] * b[0*3 + column] +
+ a[row*3 + 1] * b[1*3 + column] +
+ a[row*3 + 2] * b[2*3 + column];
+ }
+ }
}
static void gsr_capture_kms_update_hdr_color_transforms(gsr_capture_kms *self, gsr_color_conversion *color_conversion, const gsr_kms_response_item *drm_fd) {
const bool plane_is_hdr = drm_plane_is_hdr(drm_fd);
- gsr_color_conversion_enable_gamma_lut(color_conversion, plane_is_hdr && self->gamma_lut_blob_id != 0);
+
+ gsr_monitor_hdr_info monitor_hdr_info;
+ gsr_capture_kms_get_monitor_hdr_info(self, drm_fd, &monitor_hdr_info);
+ const float sdr_white_luminance = monitor_hdr_info.sdr_white_luminance;
+ const float hdr_peak_luminance = monitor_hdr_info.max_peak_luminance;
+
+ /* For hdr the night light luminance dimming is normalized away by the reference luminance from the compositor, which the compositor
+ dims by the same amount. The hdr image is encoded in the color volume of the monitor reported by the compositor rather than sRGB */
+ const gsr_night_light_compensation night_light_compensation = plane_is_hdr ? GSR_NIGHT_LIGHT_COMPENSATION_TINT_KEEP_LUMINANCE : GSR_NIGHT_LIGHT_COMPENSATION_TINT;
+ const gsr_color_chromaticities *night_light_container_primaries = plane_is_hdr && monitor_hdr_info.primaries_valid ? &monitor_hdr_info.primaries : NULL;
float night_light_matrix[9];
- const bool night_light = self->params.kde_night_light && gsr_kde_night_light_get_inverse_matrix(self->params.kde_night_light, night_light_matrix);
+ const bool night_light = self->params.kde_night_light && gsr_kde_night_light_get_inverse_matrix(self->params.kde_night_light, night_light_compensation, night_light_container_primaries, night_light_matrix);
if(night_light && !self->night_light_message_shown) {
self->night_light_message_shown = true;
fprintf(stderr, "gsr info: gsr_capture_kms_update_hdr_color_transforms: night light is active, removing the night light tint from the capture\n");
}
- float sdr_white_luminance = 0.0f;
- float hdr_peak_luminance = 0.0f;
- gsr_capture_kms_get_monitor_luminances(self, drm_fd, &sdr_white_luminance, &hdr_peak_luminance);
+ /* The compositor applies the night light tint at scanout with the crtc gamma lut when it offloads its color transforms to the crtc
+ ("prefer efficiency" color accuracy on kde plasma), so the gamma lut is applied to the captured image to get the same image that the monitor displays */
+ gsr_color_conversion_enable_gamma_lut(color_conversion, (plane_is_hdr || night_light) && self->gamma_lut_uploaded_blob_id != 0);
/* Hdr passthrough: the compositor composites sdr white at the monitors sdr white luminance while the video should have
sdr white at the reference luminance (203 nits), so the luminance of the video is scaled to match that */
@@ -466,10 +496,13 @@ static void gsr_capture_kms_update_hdr_color_transforms(gsr_capture_kms *self, g
0.016391f, 0.088013f, 0.895595f
};
float color_matrix[9];
+ if(night_light) {
+ mat3_multiply(BT709_TO_BT2020, night_light_matrix, color_matrix);
+ } else {
+ memcpy(color_matrix, BT709_TO_BT2020, sizeof(color_matrix));
+ }
for(int i = 0; i < 9; ++i) {
- color_matrix[i] = BT709_TO_BT2020[i] * (SDR_WHITE_LUMINANCE_REFERENCE / 10000.0f);
- if(night_light)
- color_matrix[i] *= night_light_matrix[(i % 3) * 4];
+ color_matrix[i] *= SDR_WHITE_LUMINANCE_REFERENCE / 10000.0f;
}
gsr_color_conversion_set_color_matrix(color_conversion, color_matrix, GSR_COLOR_MATRIX_TRANSFER_GAMMA22_TO_PQ);
} else if(night_light || luminance_scale != 1.0f) {
@@ -519,10 +552,9 @@ static void gsr_kms_set_hdr_metadata(gsr_capture_kms *self, const gsr_kms_respon
self->hdr_metadata_set = true;
self->hdr_metadata = drm_fd->hdr_metadata;
- float sdr_white_luminance = 0.0f;
- float hdr_peak_luminance = 0.0f;
- gsr_capture_kms_get_monitor_luminances(self, drm_fd, &sdr_white_luminance, &hdr_peak_luminance);
- self->hdr_video_max_luminance = hdr_peak_luminance * (SDR_WHITE_LUMINANCE_REFERENCE / sdr_white_luminance);
+ gsr_monitor_hdr_info monitor_hdr_info;
+ gsr_capture_kms_get_monitor_hdr_info(self, drm_fd, &monitor_hdr_info);
+ self->hdr_video_max_luminance = monitor_hdr_info.max_peak_luminance * (SDR_WHITE_LUMINANCE_REFERENCE / monitor_hdr_info.sdr_white_luminance);
}
static vec2i swap_vec2i(vec2i value) {
@@ -779,7 +811,7 @@ static void gsr_capture_kms_update_connector_ids(gsr_capture_kms *self) {
if(self->is_x11)
return;
- self->gamma_lut_connector_id = 0;
+ self->crtc_connector_id = 0;
self->monitor_id.num_connector_ids = 0;
const gsr_connection_type connection_type = self->is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM;
@@ -831,8 +863,8 @@ static void gsr_capture_kms_pre_capture(gsr_capture *cap, gsr_capture_metadata *
if(!self->drm_fd)
return;
- if(drm_plane_is_hdr(self->drm_fd))
- gsr_capture_kms_update_gamma_lut(self, color_conversion);
+ if(self->params.kde_night_light || drm_plane_is_hdr(self->drm_fd))
+ gsr_capture_kms_update_crtc_color_ops(self, color_conversion);
if(self->params.hdr) {
if(drm_plane_is_hdr(self->drm_fd))
diff --git a/src/kde_night_light.c b/src/kde_night_light.c
index bed43af..d5c9578 100644
--- a/src/kde_night_light.c
+++ b/src/kde_night_light.c
@@ -19,13 +19,18 @@ typedef struct {
double v[3];
} vec3d;
+typedef struct {
+ double m[9];
+} mat3d;
+
struct gsr_kde_night_light {
pthread_t thread;
bool thread_created;
atomic_bool stop;
atomic_int temperature;
- int cached_temperature;
- float cached_inverse_matrix[9];
+ int cached_temperature[2];
+ gsr_color_chromaticities cached_container_primaries[2];
+ float cached_inverse_matrix[2][9];
};
/* Blackbody whitepoint table for 1000K-6500K at 100K intervals, from https://github.com/jonls/redshift/blob/master/README-colorramp */
@@ -77,13 +82,152 @@ static vec3d night_light_channel_factors(int temperature) {
return result;
}
-/* Kde plasma multiplies the composited image with the night light channel factors in linear space (verified against kde plasma 6.7, both in sdr and hdr mode) */
-static void compute_inverse_night_light_matrix(int temperature, float inverse_matrix[9]) {
- const vec3d factors = night_light_channel_factors(temperature);
+static const gsr_color_chromaticities SRGB_CHROMATICITIES = {
+ .red_x = 0.64f, .red_y = 0.33f,
+ .green_x = 0.30f, .green_y = 0.60f,
+ .blue_x = 0.15f, .blue_y = 0.06f,
+ .white_x = 0.3127f, .white_y = 0.3290f
+};
+
+/* Bradford cone response matrices, the same values that kde plasma uses (kwin colorspace.cpp, chromaticAdaptationMatrix) */
+static const mat3d BRADFORD = { .m = {
+ 0.8951, 0.2664, -0.1614,
+ -0.7502, 1.7135, 0.0367,
+ 0.0389, -0.0685, 1.0296
+}};
+
+static const mat3d BRADFORD_INVERSE = { .m = {
+ 0.9869929, -0.1470543, 0.1599627,
+ 0.4323053, 0.5183603, 0.0492912,
+ -0.0085287, 0.0400428, 0.9684867
+}};
+
+static vec3d chromaticity_to_xyz(double x, double y) {
+ return (vec3d){ .v = { x / y, 1.0, (1.0 - x - y) / y } };
+}
+
+static vec3d mat3d_mul_vec3d(const mat3d *matrix, vec3d vec) {
+ vec3d result;
+ for(int i = 0; i < 3; ++i) {
+ result.v[i] = matrix->m[i*3 + 0] * vec.v[0] + matrix->m[i*3 + 1] * vec.v[1] + matrix->m[i*3 + 2] * vec.v[2];
+ }
+ return result;
+}
+
+static mat3d mat3d_multiply(const mat3d *a, const mat3d *b) {
+ mat3d result;
+ for(int row = 0; row < 3; ++row) {
+ for(int column = 0; column < 3; ++column) {
+ result.m[row*3 + column] =
+ a->m[row*3 + 0] * b->m[0*3 + column] +
+ a->m[row*3 + 1] * b->m[1*3 + column] +
+ a->m[row*3 + 2] * b->m[2*3 + column];
+ }
+ }
+ return result;
+}
+
+static bool mat3d_invert(const mat3d *matrix, mat3d *result) {
+ const double *m = matrix->m;
+ const double c00 = m[4]*m[8] - m[5]*m[7];
+ const double c01 = m[5]*m[6] - m[3]*m[8];
+ const double c02 = m[3]*m[7] - m[4]*m[6];
+ const double determinant = m[0]*c00 + m[1]*c01 + m[2]*c02;
+ if(fabs(determinant) < 1e-12)
+ return false;
+
+ const double inverse_determinant = 1.0 / determinant;
+ result->m[0] = c00 * inverse_determinant;
+ result->m[1] = (m[2]*m[7] - m[1]*m[8]) * inverse_determinant;
+ result->m[2] = (m[1]*m[5] - m[2]*m[4]) * inverse_determinant;
+ result->m[3] = c01 * inverse_determinant;
+ result->m[4] = (m[0]*m[8] - m[2]*m[6]) * inverse_determinant;
+ result->m[5] = (m[2]*m[3] - m[0]*m[5]) * inverse_determinant;
+ result->m[6] = c02 * inverse_determinant;
+ result->m[7] = (m[1]*m[6] - m[0]*m[7]) * inverse_determinant;
+ result->m[8] = (m[0]*m[4] - m[1]*m[3]) * inverse_determinant;
+ return true;
+}
+
+static bool rgb_to_xyz_matrix(const gsr_color_chromaticities *chromaticities, mat3d *result) {
+ const vec3d red = chromaticity_to_xyz(chromaticities->red_x, chromaticities->red_y);
+ const vec3d green = chromaticity_to_xyz(chromaticities->green_x, chromaticities->green_y);
+ const vec3d blue = chromaticity_to_xyz(chromaticities->blue_x, chromaticities->blue_y);
+ const vec3d white = chromaticity_to_xyz(chromaticities->white_x, chromaticities->white_y);
+
+ mat3d primaries;
+ for(int i = 0; i < 3; ++i) {
+ primaries.m[i*3 + 0] = red.v[i];
+ primaries.m[i*3 + 1] = green.v[i];
+ primaries.m[i*3 + 2] = blue.v[i];
+ }
+
+ mat3d primaries_inverse;
+ if(!mat3d_invert(&primaries, &primaries_inverse))
+ return false;
+
+ const vec3d white_scale = mat3d_mul_vec3d(&primaries_inverse, white);
+ for(int i = 0; i < 3; ++i) {
+ result->m[i*3 + 0] = red.v[i] * white_scale.v[0];
+ result->m[i*3 + 1] = green.v[i] * white_scale.v[1];
+ result->m[i*3 + 2] = blue.v[i] * white_scale.v[2];
+ }
+ return true;
+}
+
+static mat3d bradford_chromatic_adaptation_matrix(vec3d source_whitepoint, vec3d destination_whitepoint) {
+ const vec3d source_response = mat3d_mul_vec3d(&BRADFORD, source_whitepoint);
+ const vec3d destination_response = mat3d_mul_vec3d(&BRADFORD, destination_whitepoint);
+ mat3d scaled_bradford = BRADFORD;
+ for(int i = 0; i < 3; ++i) {
+ const double response_scale = destination_response.v[i] / source_response.v[i];
+ for(int j = 0; j < 3; ++j) {
+ scaled_bradford.m[i*3 + j] *= response_scale;
+ }
+ }
+ return mat3d_multiply(&BRADFORD_INVERSE, &scaled_bradford);
+}
+
+/* Kde plasma applies night light by moving the whitepoint of the output to the night light whitepoint with bradford chromatic adaptation
+ and dimming the output by the luminance of that whitepoint (kwin drm_output.cpp, applyNightLight - verified against kde plasma 6.7).
+ The chromatic adaptation acts on the image in the color volume that the image is encoded in, so the inverse is conjugated by |container_primaries|,
+ which is assumed to have a D65 whitepoint before the night light whitepoint change */
+static void compute_inverse_night_light_matrix(int temperature, gsr_night_light_compensation compensation, const gsr_color_chromaticities *container_primaries, float inverse_matrix[9]) {
memset(inverse_matrix, 0, sizeof(float) * 9);
- inverse_matrix[0] = 1.0 / fmax(factors.v[0], 0.0001);
- inverse_matrix[4] = 1.0 / fmax(factors.v[1], 0.0001);
- inverse_matrix[8] = 1.0 / fmax(factors.v[2], 0.0001);
+ inverse_matrix[0] = 1.0f;
+ inverse_matrix[4] = 1.0f;
+ inverse_matrix[8] = 1.0f;
+
+ const vec3d factors = night_light_channel_factors(temperature);
+
+ gsr_color_chromaticities container_primaries_d65 = *container_primaries;
+ container_primaries_d65.white_x = SRGB_CHROMATICITIES.white_x;
+ container_primaries_d65.white_y = SRGB_CHROMATICITIES.white_y;
+
+ mat3d srgb_to_xyz, container_to_xyz, xyz_to_container;
+ if(!rgb_to_xyz_matrix(&SRGB_CHROMATICITIES, &srgb_to_xyz))
+ return;
+ if(!rgb_to_xyz_matrix(&container_primaries_d65, &container_to_xyz))
+ return;
+ if(!mat3d_invert(&container_to_xyz, &xyz_to_container))
+ return;
+
+ vec3d night_light_whitepoint = mat3d_mul_vec3d(&srgb_to_xyz, factors);
+ const double luminance = fmax(night_light_whitepoint.v[1], 0.0001);
+ for(int i = 0; i < 3; ++i) {
+ night_light_whitepoint.v[i] /= luminance;
+ }
+
+ const vec3d d65_whitepoint = chromaticity_to_xyz(SRGB_CHROMATICITIES.white_x, SRGB_CHROMATICITIES.white_y);
+ const mat3d inverse_adaptation = bradford_chromatic_adaptation_matrix(night_light_whitepoint, d65_whitepoint);
+
+ const mat3d adaptation_in_container = mat3d_multiply(&xyz_to_container, &inverse_adaptation);
+ const mat3d inverse_tint = mat3d_multiply(&adaptation_in_container, &container_to_xyz);
+
+ const double luminance_scale = compensation == GSR_NIGHT_LIGHT_COMPENSATION_TINT ? 1.0 / luminance : 1.0;
+ for(int i = 0; i < 9; ++i) {
+ inverse_matrix[i] = inverse_tint.m[i] * luminance_scale;
+ }
}
static bool dbus_get_night_light_property(DBusConnection *connection, const char *property_name, int expected_type, DBusBasicValue *value) {
@@ -151,7 +295,8 @@ gsr_kde_night_light* gsr_kde_night_light_create(void) {
if(!self)
return NULL;
- self->cached_temperature = -1;
+ self->cached_temperature[0] = -1;
+ self->cached_temperature[1] = -1;
if(pthread_create(&self->thread, NULL, night_light_poll_thread, self) != 0) {
free(self);
@@ -173,7 +318,7 @@ void gsr_kde_night_light_destroy(gsr_kde_night_light *self) {
free(self);
}
-bool gsr_kde_night_light_get_inverse_matrix(gsr_kde_night_light *self, float inverse_matrix[9]) {
+bool gsr_kde_night_light_get_inverse_matrix(gsr_kde_night_light *self, gsr_night_light_compensation compensation, const gsr_color_chromaticities *container_primaries, float inverse_matrix[9]) {
if(!self)
return false;
@@ -181,12 +326,17 @@ bool gsr_kde_night_light_get_inverse_matrix(gsr_kde_night_light *self, float inv
if(temperature <= 0)
return false;
- if(self->cached_temperature != temperature) {
- compute_inverse_night_light_matrix(temperature, self->cached_inverse_matrix);
- self->cached_temperature = temperature;
+ if(!container_primaries)
+ container_primaries = &SRGB_CHROMATICITIES;
+
+ const int cache_index = compensation;
+ if(self->cached_temperature[cache_index] != temperature || memcmp(&self->cached_container_primaries[cache_index], container_primaries, sizeof(*container_primaries)) != 0) {
+ compute_inverse_night_light_matrix(temperature, compensation, container_primaries, self->cached_inverse_matrix[cache_index]);
+ self->cached_temperature[cache_index] = temperature;
+ self->cached_container_primaries[cache_index] = *container_primaries;
}
- memcpy(inverse_matrix, self->cached_inverse_matrix, sizeof(self->cached_inverse_matrix));
+ memcpy(inverse_matrix, self->cached_inverse_matrix[cache_index], sizeof(self->cached_inverse_matrix[cache_index]));
return true;
}
@@ -204,8 +354,10 @@ void gsr_kde_night_light_destroy(gsr_kde_night_light *self) {
(void)self;
}
-bool gsr_kde_night_light_get_inverse_matrix(gsr_kde_night_light *self, float inverse_matrix[9]) {
+bool gsr_kde_night_light_get_inverse_matrix(gsr_kde_night_light *self, gsr_night_light_compensation compensation, const gsr_color_chromaticities *container_primaries, float inverse_matrix[9]) {
(void)self;
+ (void)compensation;
+ (void)container_primaries;
(void)inverse_matrix;
return false;
}
diff --git a/src/window/wayland.c b/src/window/wayland.c
index dc5dfc5..6c263c5 100644
--- a/src/window/wayland.c
+++ b/src/window/wayland.c
@@ -33,6 +33,8 @@ typedef struct {
uint32_t reference_luminance; /* cd/m² (nits) */
uint32_t max_luminance; /* cd/m² (nits) */
bool luminance_valid;
+ int32_t primaries[8]; /* CIE 1931 xy chromaticity coordinates multiplied by 1 million, in the order red, green, blue, white */
+ bool primaries_valid;
} gsr_wayland_output;
struct gsr_window_wayland {
@@ -113,6 +115,7 @@ static void image_description_info_handle_done(void *data, struct wp_image_descr
wp_image_description_info_v1_destroy(info);
output->image_description_info = NULL;
output->luminance_valid = output->reference_luminance > 0 || output->max_luminance > 0;
+ output->primaries_valid = output->primaries[1] > 0 && output->primaries[3] > 0 && output->primaries[5] > 0 && output->primaries[7] > 0;
}
static void image_description_info_handle_icc_file(void *data, struct wp_image_description_info_v1 *info, int32_t icc, uint32_t icc_size) {
@@ -121,6 +124,19 @@ static void image_description_info_handle_icc_file(void *data, struct wp_image_d
}
static void image_description_info_handle_primaries(void *data, struct wp_image_description_info_v1 *info, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) {
+ (void)info;
+ gsr_wayland_output *output = data;
+ output->primaries[0] = r_x;
+ output->primaries[1] = r_y;
+ output->primaries[2] = g_x;
+ output->primaries[3] = g_y;
+ output->primaries[4] = b_x;
+ output->primaries[5] = b_y;
+ output->primaries[6] = w_x;
+ output->primaries[7] = w_y;
+}
+
+static void image_description_info_handle_target_primaries(void *data, struct wp_image_description_info_v1 *info, int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) {
(void)data; (void)info; (void)r_x; (void)r_y; (void)g_x; (void)g_y; (void)b_x; (void)b_y; (void)w_x; (void)w_y;
}
@@ -147,7 +163,7 @@ static const struct wp_image_description_info_v1_listener image_description_info
.tf_power = image_description_info_handle_uint_noop,
.tf_named = image_description_info_handle_uint_noop,
.luminances = image_description_info_handle_luminances,
- .target_primaries = image_description_info_handle_primaries,
+ .target_primaries = image_description_info_handle_target_primaries,
.target_luminance = image_description_info_handle_target_luminance,
.target_max_cll = image_description_info_handle_uint_noop,
.target_max_fall = image_description_info_handle_uint_noop,
@@ -580,6 +596,19 @@ static bool gsr_window_wayland_get_monitor_hdr_info(const gsr_window *window, co
hdr_info->sdr_white_luminance = output->reference_luminance;
hdr_info->max_peak_luminance = output->max_luminance;
+ hdr_info->primaries_valid = output->primaries_valid;
+ if(output->primaries_valid) {
+ hdr_info->primaries.red_x = output->primaries[0] / 1000000.0f;
+ hdr_info->primaries.red_y = output->primaries[1] / 1000000.0f;
+ hdr_info->primaries.green_x = output->primaries[2] / 1000000.0f;
+ hdr_info->primaries.green_y = output->primaries[3] / 1000000.0f;
+ hdr_info->primaries.blue_x = output->primaries[4] / 1000000.0f;
+ hdr_info->primaries.blue_y = output->primaries[5] / 1000000.0f;
+ hdr_info->primaries.white_x = output->primaries[6] / 1000000.0f;
+ hdr_info->primaries.white_y = output->primaries[7] / 1000000.0f;
+ } else {
+ memset(&hdr_info->primaries, 0, sizeof(hdr_info->primaries));
+ }
return true;
}
return false;