render/color: introduce wlr_color_transform

Co-authored-by: Manuel Stoeckl <code@mstoeckl.com>
master
Simon Ser 1 year ago
parent ee0007c0f2
commit 895e3d18b9

@ -2,6 +2,7 @@ image: alpine/edge
packages: packages:
- eudev-dev - eudev-dev
- glslang - glslang
- lcms2-dev
- libdisplay-info-dev - libdisplay-info-dev
- libinput-dev - libinput-dev
- libliftoff-dev - libliftoff-dev

@ -1,6 +1,7 @@
image: archlinux image: archlinux
packages: packages:
- clang - clang
- lcms2
- libinput - libinput
- libdisplay-info - libdisplay-info
- libliftoff - libliftoff

@ -5,6 +5,7 @@ packages:
- devel/meson # implies ninja - devel/meson # implies ninja
- devel/pkgconf - devel/pkgconf
- graphics/glslang - graphics/glslang
- graphics/lcms2
- graphics/libdrm - graphics/libdrm
- graphics/libliftoff - graphics/libliftoff
- graphics/mesa-libs - graphics/mesa-libs

@ -20,6 +20,9 @@ endif
if not features.get('vulkan-renderer') if not features.get('vulkan-renderer')
exclude_files += 'render/vulkan.h' exclude_files += 'render/vulkan.h'
endif endif
if not features.get('color-management')
exclude_files += 'render/color.h'
endif
if not features.get('session') if not features.get('session')
exclude_files += 'backend/session.h' exclude_files += 'backend/session.h'
endif endif

@ -0,0 +1,39 @@
#ifndef RENDER_COLOR_H
#define RENDER_COLOR_H
#include <stdint.h>
#include <wlr/util/addon.h>
/**
* 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_lut3d {
float *lut_3d;
size_t dim_len;
};
enum wlr_color_transform_type {
COLOR_TRANSFORM_SRGB,
COLOR_TRANSFORM_LUT_3D,
};
struct wlr_color_transform {
int ref_count;
struct wlr_addon_set addons; // per-renderer helper state
enum wlr_color_transform_type type;
struct wlr_color_transform_lut3d lut3d;
};
#endif

@ -14,4 +14,6 @@
#mesondefine WLR_HAS_SESSION #mesondefine WLR_HAS_SESSION
#mesondefine WLR_HAS_COLOR_MANAGEMENT
#endif #endif

@ -0,0 +1,55 @@
/*
* 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 <stdbool.h>
#include <sys/types.h>
/**
* A color transformation formula, which maps a linear color space with
* sRGB primaries to an output color space.
*
* For ease of use, this type is heap allocated and reference counted.
* Use wlr_color_transform_ref()/wlr_color_transform_unref(). The initial reference
* count after creation is 1.
*
* Color transforms are immutable; their type/parameters should not be changed,
* and this API provides no functions to modify them after creation.
*
* This formula may be implemented using a 3d look-up table, or some other
* means.
*/
struct wlr_color_transform;
/**
* Initialize a color transformation to convert linear
* (with sRGB(?) primaries) to an ICC profile. Returns NULL on failure.
*/
struct wlr_color_transform *wlr_color_transform_init_linear_to_icc(
const void *data, size_t size);
/**
* Initialize a color transformation to apply sRGB encoding.
* Returns NULL on failure.
*/
struct wlr_color_transform *wlr_color_transform_init_srgb(void);
/**
* Increase the reference count of the color transform by 1.
*/
void wlr_color_transform_ref(struct wlr_color_transform *tr);
/**
* Reduce the reference count of the color transform by 1; freeing it and
* all associated resources when the reference count hits zero.
*/
void wlr_color_transform_unref(struct wlr_color_transform *tr);
#endif

