[PATCH 2/6] Attach input profiles and build corresponding LUTs

Niels Ole Salscheider niels_ole at salscheider-online.de
Mon Oct 13 10:40:47 PDT 2014


This implements the functionality to attach a profile to a surface
in weston. An LUT is built from the profile that can be used to
transform colors from the input color space to the blending color
space.

An sRGB color space is assumed for all newly created outputs

This patch uses the sRGB color space as blending space because it
uses 8 bit LUTs for now and I want to avoid additional banding. In
the long term we should use a linear blending space though to get
correct results.

Signed-off-by: Niels Ole Salscheider <niels_ole at salscheider-online.de>
---
 Makefile.am      |   3 +
 configure.ac     |   2 +-
 src/compositor.c | 411 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/compositor.h |  41 ++++++
 4 files changed, 453 insertions(+), 4 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 3af3b46..1ecab56 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -45,6 +45,9 @@ weston_CPPFLAGS = $(AM_CPPFLAGS) -DIN_WESTON
 weston_CFLAGS = $(GCC_CFLAGS) $(COMPOSITOR_CFLAGS) $(LIBUNWIND_CFLAGS)
 weston_LDADD = $(COMPOSITOR_LIBS) $(LIBUNWIND_LIBS) \
 	$(DLOPEN_LIBS) -lm libshared.la
+if HAVE_LCMS
+weston_LDADD += $(LCMS_LIBS)
+endif
 
 weston_SOURCES =					\
 	src/git-version.h				\
diff --git a/configure.ac b/configure.ac
index 1c133bd..00b7cca 100644
--- a/configure.ac
+++ b/configure.ac
@@ -59,7 +59,7 @@ AC_CHECK_HEADERS([execinfo.h])
 
 AC_CHECK_FUNCS([mkostemp strchrnul initgroups posix_fallocate])
 
-COMPOSITOR_MODULES="wayland-server >= 1.5.91 pixman-1 >= 0.25.2"
+COMPOSITOR_MODULES="wayland-server >= 1.5.91 pixman-1 >= 0.25.2 glib-2.0"
 
 AC_ARG_ENABLE(egl, [  --disable-egl],,
               enable_egl=yes)
diff --git a/src/compositor.c b/src/compositor.c
index 29731c7..4f959a4 100644
--- a/src/compositor.c
+++ b/src/compositor.c
@@ -56,6 +56,7 @@
 #include "compositor.h"
 #include "scaler-server-protocol.h"
 #include "presentation_timing-server-protocol.h"
+#include "cms-server-protocol.h"
 #include "../shared/os-compatibility.h"
 #include "git-version.h"
 #include "version.h"
@@ -518,7 +519,8 @@ surface_state_handle_buffer_destroy(struct wl_listener *listener, void *data)
 }
 
 static void
-weston_surface_state_init(struct weston_surface_state *state)
+weston_surface_state_init(struct weston_surface_state *state,
+			  struct weston_compositor *compositor)
 {
 	state->newly_attached = 0;
 	state->buffer = NULL;
@@ -539,6 +541,8 @@ weston_surface_state_init(struct weston_surface_state *state)
 	state->buffer_viewport.buffer.src_width = wl_fixed_from_int(-1);
 	state->buffer_viewport.surface.width = -1;
 	state->buffer_viewport.changed = 0;
+
+	state->colorspace = &compositor->srgb_colorspace;
 }
 
 static void
@@ -576,6 +580,163 @@ weston_surface_state_set_buffer(struct weston_surface_state *state,
 			      &state->buffer_destroy_listener);
 }
 
