ICC profiles can now be specified in weston.ini for each output, or a CMS implementation can optionally loaded from a pluggable module. --- configure.ac | 7 +++ src/Makefile.am | 6 ++- src/cms.c | 106 +++++++++++++++++++++++++++++++++++++ src/cms.h | 84 +++++++++++++++++++++++++++++ src/compositor-drm.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/compositor.c | 7 +++ src/compositor.h | 21 ++++++++ weston.ini | 2 + 8 files changed, 376 insertions(+), 4 deletions(-) create mode 100644 src/cms.c create mode 100644 src/cms.h
diff --git a/configure.ac b/configure.ac index d5fea9d..1552d73 100644 --- a/configure.ac +++ b/configure.ac @@ -315,6 +315,13 @@ AC_MSG_NOTICE([Weston's native backend: $WESTON_NATIVE_BACKEND]) AC_DEFINE_UNQUOTED([WESTON_NATIVE_BACKEND], ["$WESTON_NATIVE_BACKEND"], [The default backend to load, if not wayland nor x11.]) +PKG_CHECK_MODULES(LCMS, lcms2, + [have_lcms=yes], [have_lcms=no]) +if test "x$have_lcms" = xyes; then + AC_DEFINE(HAVE_LCMS, 1, [Have lcms support]) +fi +AM_CONDITIONAL(HAVE_LCMS, [test "x$have_lcms" = xyes]) + WAYLAND_SCANNER_RULES(['$(top_srcdir)/protocol']) AC_CONFIG_FILES([Makefile diff --git a/src/Makefile.am b/src/Makefile.am index d33ebc5..7489086 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,8 +9,8 @@ AM_CPPFLAGS = \ -DIN_WESTON weston_LDFLAGS = -export-dynamic -weston_CFLAGS = $(GCC_CFLAGS) $(COMPOSITOR_CFLAGS) $(LIBUNWIND_CFLAGS) -weston_LDADD = $(COMPOSITOR_LIBS) $(LIBUNWIND_LIBS) \ +weston_CFLAGS = $(GCC_CFLAGS) $(COMPOSITOR_CFLAGS) $(LIBUNWIND_CFLAGS) $(LCMS_CFLAGS) +weston_LDADD = $(COMPOSITOR_LIBS) $(LIBUNWIND_LIBS) $(LCMS_LIBS) \ $(DLOPEN_LIBS) -lm ../shared/libshared.la weston_SOURCES = \ @@ -20,6 +20,8 @@ weston_SOURCES = \ compositor.h \ filter.c \ filter.h \ + cms.c \ + cms.h \ screenshooter.c \ screenshooter-protocol.c \ screenshooter-server-protocol.h \ diff --git a/src/cms.c b/src/cms.c new file mode 100644 index 0000000..1acc680 --- /dev/null +++ b/src/cms.c @@ -0,0 +1,106 @@ +/* + * Copyright © 2013 Richard Hughes + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#ifdef HAVE_LCMS +#include <lcms2.h> +#endif + +#include "compositor.h" +#include "cms.h" + +WL_EXPORT void +weston_cms_set_color_profile(struct weston_output *o, + struct weston_color_profile *p) +{ + if (o->color_profile == p) + return; + if (o->color_profile) + weston_cms_destroy_profile(o->color_profile); + o->color_profile = p; + if (o->updated_color_profile) + o->updated_color_profile(o); +} + +WL_EXPORT int +weston_cms_output_added(struct weston_output *o, + enum weston_color_manager_flags flags) +{ + struct weston_compositor *ec = o->compositor; + if (!ec->cms) + return 0; + if (!ec->cms->output_added) + return 0; + return ec->cms->output_added(o, flags); +} + +WL_EXPORT int +weston_cms_output_removed(struct weston_output *o) +{ + struct weston_compositor *ec = o->compositor; + if (!ec->cms) + return 0; + if (!ec->cms->output_removed) + return 0; + return ec->cms->output_removed(o); +} + +WL_EXPORT void +weston_cms_destroy_profile(struct weston_color_profile *p) +{ +#ifdef HAVE_LCMS + cmsCloseProfile(p->lcms_handle); +#endif + free(p->filename); + free(p); +} + +WL_EXPORT struct weston_color_profile * +weston_cms_create_profile(const char *filename, + void *lcms_profile) +{ + struct weston_color_profile *p; + p = calloc(sizeof(struct weston_color_profile), 1); + p->filename = strdup(filename); + p->lcms_handle = lcms_profile; + return p; +} + +WL_EXPORT struct weston_color_profile * +weston_cms_load_profile(const char *filename) +{ + struct weston_color_profile *p = NULL; +#ifdef HAVE_LCMS + cmsHPROFILE lcms_profile; + lcms_profile = cmsOpenProfileFromFile(filename, "r"); + if (lcms_profile) + p = weston_cms_create_profile(filename, lcms_profile); +#endif + return p; +} diff --git a/src/cms.h b/src/cms.h new file mode 100644 index 0000000..d99587f --- /dev/null +++ b/src/cms.h @@ -0,0 +1,84 @@ +/* + * Copyright © 2013 Richard Hughes + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _WESTON_CMS_H_ +#define _WESTON_CMS_H_ + +#include "compositor.h" + +/* General overview on how to be a CMS plugin: + * + * First, some nomenclature: + * + * CMF: Color management framework, i.e. "Use foo.icc for device $bar" + * CMM: Color management module that converts pixel colors, which is + * usually lcms2 on any modern OS. + * CMS: Color management system that encompasses both a CMF and CMM. + * ICC: International Color Consortium, the people that define the + * binary encoding of a .icc file. + * VCGT: Video Card Gamma Tag. An Apple extension to the ICC specification + * that allows the calibration state to be stored in the ICC profile + * Output: Physical port with a display attached, e.g. LVDS1 + * + * As a CMF is probably something you don't want or need on an embeded install + * these functions will not be called if the icc_profile key is set for a + * specific [output] section in weston.ini + * + * Most desktop environments want the CMF to decide what profile to use in + * different situations, so that displays can be profiled and also so that + * the ICC profiles can be changed at runtime depending on the task or ambient + * environment. + * + * The CMF can be selected using the 'cms' key in the [core] section. + * Specifying multiple CMFs will not work. + * + * The general idea is the compositor creates an output which is registered + * with the CMS using weston_cms_output_added(). This is expected to call + * the ->updated_color_profile() vfunc on the output with the prepared object + * of type weston_color_profile, or NULL if there is no color profile to apply. + * + * If the CMF can detect that the assigned ICC profile has changed for a + * specific output, then it can call the ->updated_color_profile() vfunc at any + * time until weston_cms_output_removed() is called. + * + * The updated_color_profile() vfunc is expected to set the output calibration + * state, typically by uploading the VCGT data to the graphics card using + * drmModeCrtcSetGamma() + */ + +void +weston_cms_set_color_profile(struct weston_output *o, + struct weston_color_profile *p); +int +weston_cms_output_added(struct weston_output *o, + enum weston_color_manager_flags flags); +int +weston_cms_output_removed(struct weston_output *o); +struct weston_color_profile * +weston_cms_create_profile(const char *filename, + void *lcms_profile); +struct weston_color_profile * +weston_cms_load_profile(const char *filename); +void +weston_cms_destroy_profile(struct weston_color_profile *p); + +#endif diff --git a/src/compositor-drm.c b/src/compositor-drm.c index c8016cd..15f99c3 100644 --- a/src/compositor-drm.c +++ b/src/compositor-drm.c @@ -21,6 +21,10 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + #define _GNU_SOURCE #include <errno.h> @@ -33,6 +37,10 @@ #include <assert.h> #include <sys/mman.h> +#ifdef HAVE_LCMS +#include <lcms2.h> +#endif + #include <xf86drm.h> #include <xf86drmMode.h> #include <drm_fourcc.h> @@ -46,11 +54,13 @@ #include "pixman-renderer.h" #include "udev-seat.h" #include "launcher-util.h" +#include "cms.h" static int option_current_mode = 0; static char *output_name; static char *output_mode; static char *output_transform; +static char *output_icc_profile; static struct wl_list configured_output_list; enum output_config { @@ -70,6 +80,7 @@ struct drm_configured_output { drmModeModeInfo crtc_mode; enum output_config config; struct wl_list link; + struct weston_color_profile *color_profile; }; struct drm_compositor { @@ -545,6 +556,95 @@ drm_output_render(struct drm_output *output, pixman_region32_t *damage) } static void +drm_output_gamma_reset(struct drm_compositor *compositor, + struct drm_output *output) +{ + int i; + int ret; + int size; + uint16_t *blue = NULL; + uint16_t *green = NULL; + uint16_t *red = NULL; + uint32_t tmp; + + if (!output->original_crtc) + return; + + size = output->original_crtc->gamma_size; + red = calloc(sizeof(uint16_t), size); + green = calloc(sizeof(uint16_t), size); + blue = calloc(sizeof(uint16_t), size); + for (i = 0; i < size; i++) { + tmp = (uint32_t) 0xffff * (uint32_t) i / (uint32_t) (size - 1); + red[i] = green[i] = blue[i] = tmp; + } + ret = drmModeCrtcSetGamma(compositor->drm.fd, + output->crtc_id, + size, + red, green, blue); + if (ret) + weston_log("reset gamma failed: %m\n"); + + free(red); + free(green); + free(blue); +} + +static void +drm_output_updated_color_profile(struct weston_output *output_base) +{ +#ifdef HAVE_LCMS + struct drm_output *output = (struct drm_output *) output_base; + struct drm_compositor *compositor = (struct drm_compositor *) output->base.compositor; + cmsFloat32Number in; + const cmsToneCurve **vcgt; + int i; + int rc; + int size; + uint16_t *red = NULL; + uint16_t *green = NULL; + uint16_t *blue = NULL; + + /* anything to apply */ + if (!output->base.color_profile) { + drm_output_gamma_reset (compositor, output); + return; + } + if (!output->original_crtc) + return; + weston_log("Using ICC profile %s\n", + output->base.color_profile->filename); + + vcgt = cmsReadTag (output->base.color_profile->lcms_handle, cmsSigVcgtTag); + if (vcgt == NULL || vcgt[0] == NULL) { + drm_output_gamma_reset(compositor, output); + return; + } + + size = output->original_crtc->gamma_size; + red = calloc(sizeof(uint16_t), size); + green = calloc(sizeof(uint16_t), size); + blue = calloc(sizeof(uint16_t), size); + for (i = 0; i < size; i++) { + in = (cmsFloat32Number) i / (cmsFloat32Number) (size - 1); + red[i] = cmsEvalToneCurveFloat(vcgt[0], in) * (double) 0xffff; + green[i] = cmsEvalToneCurveFloat(vcgt[1], in) * (double) 0xffff; + blue[i] = cmsEvalToneCurveFloat(vcgt[2], in) * (double) 0xffff; + } + rc = drmModeCrtcSetGamma(compositor->drm.fd, + output->crtc_id, + size, + red, green, blue); + if (rc) + weston_log("set gamma failed: %m\n"); + + free(red); + free(green); + free(blue); +#endif +} + +static void drm_output_repaint(struct weston_output *output_base, pixman_region32_t *damage) { @@ -570,6 +670,7 @@ drm_output_repaint(struct weston_output *output_base, weston_log("set mode failed: %m\n"); return; } + drm_output_updated_color_profile(output_base); } if (drmModePageFlip(compositor->drm.fd, output->crtc_id, @@ -1026,7 +1127,11 @@ drm_output_destroy(struct weston_output *output_base) /* Turn off hardware cursor */ drmModeSetCursor(c->drm.fd, output->crtc_id, 0, 0, 0); + /* Remove CMS device */ + weston_cms_output_removed(&output->base); + /* Restore original CRTC state */ + drm_output_gamma_reset (c, output); drmModeSetCrtc(c->drm.fd, origcrtc->crtc_id, origcrtc->buffer_id, origcrtc->x, origcrtc->y, &output->connector_id, 1, &origcrtc->mode); @@ -1648,6 +1753,7 @@ create_output_for_connector(struct drm_compositor *ec, int i; char name[32]; const char *type_name; + enum weston_color_manager_flags cm_flags = 0; i = find_crtc_for_connector(ec, resources, connector); if (i < 0) { @@ -1787,6 +1893,18 @@ create_output_for_connector(struct drm_compositor *ec, output->base.backlight_current = drm_get_backlight(output); } + /* get the ICC profile for the device from the CMS, *unless* there is + * a hardcoded profile specified in [output] */ + if (o && o->color_profile) { + weston_log("Using hardcoded ICC profile %s\n", + o->color_profile->filename); + weston_cms_set_color_profile(&output->base, o->color_profile); + } else { + if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) + cm_flags &= WESTON_COLOR_MANAGER_FLAG_OUTPUT_INTERNAL; + weston_cms_output_added(&output->base, cm_flags); + } + wl_list_insert(ec->base.output_list.prev, &output->base.link); find_and_parse_output_edid(ec, output, connector); @@ -1798,6 +1916,7 @@ create_output_for_connector(struct drm_compositor *ec, output->base.assign_planes = drm_assign_planes; output->base.set_dpms = drm_set_dpms; output->base.switch_mode = drm_output_switch_mode; + output->base.updated_color_profile = drm_output_updated_color_profile; weston_plane_init(&output->cursor_plane, 0, 0); weston_plane_init(&output->fb_plane, 0, 0); @@ -2098,6 +2217,7 @@ drm_restore(struct weston_compositor *ec) static void drm_free_configured_output(struct drm_configured_output *output) { + weston_cms_destroy_profile(output->color_profile); free(output->name); free(output->mode); free(output); @@ -2565,21 +2685,39 @@ drm_output_set_transform(struct drm_configured_output *output) } static void +drm_output_set_icc_profile(struct drm_configured_output *output) +{ + if (!output_icc_profile) { + output->color_profile = NULL; + return; + } + output->color_profile = weston_cms_load_profile(output_icc_profile); + if (output->color_profile) { + weston_log("Using hardcoded profile \"%s\" for output %s\n", + output_icc_profile, output_name); + } else { + weston_log("Failed to load profile \"%s\" for output %s\n", + output_icc_profile, output_name); + } +} + +static void output_section_done(void *data) { struct drm_configured_output *output; output = malloc(sizeof *output); - if (!output || !output_name || (output_name[0] == 'X') || - (!output_mode && !output_transform)) { + if (!output || !output_name) { free(output_name); free(output_mode); free(output_transform); + free(output_icc_profile); free(output); output_name = NULL; output_mode = NULL; output_transform = NULL; + output_icc_profile = NULL; return; } @@ -2607,12 +2745,16 @@ output_section_done(void *data) } drm_output_set_transform(output); + drm_output_set_icc_profile(output); wl_list_insert(&configured_output_list, &output->link); if (output_transform) free(output_transform); output_transform = NULL; + if (output_icc_profile) + free(output_icc_profile); + output_icc_profile = NULL; } WL_EXPORT struct weston_compositor * @@ -2638,6 +2780,7 @@ backend_init(struct wl_display *display, int *argc, char *argv[], { "name", CONFIG_KEY_STRING, &output_name }, { "mode", CONFIG_KEY_STRING, &output_mode }, { "transform", CONFIG_KEY_STRING, &output_transform }, + { "icc_profile", CONFIG_KEY_STRING, &output_icc_profile }, }; const struct config_section config_section[] = { diff --git a/src/compositor.c b/src/compositor.c index 693df2c..5047471 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -56,6 +56,7 @@ #include <wayland-server.h> #include "compositor.h" +#include "cms.h" #include "../shared/os-compatibility.h" #include "git-version.h" #include "version.h" @@ -2906,6 +2907,8 @@ weston_output_destroy(struct weston_output *output) { struct weston_compositor *c = output->compositor; + weston_cms_destroy_profile(output->color_profile); + pixman_region32_fini(&output->region); pixman_region32_fini(&output->previous_damage); output->compositor->output_id_pool &= ~(1 << output->id); @@ -3526,6 +3529,7 @@ int main(int argc, char *argv[]) int i; char *backend = NULL; const char *modules = "desktop-shell.so", *option_modules = NULL; + const char *cms_modules = NULL; char *log = NULL; int32_t idle_time = 300; int32_t help = 0; @@ -3535,6 +3539,7 @@ int main(int argc, char *argv[]) const struct config_key core_config_keys[] = { { "modules", CONFIG_KEY_STRING, &modules }, + { "cms", CONFIG_KEY_STRING, &cms_modules }, }; const struct config_section cs[] = { @@ -3617,6 +3622,8 @@ int main(int argc, char *argv[]) setenv("WAYLAND_DISPLAY", socket_name, 1); + if (load_modules(ec, cms_modules, &argc, argv, config_file) < 0) + goto out; if (load_modules(ec, modules, &argc, argv, config_file) < 0) goto out; if (load_modules(ec, option_modules, &argc, argv, config_file) < 0) diff --git a/src/compositor.h b/src/compositor.h index eb8ad82..81d7035 100644 --- a/src/compositor.h +++ b/src/compositor.h @@ -154,6 +154,11 @@ enum dpms_enum { WESTON_DPMS_OFF }; +struct weston_color_profile { + char *filename; + void *lcms_handle; +}; + struct weston_output { uint32_t id; @@ -197,6 +202,9 @@ struct weston_output { uint32_t backlight_current; void (*set_backlight)(struct weston_output *output, uint32_t value); void (*set_dpms)(struct weston_output *output, enum dpms_enum level); + + struct weston_color_profile *color_profile; + void (*updated_color_profile)(struct weston_output *output); }; struct weston_xkb_info { @@ -297,6 +305,17 @@ struct weston_renderer { void (*destroy)(struct weston_compositor *ec); }; +enum weston_color_manager_flags { + WESTON_COLOR_MANAGER_FLAG_OUTPUT_INTERNAL = 1, +}; + +struct weston_color_manager { + void (*destroy) (struct weston_compositor *c); + int (*output_added) (struct weston_output *o, + enum weston_color_manager_flags flags); + int (*output_removed) (struct weston_output *o); +}; + struct weston_compositor { struct wl_signal destroy_signal; @@ -360,6 +379,8 @@ struct weston_compositor { struct xkb_rule_names xkb_names; struct xkb_context *xkb_context; struct weston_xkb_info xkb_info; + + struct weston_color_manager *cms; }; struct weston_buffer_reference { diff --git a/weston.ini b/weston.ini index c6cff76..639a0e8 100644 --- a/weston.ini +++ b/weston.ini @@ -1,5 +1,6 @@ [core] #modules=desktop-shell.so,xwayland.so +#cms=colord.so [shell] background-image=/usr/share/backgrounds/gnome/Aqua.jpg @@ -45,6 +46,7 @@ path=/usr/libexec/weston-keyboard #name=LVDS1 #mode=1680x1050 #transform=90 +#icc_profile=/usr/share/color/icc/colord/Bluish.icc #[output] #name=VGA1 -- 1.8.2 _______________________________________________ wayland-devel mailing list wayland-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/wayland-devel