diff --git a/.builds/alpine.yml b/.builds/alpine.yml index 6028af008..c296b8013 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -3,6 +3,7 @@ packages: - eudev-dev - ffmpeg-dev - glslang + - lcms2-dev - libinput-dev - libxkbcommon-dev - mesa-dev diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index 2d62d0d1f..dd5dc3a4d 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -2,6 +2,7 @@ image: archlinux packages: - clang - ffmpeg + - lcms2 - libinput - libxkbcommon - mesa diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 0f4007bb7..181970f78 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -6,6 +6,7 @@ packages: - devel/meson # implies ninja - devel/pkgconf - graphics/glslang + - graphics/lcms2 - graphics/libdrm - graphics/mesa-libs - graphics/png diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h new file mode 100644 index 000000000..5cc1b9b52 --- /dev/null +++ b/include/wlr/render/color.h @@ -0,0 +1,44 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_RENDER_COLOR_H +#define WLR_RENDER_COLOR_H + +#include +#include + +/** + * A color transformation formula. + * + * The formula is approximated via a 3D look-up table. A 3D LUT is a + * three-dimensional array where each element is an RGB triplet. The flat lut_3d + * array has a length of dim_len³. + * + * Color channel values in the range [0.0, 1.0] are mapped linearly to + * 3D LUT indices such that 0.0 maps exactly to the first element and 1.0 maps + * exactly to the last element in each dimension. + * + * The offset of the RGB triplet given red, green and blue indices r_index, + * g_index and b_index is: + * + * offset = 3 * (r_index + dim_len * g_index + dim_len * dim_len * b_index) + */ +struct wlr_color_transform { + float *lut_3d; + size_t dim_len; +}; + +/** + * Initialize a color transformation to convert sRGB to an ICC profile. + */ +bool wlr_color_transform_init_srgb_to_icc(struct wlr_color_transform *tr, + const void *data, size_t size); + +void wlr_color_transform_finish(struct wlr_color_transform *tr); + +#endif diff --git a/meson_options.txt b/meson_options.txt index 6977643ca..0c9a1a8ce 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -7,3 +7,4 @@ option('backends', type: 'array', choices: ['auto', 'drm', 'libinput', 'x11'], v option('allocators', type: 'array', choices: ['auto', 'gbm'], value: ['auto'], description: 'Select built-in allocators') option('session', type: 'feature', value: 'auto', description: 'Enable session support') +option('color-management', type: 'feature', value: 'auto', description: 'Enable support for color management') diff --git a/render/color.c b/render/color.c new file mode 100644 index 000000000..9b7ed7f51 --- /dev/null +++ b/render/color.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include + +static void handle_lcms_error(cmsContext ctx, cmsUInt32Number code, const char *text) { + wlr_log(WLR_ERROR, "[lcms] %s", text); +} + +bool wlr_color_transform_init_srgb_to_icc(struct wlr_color_transform *tr, + const void *data, size_t size) { + bool ok = false; + + cmsContext ctx = cmsCreateContext(NULL, NULL); + if (ctx == NULL) { + wlr_log(WLR_ERROR, "cmsCreateContext failed"); + return false; + } + + cmsSetLogErrorHandlerTHR(ctx, handle_lcms_error); + + cmsHPROFILE icc_profile = cmsOpenProfileFromMemTHR(ctx, data, size); + if (icc_profile == NULL) { + wlr_log(WLR_ERROR, "cmsOpenProfileFromMemTHR failed"); + goto out_ctx; + } + + if (cmsGetDeviceClass(icc_profile) != cmsSigDisplayClass) { + wlr_log(WLR_ERROR, "ICC profile must have the Display device class"); + goto out_icc_profile; + } + + cmsHPROFILE srgb_profile = cmsCreate_sRGBProfile(); + if (srgb_profile == NULL) { + wlr_log(WLR_ERROR, "cmsCreate_sRGBProfile failed"); + goto out_icc_profile; + } + + cmsHTRANSFORM lcms_tr = cmsCreateTransformTHR(ctx, + srgb_profile, TYPE_RGB_FLT, icc_profile, TYPE_RGB_FLT, + INTENT_RELATIVE_COLORIMETRIC, 0); + if (lcms_tr == NULL) { + wlr_log(WLR_ERROR, "cmsCreateTransformTHR failed"); + goto out_srgb_profile; + } + + size_t dim_len = 33; + float *lut_3d = calloc(3 * dim_len * dim_len * dim_len, sizeof(float)); + if (lut_3d == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto out_lcms_tr; + } + + float factor = 1.0f / (dim_len - 1); + for (size_t b_index = 0; b_index < dim_len; b_index++) { + for (size_t g_index = 0; g_index < dim_len; g_index++) { + for (size_t r_index = 0; r_index < dim_len; r_index++) { + float rgb_in[3] = { + r_index * factor, + g_index * factor, + b_index * factor, + }; + float rgb_out[3]; + cmsDoTransform(lcms_tr, rgb_in, rgb_out, 1); + + size_t offset = 3 * (r_index + dim_len * g_index + dim_len * dim_len * b_index); + // TODO: maybe clamp values to [0.0, 1.0] here? + lut_3d[offset] = rgb_out[0]; + lut_3d[offset + 1] = rgb_out[1]; + lut_3d[offset + 2] = rgb_out[2]; + } + } + } + + ok = true; + *tr = (struct wlr_color_transform){ + .lut_3d = lut_3d, + .dim_len = dim_len, + }; + +out_lcms_tr: + cmsDeleteTransform(lcms_tr); +out_srgb_profile: + cmsCloseProfile(srgb_profile); +out_icc_profile: + cmsCloseProfile(icc_profile); +out_ctx: + cmsDeleteContext(ctx); + return ok; +} + +void wlr_color_transform_finish(struct wlr_color_transform *tr) { + free(tr->lut_3d); +} diff --git a/render/meson.build b/render/meson.build index 4c3d4522a..a687a7556 100644 --- a/render/meson.build +++ b/render/meson.build @@ -38,3 +38,9 @@ endif subdir('pixman') subdir('allocator') + +lcms2 = dependency('lcms2', required: get_option('color-management')) +if lcms2.found() + wlr_deps += lcms2 + wlr_files += files('color.c') +endif