+#if HAVE_LCMS
+static void
+weston_free_clut(struct weston_clut *clut)
+{
+	clut->points = 0;
+	free(clut->data);
+	clut->data = NULL;
+}
+
+static void
+weston_build_clut(struct weston_colorspace *colorspace)
+{
+	cmsHTRANSFORM xform;
+	unsigned r, g, b;
+	cmsHPROFILE input_profile, output_profile;
+	cmsUInt8Number input_id[16];
+	cmsUInt8Number output_id[16];
+	struct weston_clut *clut = &colorspace->clut;
+	struct weston_compositor *ec = colorspace->compositor;
+
+	weston_free_clut(clut);
+
+	if (colorspace->input) {
+		input_profile = colorspace->lcms_handle;
+		output_profile = ec->blending_colorspace.lcms_handle;
+	} else {
+		input_profile = ec->blending_colorspace.lcms_handle;
+		output_profile = colorspace->lcms_handle;
+	}
+
+	cmsGetHeaderProfileID(input_profile, input_id);
+	cmsGetHeaderProfileID(output_profile, output_id);
+	if (memcmp(input_id, output_id, 16) == 0)
+		return;
+
+	clut->points = 64;
+
+	xform = cmsCreateTransform(input_profile, TYPE_RGB_8, output_profile,
+				   TYPE_RGB_8, INTENT_PERCEPTUAL, 0);
+
+	clut->data = malloc(clut->points * clut->points * clut->points *
+			    3 * sizeof(char));
+
+	for (b = 0; b < clut->points; b++)
+		for (g = 0; g < clut->points; g++)
+			for (r = 0; r < clut->points; r++) {
+				char in[3];
+				unsigned entry = 3 * (r + clut->points *
+						 (g + clut->points * b));
+				const float step = 255.0 / (clut->points - 1);
+				in[0] = (char) (r * step + 0.5);
+				in[1] = (char) (g * step + 0.5);
+				in[2] = (char) (b * step + 0.5);
+				cmsDoTransform(xform, in,
+					       &clut->data[entry], 1);
+			}
+
+	cmsDeleteTransform(xform);
+}
+
+WL_EXPORT struct weston_colorspace *
+weston_colorspace_from_fd(int fd, int input, struct weston_compositor *ec)
+{
+	FILE *profile;
+	cmsHPROFILE lcms_handle;
+	cmsUInt8Number profile_id[17];
+	struct weston_colorspace *colorspace;
+
+	profile = fdopen(fd, "r");
+	if (!profile) {
+		return 0;
+	}
+
+	lcms_handle = cmsOpenProfileFromStream(profile, "r");
+	if (!lcms_handle) {
+		return 0;
+	}
+
+	cmsMD5computeID(lcms_handle);
+	cmsGetHeaderProfileID(lcms_handle, profile_id);
+	profile_id[16] = '\0';
+
+	if (input)
+		colorspace = g_hash_table_lookup(ec->input_colorspaces,
+						 profile_id);
+	else
+		colorspace = g_hash_table_lookup(ec->output_colorspaces,
+						 profile_id);
+	if (colorspace) {
+		colorspace->refcount++;
+
+		cmsCloseProfile(lcms_handle);
+		return colorspace;
+	}
+
+	colorspace = calloc(1, sizeof(struct weston_colorspace));
+	colorspace->refcount = 1;
+	colorspace->destroyable = 1;
+	colorspace->input = input;
+	colorspace->lcms_handle = lcms_handle;
+	colorspace->compositor = ec;
+
+	weston_build_clut(colorspace);
+
+	g_hash_table_insert(ec->input_colorspaces,
+			    g_strdup((const gchar *) profile_id),
+			    colorspace);
+
+	return colorspace;
+}
+
+/* This function is called when a color space is removed from the hash table */
+static void
+weston_colorspace_free(struct weston_colorspace *colorspace)
+{
+	if (!colorspace->destroyable)
+		return;
+
+	cmsCloseProfile(colorspace->lcms_handle);
+
+	weston_free_clut(&colorspace->clut);
+	free(colorspace);
+}
+
+WL_EXPORT void
+weston_colorspace_destroy(struct weston_colorspace *colorspace)
+{
+	cmsUInt8Number profile_id[17];
+
+	if (!colorspace->destroyable)
+		return;
+
+	colorspace->refcount--;
+	if (colorspace->refcount > 0)
+		return;
+
+	cmsGetHeaderProfileID(colorspace->lcms_handle, profile_id);
+	profile_id[16] = '\0';
+
+	if (colorspace->input)
+		g_hash_table_remove(colorspace->compositor->input_colorspaces,
+				    profile_id);
+	else
+		g_hash_table_remove(colorspace->compositor->output_colorspaces,
+				    profile_id);
+
+}
+
+static void
+weston_colorspace_resource_destroy(struct wl_resource *cms_colorspace)
+{
+	struct weston_colorspace *colorspace =
+		wl_resource_get_user_data(cms_colorspace);
+	weston_colorspace_destroy(colorspace);
+}
+#endif
+
 WL_EXPORT struct weston_surface *
 weston_surface_create(struct weston_compositor *compositor)
 {
@@ -597,7 +758,9 @@ weston_surface_create(struct weston_compositor *compositor)
 	surface->buffer_viewport.buffer.src_width = wl_fixed_from_int(-1);
 	surface->buffer_viewport.surface.width = -1;
 
-	weston_surface_state_init(&surface->pending);
+	surface->colorspace = &compositor->srgb_colorspace;
+
+	weston_surface_state_init(&surface->pending, compositor);
 
 	surface->output = NULL;
 
@@ -615,6 +778,7 @@ weston_surface_create(struct weston_compositor *compositor)
 	wl_list_init(&surface->subsurface_list);
 	wl_list_init(&surface->subsurface_list_pending);
 
+
 	return surface;
 }
 