@ -95,6 +95,7 @@ features = {
'vulkan-renderer': false, 'vulkan-renderer': false,
'gbm-allocator': false, 'gbm-allocator': false,
'session': false, 'session': false,
'color-management': false,
} }
internal_features = { internal_features = {
'xcb-errors': false, 'xcb-errors': false,

@ -7,3 +7,4 @@ option('backends', type: 'array', choices: ['auto', 'drm', 'libinput', 'x11'], v
option('allocators', type: 'array', choices: ['auto', 'gbm'], value: ['auto'], option('allocators', type: 'array', choices: ['auto', 'gbm'], value: ['auto'],
description: 'Select built-in allocators') description: 'Select built-in allocators')
option('session', type: 'feature', value: 'auto', description: 'Enable session support') option('session', type: 'feature', value: 'auto', description: 'Enable session support')
option('color-management', type: 'feature', value: 'auto', description: 'Enable support for color management')

@ -0,0 +1,155 @@
#include <assert.h>
#include <lcms2.h>
#include <stdlib.h>
#include <wlr/util/log.h>
#include <wlr/util/addon.h>
#include <wlr/render/color.h>
#include "render/color.h"
static const cmsCIExyY srgb_whitepoint = { 0.3127, 0.3291, 1 };
static const cmsCIExyYTRIPLE srgb_primaries = {
.Red = { 0.64, 0.33, 1 },
.Green = { 0.3, 0.6, 1 },
.Blue = { 0.15, 0.06, 1},
};
static void handle_lcms_error(cmsContext ctx, cmsUInt32Number code, const char *text) {
wlr_log(WLR_ERROR, "[lcms] %s", text);
}
struct wlr_color_transform *wlr_color_transform_init_linear_to_icc(
const void *data, size_t size) {
struct wlr_color_transform *tx = NULL;
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;
}
cmsToneCurve *linear_tone_curve = cmsBuildGamma(ctx, 1);
if (linear_tone_curve == NULL) {
wlr_log(WLR_ERROR, "cmsBuildGamma failed");
goto out_icc_profile;
}
cmsToneCurve *linear_tf[] = {
linear_tone_curve,
linear_tone_curve,
linear_tone_curve,
};
cmsHPROFILE srgb_profile = cmsCreateRGBProfileTHR(ctx, &srgb_whitepoint,
&srgb_primaries, linear_tf);
if (srgb_profile == NULL) {
wlr_log(WLR_ERROR, "cmsCreateRGBProfileTHR failed");
goto out_linear_tone_curve;
}
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];
// TODO: use a single call to cmsDoTransform for the entire calculation?
// this does require allocating an extra temp buffer
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];
}
}
}
tx = calloc(1, sizeof(struct wlr_color_transform));
if (!tx) {
goto out_lcms_tr;
}
tx->type = COLOR_TRANSFORM_LUT_3D;
tx->lut3d.dim_len = dim_len;
tx->lut3d.lut_3d = lut_3d;
tx->ref_count = 1;
wlr_addon_set_init(&tx->addons);
out_lcms_tr:
cmsDeleteTransform(lcms_tr);
out_linear_tone_curve:
cmsFreeToneCurve(linear_tone_curve);
out_srgb_profile:
cmsCloseProfile(srgb_profile);
out_icc_profile:
cmsCloseProfile(icc_profile);
out_ctx:
cmsDeleteContext(ctx);
return tx;
}
struct wlr_color_transform *wlr_color_transform_init_srgb(void) {
struct wlr_color_transform *tx = calloc(1, sizeof(struct wlr_color_transform));
if (!tx) {
return NULL;
}
tx->type = COLOR_TRANSFORM_SRGB;
tx->ref_count = 1;
wlr_addon_set_init(&tx->addons);
return tx;
}
static void color_transform_destroy(struct wlr_color_transform *tr) {
free(tr->lut3d.lut_3d);
wlr_addon_set_finish(&tr->addons);
free(tr);
}
void wlr_color_transform_ref(struct wlr_color_transform *tr) {
tr->ref_count += 1;
}
void wlr_color_transform_unref(struct wlr_color_transform *tr) {
if (!tr) {
return;
}
assert(tr->ref_count > 0);
tr->ref_count -= 1;
if (tr->ref_count == 0) {
color_transform_destroy(tr);
}
}

@ -39,3 +39,10 @@ endif
subdir('pixman') subdir('pixman')
subdir('allocator') subdir('allocator')
lcms2 = dependency('lcms2', required: get_option('color-management'))
if lcms2.found()
wlr_deps += lcms2
wlr_files += files('color.c')
features += { 'color-management': true }
endif

Loading…
Cancel
Save