diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index cff1a3d9..9fdc39d1 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -79,6 +79,7 @@ struct linux_dmabuf_buffer; struct weston_recorder; struct weston_pointer_constraint; struct ro_anonymous_file; +struct weston_color_profile; struct weston_color_transform; enum weston_keyboard_modifier { @@ -2126,6 +2127,15 @@ void weston_timeline_refresh_subscription_objects(struct weston_compositor *wc, void *object); +struct weston_color_profile * +weston_color_profile_ref(struct weston_color_profile *cprof); + +void +weston_color_profile_unref(struct weston_color_profile *cprof); + +const char * +weston_color_profile_get_description(struct weston_color_profile *cprof); + #ifdef __cplusplus } #endif diff --git a/libweston/color-lcms/color-lcms.c b/libweston/color-lcms/color-lcms.c index e7cff947..16e99812 100644 --- a/libweston/color-lcms/color-lcms.c +++ b/libweston/color-lcms/color-lcms.c @@ -159,6 +159,7 @@ cmlcms_destroy(struct weston_color_manager *cm_base) struct weston_color_manager_lcms *cm = get_cmlcms(cm_base); assert(wl_list_empty(&cm->color_transform_list)); + assert(wl_list_empty(&cm->color_profile_list)); cmsDeleteContext(cm->lcms_ctx); free(cm); @@ -178,6 +179,8 @@ weston_color_manager_create(struct weston_compositor *compositor) cm->base.supports_client_protocol = true; cm->base.init = cmlcms_init; cm->base.destroy = cmlcms_destroy; + cm->base.destroy_color_profile = cmlcms_destroy_color_profile; + cm->base.get_color_profile_from_icc = cmlcms_get_color_profile_from_icc; cm->base.destroy_color_transform = cmlcms_destroy_color_transform; cm->base.get_surface_color_transform = cmlcms_get_surface_color_transform; @@ -188,6 +191,7 @@ weston_color_manager_create(struct weston_compositor *compositor) cmlcms_get_sRGB_to_blend_color_transform; wl_list_init(&cm->color_transform_list); + wl_list_init(&cm->color_profile_list); return &cm->base; } diff --git a/libweston/color-lcms/color-lcms.h b/libweston/color-lcms/color-lcms.h index 0458d31a..dd5c38fc 100644 --- a/libweston/color-lcms/color-lcms.h +++ b/libweston/color-lcms/color-lcms.h @@ -38,6 +38,7 @@ struct weston_color_manager_lcms { cmsContext lcms_ctx; struct wl_list color_transform_list; /* cmlcms_color_transform::link */ + struct wl_list color_profile_list; /* cmlcms_color_profile::link */ }; static inline struct weston_color_manager_lcms * @@ -46,6 +47,37 @@ get_cmlcms(struct weston_color_manager *cm_base) return container_of(cm_base, struct weston_color_manager_lcms, base); } +struct cmlcms_md5_sum { + uint8_t bytes[16]; +}; + +struct cmlcms_color_profile { + struct weston_color_profile base; + + /* struct weston_color_manager_lcms::color_profile_list */ + struct wl_list link; + + cmsHPROFILE profile; + struct cmlcms_md5_sum md5sum; +}; + +static inline struct cmlcms_color_profile * +get_cprof(struct weston_color_profile *cprof_base) +{ + return container_of(cprof_base, struct cmlcms_color_profile, base); +} + +bool +cmlcms_get_color_profile_from_icc(struct weston_color_manager *cm, + const void *icc_data, + size_t icc_len, + const char *name_part, + struct weston_color_profile **cprof_out, + char **errmsg); + +void +cmlcms_destroy_color_profile(struct weston_color_profile *cprof_base); + /* * Perhaps a placeholder, until we get actual color spaces involved and * see how this would work better. diff --git a/libweston/color-lcms/color-profile.c b/libweston/color-lcms/color-profile.c new file mode 100644 index 00000000..8884a071 --- /dev/null +++ b/libweston/color-lcms/color-profile.c @@ -0,0 +1,202 @@ +/* + * Copyright 2019 Sebastian Wick + * Copyright 2021 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "color.h" +#include "color-lcms.h" +#include "shared/helpers.h" +#include "shared/string-helpers.h" + +/* FIXME: sync with spec! */ +static bool +validate_icc_profile(cmsHPROFILE profile, char **errmsg) +{ + cmsColorSpaceSignature cs = cmsGetColorSpace(profile); + uint32_t nr_channels = cmsChannelsOf(cs); + uint8_t version = cmsGetEncodedICCversion(profile) >> 24; + + if (version != 2 && version != 4) { + str_printf(errmsg, + "ICC profile major version %d is unsupported, should be 2 or 4.", + version); + return false; + } + + if (nr_channels != 3) { + str_printf(errmsg, + "ICC profile must contain 3 channels for the color space, not %u.", + nr_channels); + return false; + } + + if (cmsGetDeviceClass(profile) != cmsSigDisplayClass) { + str_printf(errmsg, "ICC profile is required to be of Display device class, but it is not."); + return false; + } + + return true; +} + +static struct cmlcms_color_profile * +cmlcms_find_color_profile_by_md5(const struct weston_color_manager_lcms *cm, + const struct cmlcms_md5_sum *md5sum) +{ + struct cmlcms_color_profile *cprof; + + wl_list_for_each(cprof, &cm->color_profile_list, link) { + if (memcmp(cprof->md5sum.bytes, + md5sum->bytes, sizeof(md5sum->bytes)) == 0) + return cprof; + } + + return NULL; +} + +static struct cmlcms_color_profile * +cmlcms_color_profile_create(struct weston_color_manager_lcms *cm, + cmsHPROFILE profile, + char *desc, + char **errmsg) +{ + struct cmlcms_color_profile *cprof; + + cprof = zalloc(sizeof *cprof); + if (!cprof) + return NULL; + + weston_color_profile_init(&cprof->base, &cm->base); + cprof->base.description = desc; + cprof->profile = profile; + cmsGetHeaderProfileID(profile, cprof->md5sum.bytes); + wl_list_insert(&cm->color_profile_list, &cprof->link); + + return cprof; +} + +static void +cmlcms_color_profile_destroy(struct cmlcms_color_profile *cprof) +{ + wl_list_remove(&cprof->link); + cmsCloseProfile(cprof->profile); + free(cprof->base.description); + free(cprof); +} + +static char * +make_icc_file_description(cmsHPROFILE profile, + const struct cmlcms_md5_sum *md5sum, + const char *name_part) +{ + char md5sum_str[sizeof(md5sum->bytes) * 2 + 1]; + char *desc; + size_t i; + + for (i = 0; i < sizeof(md5sum->bytes); i++) { + snprintf(md5sum_str + 2 * i, sizeof(md5sum_str) - 2 * i, + "%02x", md5sum->bytes[i]); + } + + str_printf(&desc, "ICCv%f %s %s", cmsGetProfileVersion(profile), + name_part, md5sum_str); + + return desc; +} + +bool +cmlcms_get_color_profile_from_icc(struct weston_color_manager *cm_base, + const void *icc_data, + size_t icc_len, + const char *name_part, + struct weston_color_profile **cprof_out, + char **errmsg) +{ + struct weston_color_manager_lcms *cm = get_cmlcms(cm_base); + cmsHPROFILE profile; + struct cmlcms_md5_sum md5sum; + struct cmlcms_color_profile *cprof; + char *desc = NULL; + + if (!icc_data || icc_len < 1) { + str_printf(errmsg, "No ICC data."); + return false; + } + if (icc_len >= UINT32_MAX) { + str_printf(errmsg, "Too much ICC data."); + return false; + } + + profile = cmsOpenProfileFromMemTHR(cm->lcms_ctx, icc_data, icc_len); + if (!profile) { + str_printf(errmsg, "ICC data not understood."); + return false; + } + + if (!validate_icc_profile(profile, errmsg)) + goto err_close; + + if (!cmsMD5computeID(profile)) { + str_printf(errmsg, "Failed to compute MD5 for ICC profile."); + goto err_close; + } + + cmsGetHeaderProfileID(profile, md5sum.bytes); + cprof = cmlcms_find_color_profile_by_md5(cm, &md5sum); + if (cprof) { + *cprof_out = weston_color_profile_ref(&cprof->base); + cmsCloseProfile(profile); + return true; + } + + desc = make_icc_file_description(profile, &md5sum, name_part); + if (!desc) + goto err_close; + + cprof = cmlcms_color_profile_create(cm, profile, desc, errmsg); + if (!cprof) + goto err_close; + + *cprof_out = &cprof->base; + return true; + +err_close: + free(desc); + cmsCloseProfile(profile); + return false; +} + +void +cmlcms_destroy_color_profile(struct weston_color_profile *cprof_base) +{ + struct cmlcms_color_profile *cprof = get_cprof(cprof_base); + + cmlcms_color_profile_destroy(cprof); +} diff --git a/libweston/color-lcms/meson.build b/libweston/color-lcms/meson.build index 1f11013f..86e2871f 100644 --- a/libweston/color-lcms/meson.build +++ b/libweston/color-lcms/meson.build @@ -9,6 +9,7 @@ endif srcs_color_lcms = [ 'color-lcms.c', + 'color-profile.c', 'color-transform.c', ] diff --git a/libweston/color-noop.c b/libweston/color-noop.c index aa73d42e..35d0fad9 100644 --- a/libweston/color-noop.c +++ b/libweston/color-noop.c @@ -29,6 +29,7 @@ #include "color.h" #include "shared/helpers.h" +#include "shared/string-helpers.h" struct weston_color_manager_noop { struct weston_color_manager base; @@ -40,6 +41,24 @@ get_cmnoop(struct weston_color_manager *cm_base) return container_of(cm_base, struct weston_color_manager_noop, base); } +static void +cmnoop_destroy_color_profile(struct weston_color_profile *cprof) +{ + /* Never called, as never creates an actual color profile. */ +} + +static bool +cmnoop_get_color_profile_from_icc(struct weston_color_manager *cm, + const void *icc_data, + size_t icc_len, + const char *name_part, + struct weston_color_profile **cprof_out, + char **errmsg) +{ + str_printf(errmsg, "ICC profiles are unsupported."); + return false; +} + static void cmnoop_destroy_color_transform(struct weston_color_transform *xform) { @@ -131,6 +150,8 @@ weston_color_manager_noop_create(struct weston_compositor *compositor) cm->base.supports_client_protocol = false; cm->base.init = cmnoop_init; cm->base.destroy = cmnoop_destroy; + cm->base.destroy_color_profile = cmnoop_destroy_color_profile; + cm->base.get_color_profile_from_icc = cmnoop_get_color_profile_from_icc; cm->base.destroy_color_transform = cmnoop_destroy_color_transform; cm->base.get_surface_color_transform = cmnoop_get_surface_color_transform; diff --git a/libweston/color.c b/libweston/color.c index 191b02fe..d06a8558 100644 --- a/libweston/color.c +++ b/libweston/color.c @@ -31,6 +31,77 @@ #include "color.h" #include "libweston-internal.h" +/** + * Increase reference count of the color profile object + * + * \param cprof The color profile. NULL is accepted too. + * \return cprof. + */ +WL_EXPORT struct weston_color_profile * +weston_color_profile_ref(struct weston_color_profile *cprof) +{ + /* NULL is a valid color space: sRGB */ + if (!cprof) + return NULL; + + assert(cprof->ref_count > 0); + cprof->ref_count++; + return cprof; +} + +/** + * Decrease reference count and potentially destroy the color profile object + * + * \param cprof The color profile. NULL is accepted too. + */ +WL_EXPORT void +weston_color_profile_unref(struct weston_color_profile *cprof) +{ + if (!cprof) + return; + + assert(cprof->ref_count > 0); + if (--cprof->ref_count > 0) + return; + + cprof->cm->destroy_color_profile(cprof); +} + +/** + * Get color profile description + * + * A description of the profile is meant for human readable logs. + * + * \param cprof The color profile, NULL is accepted too. + * \returns The color profile description, valid as long as the + * color profile itself is. + */ +WL_EXPORT const char * +weston_color_profile_get_description(struct weston_color_profile *cprof) +{ + if (cprof) + return cprof->description; + else + return "built-in default sRGB SDR profile"; +} + +/** + * Initializes a newly allocated color profile object + * + * This is used only by color managers. They sub-class weston_color_profile. + * + * The reference count starts at 1. + * + * To destroy a weston_color_profile, use weston_color_profile_unref(). + */ +WL_EXPORT void +weston_color_profile_init(struct weston_color_profile *cprof, + struct weston_color_manager *cm) +{ + cprof->cm = cm; + cprof->ref_count = 1; +} + /** * Increase reference count of the color transform object * diff --git a/libweston/color.h b/libweston/color.h index 30a83924..d2137af8 100644 --- a/libweston/color.h +++ b/libweston/color.h @@ -27,8 +27,20 @@ #define WESTON_COLOR_H #include +#include #include +/** + * Represents a color profile description (an ICC color profile) + * + * Sub-classed by the color manager that created this. + */ +struct weston_color_profile { + struct weston_color_manager *cm; + int ref_count; + char *description; +}; + /** Type or formula for a curve */ enum weston_color_curve_type { /** Identity function, no-op */ @@ -161,6 +173,35 @@ struct weston_color_manager { void (*destroy)(struct weston_color_manager *cm); + /** Destroy a color profile after refcount fell to zero */ + void + (*destroy_color_profile)(struct weston_color_profile *cprof); + + /** Create a color profile from ICC data + * + * \param cm The color manager. + * \param icc_data Pointer to the ICC binary data. + * \param icc_len Length of the ICC data in bytes. + * \param name_part A string to be used in describing the profile. + * \param cprof_out On success, the created object is returned here. + * On failure, untouched. + * \param errmsg On success, untouched. On failure, a pointer to a + * string describing the error is stored here. The string must be + * free()'d. + * \return True on success, false on failure. + * + * This may return a new reference to an existing color profile if + * that profile is identical to the one that would be created, apart + * from name_part. + */ + bool + (*get_color_profile_from_icc)(struct weston_color_manager *cm, + const void *icc_data, + size_t icc_len, + const char *name_part, + struct weston_color_profile **cprof_out, + char **errmsg); + /** Destroy a color transform after refcount fell to zero */ void (*destroy_color_transform)(struct weston_color_transform *xform); @@ -231,6 +272,10 @@ struct weston_color_manager { struct weston_color_transform **xform_out); }; +void +weston_color_profile_init(struct weston_color_profile *cprof, + struct weston_color_manager *cm); + struct weston_color_transform * weston_color_transform_ref(struct weston_color_transform *xform);