@@ -2369,6 +2533,8 @@ weston_surface_commit_state(struct weston_surface *surface,
 	wl_list_insert_list(&surface->feedback_list,
 			    &state->feedback_list);
 	wl_list_init(&state->feedback_list);
+
+	surface->colorspace = state->colorspace;
 }
 
 static void
@@ -2609,6 +2775,8 @@ weston_subsurface_commit_to_cache(struct weston_subsurface *sub)
 	sub->cached.buffer_viewport.surface =
 		surface->pending.buffer_viewport.surface;
 
+	sub->cached.colorspace = surface->colorspace;
+
 	weston_surface_reset_pending_buffer(surface);
 
 	pixman_region32_copy(&sub->cached.opaque, &surface->pending.opaque);
@@ -3041,7 +3209,7 @@ weston_subsurface_create(uint32_t id, struct weston_surface *surface,
 				       sub, subsurface_resource_destroy);
 	weston_subsurface_link_surface(sub, surface);
 	weston_subsurface_link_parent(sub, parent);
-	weston_surface_state_init(&sub->cached);
+	weston_surface_state_init(&sub->cached, surface->compositor);
 	sub->cached_buffer_ref.buffer = NULL;
 	sub->synchronized = 1;
 
@@ -3363,6 +3531,10 @@ weston_output_destroy(struct weston_output *output)
 	wl_signal_emit(&output->compositor->output_destroyed_signal, output);
 	wl_signal_emit(&output->destroy_signal, output);
 
+#ifdef HAVE_LCMS
+	weston_colorspace_destroy(output->colorspace);
+#endif
+
 	free(output->name);
 	pixman_region32_fini(&output->region);
 	pixman_region32_fini(&output->previous_damage);
@@ -3554,6 +3726,7 @@ weston_output_init(struct weston_output *output, struct weston_compositor *c,
 	output->mm_height = mm_height;
 	output->dirty = 1;
 	output->original_scale = scale;
+	output->colorspace = &c->srgb_colorspace;
 
 	weston_output_transform_scale_init(output, transform, scale);
 	weston_output_init_zoom(output);
@@ -3917,6 +4090,181 @@ bind_presentation(struct wl_client *client,
 	presentation_send_clock_id(resource, compositor->presentation_clock);
 }
 
+#ifdef HAVE_LCMS
+static void
+cms_colorspace_destroy(struct wl_client *client,
+		       struct wl_resource *cms_colorspace)
+{
+	wl_resource_destroy(cms_colorspace);
+}
+
+static void
+cms_colorspace_get_profile_fd(struct wl_client *client,
+			      struct wl_resource *cms_colorspace)
+{
+	struct weston_colorspace *colorspace =
+		wl_resource_get_user_data(cms_colorspace);
+	char template[24];
+	FILE *profilefile;
+	int fd = -1;
+
+	strncpy(template, "/tmp/weston-cms-XXXXXX", 24);
+	fd = mkstemp(template);
+	profilefile = fdopen(fd, "w");
+	cmsSaveProfileToStream(colorspace->lcms_handle, profilefile);
+	rewind(profilefile);
+
+	wl_cms_colorspace_send_profile_data(cms_colorspace, fd);
+
+	close(fd);
+}
+
+static const struct wl_cms_colorspace_interface cms_colorspace_interface = {
+	cms_colorspace_destroy,
+	cms_colorspace_get_profile_fd
+};
+
+static void
+cms_set_colorspace(struct wl_client *client, struct wl_resource *cms,
+		   struct wl_resource *surface_resource,
+		   struct wl_resource *colorspace_resource)
+{
+	struct weston_surface *surface =
+		wl_resource_get_user_data(surface_resource);
+
+	struct weston_colorspace *colorspace =
+		wl_resource_get_user_data(colorspace_resource);
+	surface->pending.colorspace = colorspace;
+}
+
+static void
+cms_colorspace_from_fd(struct wl_client *client, struct wl_resource *cms,
+		       int fd, uint32_t id)
+{
+	struct weston_compositor *compositor =
+		wl_resource_get_user_data(cms);
+
+	struct weston_colorspace *colorspace;
+	struct wl_resource *colorspace_resource;
+
+	colorspace = weston_colorspace_from_fd(fd, 1, compositor);
+	if (colorspace == NULL) {
+		wl_resource_post_error(cms,
+				       WL_CMS_ERROR_INVALID_PROFILE,
+				       "the passed ICC profile is not valid");
+		return;
+	}
+
+	colorspace_resource = wl_resource_create(client,
+						 &wl_cms_colorspace_interface,
+						 1, id);
+	if (colorspace_resource == NULL) {
+		weston_colorspace_destroy(colorspace);
+		wl_client_post_no_memory(client);
+		return;
+	}
+	wl_resource_set_implementation(colorspace_resource,
+				       &cms_colorspace_interface, colorspace,
+				       weston_colorspace_resource_destroy);
+}
+
+static void
+cms_output_colorspace(struct wl_client *client, struct wl_resource *cms,
+		      struct wl_resource *output_resource, uint32_t id)
+{
+	struct weston_output *output =
+		wl_resource_get_user_data(output_resource);
+
+	struct weston_colorspace *colorspace;
+	struct wl_resource *colorspace_resource;
+
+	colorspace = output->colorspace;
+	colorspace->refcount++;
+
+	colorspace_resource = wl_resource_create(client,
+						 &wl_cms_colorspace_interface,
+						 1, id);
+	if (colorspace_resource == NULL) {
+		weston_colorspace_destroy(colorspace);
+		wl_client_post_no_memory(client);
+		return;
+	}
+	wl_resource_set_implementation(colorspace_resource,
+				       &cms_colorspace_interface, colorspace,
+				       weston_colorspace_resource_destroy);
+}
+
+static void
+cms_srgb_colorspace(struct wl_client *client, struct wl_resource *cms,
+		    uint32_t id)
+{
+	struct weston_compositor *compositor =
+		wl_resource_get_user_data(cms);
+	struct weston_colorspace *colorspace;
+	struct wl_resource *colorspace_resource;
+
+	colorspace = &compositor->srgb_colorspace;
+
+	colorspace_resource = wl_resource_create(client,
+						 &wl_cms_colorspace_interface,
+						 1, id);
+	if (colorspace_resource == NULL) {
+		weston_colorspace_destroy(colorspace);
+		wl_client_post_no_memory(client);
+		return;
+	}
+	wl_resource_set_implementation(colorspace_resource,
+				       &cms_colorspace_interface, colorspace,
+				       weston_colorspace_resource_destroy);
+}
+
+static void
+cms_blending_colorspace(struct wl_client *client, struct wl_resource *cms,
+			uint32_t id)
+{
+	struct weston_compositor *compositor =
+		wl_resource_get_user_data(cms);
+	struct weston_colorspace *colorspace;
+	struct wl_resource *colorspace_resource;
+
+	colorspace = &compositor->blending_colorspace;
+
+	colorspace_resource = wl_resource_create(client,
+						 &wl_cms_colorspace_interface,
+						 1, id);
+	if (colorspace_resource == NULL) {
+		weston_colorspace_destroy(colorspace);
+		wl_client_post_no_memory(client);
+		return;
+	}
+	wl_resource_set_implementation(colorspace_resource,
+				       &cms_colorspace_interface, colorspace,
+				       weston_colorspace_resource_destroy);
+}
+
+static const struct wl_cms_interface cms_interface = {
+	cms_set_colorspace,
+	cms_colorspace_from_fd,
+	cms_output_colorspace,
+	cms_srgb_colorspace,
+	cms_blending_colorspace
+};
+
+static void
+bind_cms(struct wl_client *client, void *data, uint32_t version, uint32_t id)
+{
+	struct wl_resource *resource;
+
+	resource = wl_resource_create(client, &wl_cms_interface, 1, id);
+	if (resource == NULL) {
+		wl_client_post_no_memory(client);
+		return;
+	}
+
+	wl_resource_set_implementation(resource, &cms_interface, data, NULL);
+}
+#endif
+
 static void
 compositor_bind(struct wl_client *client,
 		void *data, uint32_t version, uint32_t id)
@@ -4016,6 +4364,12 @@ weston_compositor_init(struct weston_compositor *ec,
 			      ec, bind_presentation))
 		return -1;
 
+#ifdef HAVE_LCMS
+	if (!wl_global_create(display, &wl_cms_interface, 1,
+			      ec, bind_cms))
+		return -1;
+#endif
+
 	wl_list_init(&ec->view_list);
 	wl_list_init(&ec->plane_list);
 	wl_list_init(&ec->layer_list);
@@ -4600,6 +4954,10 @@ int main(int argc, char *argv[])
 	struct wl_client *primary_client;
 	struct wl_listener primary_client_destroyed;
 	struct weston_seat *seat;
+#ifdef HAVE_LCMS
+	cmsUInt8Number srgb_id[17];
+	cmsUInt8Number blending_id[17];
+#endif
 
 	const struct weston_option core_options[] = {
 		{ WESTON_OPTION_STRING, "backend", 'B', &backend },
@@ -4690,6 +5048,45 @@ int main(int argc, char *argv[])
 	ec->idle_time = idle_time;
 	ec->default_pointer_grab = NULL;
 
+#ifdef HAVE_LCMS
+	ec->input_colorspaces =
+		g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+				      (GDestroyNotify) weston_colorspace_free);
+	ec->output_colorspaces =
+		g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+				      (GDestroyNotify) weston_colorspace_free);
+
+	ec->srgb_colorspace.destroyable = 0;
+	ec->srgb_colorspace.input = 1;
+	ec->srgb_colorspace.compositor = ec;
+	ec->blending_colorspace.destroyable = 0;
+	ec->blending_colorspace.input = 1;
+	ec->blending_colorspace.compositor = ec;
+
+	ec->srgb_colorspace.lcms_handle = cmsCreate_sRGBProfile();
+	/* TODO: We should really use a linear blending space. But this would
+	 * cause additional banding since we only use an 8 bit LUT for now. */
+	ec->blending_colorspace.lcms_handle = cmsCreate_sRGBProfile();
+
+	weston_build_clut(&ec->srgb_colorspace);
+	/* This is unnecessary but will initialize everything with 0 */
+	weston_build_clut(&ec->blending_colorspace);
+
+	cmsMD5computeID(ec->srgb_colorspace.lcms_handle);
+	cmsMD5computeID(ec->blending_colorspace.lcms_handle);
+	cmsGetHeaderProfileID(ec->srgb_colorspace.lcms_handle, srgb_id);
+	cmsGetHeaderProfileID(ec->blending_colorspace.lcms_handle, blending_id);
+	srgb_id[16] = '\0';
+	blending_id[16] = '\0';
+
+	g_hash_table_insert(ec->input_colorspaces,
+			    g_strdup((const gchar *) srgb_id),
+			    &ec->srgb_colorspace);
+	g_hash_table_insert(ec->input_colorspaces,
+			    g_strdup((const gchar *) blending_id),
+			    &ec->srgb_colorspace);
+#endif
+
 	for (i = 1; i < argc; i++)
 		weston_log("fatal: unhandled option: %s\n", argv[i]);
 	if (argc > 1) {
@@ -4760,6 +5157,14 @@ out:
 
 	wl_signal_emit(&ec->destroy_signal, ec);
 
+#ifdef HAVE_LCMS
+	g_hash_table_unref(ec->input_colorspaces);
+	g_hash_table_unref(ec->output_colorspaces);
+
+	weston_free_clut(&ec->srgb_colorspace.clut);
+	/* The blending colorspace cannot have a clut */
+#endif
+
 	weston_compositor_xkb_destroy(ec);
 
 	ec->destroy(ec);
diff --git a/src/compositor.h b/src/compositor.h
index 44379fe..5f198a9 100644
--- a/src/compositor.h
+++ b/src/compositor.h
@@ -31,6 +31,7 @@ extern "C" {
 #include <time.h>
 #include <pixman.h>
 #include <xkbcommon/xkbcommon.h>
+#include <glib.h>
 
 #define WL_HIDE_DEPRECATED
 #include <wayland-server.h>
@@ -40,6 +41,10 @@ extern "C" {
 #include "config-parser.h"
 #include "zalloc.h"
 
+#ifdef HAVE_LCMS
+#include <lcms2.h>
+#endif
+
 #ifndef MIN
 #define MIN(x,y) (((x) < (y)) ? (x) : (y))
 #endif
@@ -179,6 +184,24 @@ enum weston_mode_switch_op {
 	WESTON_MODE_SWITCH_RESTORE_NATIVE
 };
 
+struct weston_clut {
+	unsigned points;
+	char *data;
+};
+
+struct weston_colorspace {
+#ifdef HAVE_LCMS
+	cmsHPROFILE lcms_handle;
+#endif
+	struct weston_clut clut;
+
+	int destroyable;
+	int refcount;
+	int input;
+
+	struct weston_compositor *compositor;
+};
+
 struct weston_output {
 	uint32_t id;
 	char *name;
@@ -220,6 +243,8 @@ struct weston_output {
 	struct weston_mode *original_mode;
 	struct wl_list mode_list;
 
+	struct weston_colorspace *colorspace;
+
 	void (*start_repaint_loop)(struct weston_output *output);
 	int (*repaint)(struct weston_output *output,
 			pixman_region32_t *damage);
@@ -668,6 +693,11 @@ struct weston_compositor {
 	int32_t kb_repeat_delay;
 
 	clockid_t presentation_clock;
+
+	struct weston_colorspace srgb_colorspace;
+	struct weston_colorspace blending_colorspace;
+	GHashTable *input_colorspaces;
+	GHashTable *output_colorspaces;
 };
 
 struct weston_buffer {
@@ -844,6 +874,9 @@ struct weston_surface_state {
 	/* wl_surface.set_scaling_factor */
 	/* wl_viewport.set */
 	struct weston_buffer_viewport buffer_viewport;
+
+	/* wl_cms.set_colorspace*/
+	struct weston_colorspace *colorspace;
 };
 
 struct weston_surface {
@@ -887,6 +920,8 @@ struct weston_surface {
 	int32_t height_from_buffer;
 	int keep_buffer; /* bool for backends to prevent early release */
 
+	struct weston_colorspace *colorspace;
+
 	/* wl_viewport resource for this surface */
 	struct wl_resource *viewport_resource;
 
@@ -1410,6 +1445,12 @@ struct weston_view_animation *
 weston_slide_run(struct weston_view *view, float start, float stop,
 		 weston_view_animation_done_func_t done, void *data);
 
+struct weston_colorspace *
+weston_colorspace_from_fd(int fd, int input, struct weston_compositor *ec);
+
+void
+weston_colorspace_destroy(struct weston_colorspace *colorspace);
+
 void
 weston_surface_set_color(struct weston_surface *surface,
 			 float red, float green, float blue, float alpha);
-- 
2.1.2



More information about the wayland-devel mailing list