/* * Copyright 2024 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 "color.h" #include "color-properties.h" #include "shared/helpers.h" #include "shared/string-helpers.h" #include "shared/xalloc.h" #include "shared/weston-assert.h" /** Enum that helps keep track of what params have been set. */ enum weston_color_profile_params_set { WESTON_COLOR_PROFILE_PARAMS_PRIMARIES = 0x01, WESTON_COLOR_PROFILE_PARAMS_TF = 0x02, WESTON_COLOR_PROFILE_PARAMS_TARGET_PRIMARIES = 0x04, WESTON_COLOR_PROFILE_PARAMS_LUMINANCE = 0x08, WESTON_COLOR_PROFILE_PARAMS_MAXCLL = 0x10, WESTON_COLOR_PROFILE_PARAMS_MAXFALL = 0x20, }; /** Builder object to create color profiles with parameters. */ struct weston_color_profile_param_builder { struct weston_compositor *compositor; struct weston_color_profile_params params; /* * Keeps track of what params have already been set. * * A bitmask of values from enum weston_color_profile_params_set. */ uint32_t group_mask; /* * Keeps track of errors. * * This API may produce errors, and we store all the error messages in * the string below to help users to debug. Regarding error codes, we * store only the first that occurs. * * Such errors can be queried with * weston_color_profile_param_builder_get_error(). They are also * return'ed when users of the API set all the params and call * weston_color_profile_param_builder_create_color_profile(). */ enum weston_color_profile_param_builder_error err; bool has_errors; FILE *err_fp; char *err_msg; size_t err_msg_size; }; /** * Creates struct weston_color_profile_param_builder object. It should be used * to create color profiles from parameters. * * We expect it to be used by our frontend (to allow creating color profiles * from .ini files or similar) and by the color-management protocol * implementation (so that clients can create color profiles from parameters). * * It is invalid to set the same parameter twice using this object. * * After creating the color profile from this object, it will be automatically * destroyed. * * \param compositor The weston compositor. * \return The struct weston_color_profile_param_builder object created. */ struct weston_color_profile_param_builder * weston_color_profile_param_builder_create(struct weston_compositor *compositor) { struct weston_color_profile_param_builder *builder; builder = xzalloc(sizeof(*builder)); builder->compositor = compositor; builder->err_fp = open_memstream(&builder->err_msg, &builder->err_msg_size); weston_assert_ptr(compositor, builder->err_fp); return builder; } /** * Destroys a struct weston_color_profile_param_builder object. * * \param builder The object that should be destroyed. */ void weston_color_profile_param_builder_destroy(struct weston_color_profile_param_builder *builder) { fclose(builder->err_fp); free(builder->err_msg); free(builder); } static void __attribute__ ((format (printf, 3, 4))) store_error(struct weston_color_profile_param_builder *builder, enum weston_color_profile_param_builder_error err, const char *fmt, ...) { va_list ap; /* First error that we log. We also log the err code in such case. */ if (!builder->has_errors) { builder->has_errors = true; builder->err = err; goto log_msg; } /* There are errors already, so add new line first. */ fprintf(builder->err_fp, "\n"); log_msg: va_start(ap, fmt); vfprintf(builder->err_fp, fmt, ap); va_end(ap); } /** * Returns the code for the first error generated and a string with all error * messages that we caught. * * Function weston_color_profile_param_builder_create_color_profile() will also * fail with the first error code (if there's any), but we still need this * function because some users of the API may want to know about the error * immediately after calling a setter. * * \param builder The builder object whose parameters will be set. * \param err Set if there's an error, untouched otherwise. The first error code caught. * \param err_msg Set if there's an error, untouched otherwise. Must be free()'d * by the caller. Combination of all error messages caught. Not terminated with * a new line character. * \return true if there's an error, false otherwise. */ bool weston_color_profile_param_builder_get_error(struct weston_color_profile_param_builder *builder, enum weston_color_profile_param_builder_error *err, char **err_msg) { if (!builder->has_errors) return false; *err = builder->err; fflush(builder->err_fp); *err_msg = strdup(builder->err_msg); return true; } /** * Sets primaries for struct weston_color_profile_param_builder object. * * See also weston_color_profile_param_builder_set_primaries_named(), which is * another way of setting the primaries. * * If the primaries are already set (with this function or the one * mentioned above), this should fail. Setting a parameter twice is forbidden. * * If this fails, users can call weston_color_profile_param_builder_get_error() * to get the error details. * * \param builder The builder object whose parameters will be set. * \param primaries The object containing the primaries. * \return true on success, false otherwise. */ bool weston_color_profile_param_builder_set_primaries(struct weston_color_profile_param_builder *builder, const struct weston_color_gamut *primaries) { struct weston_color_manager *cm = builder->compositor->color_manager; bool success = true; if (!((cm->supported_color_features >> WESTON_COLOR_FEATURE_SET_PRIMARIES) & 1)) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_PRIMARIES, "set_primaries not supported by the color manager"); success = false; } if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_PRIMARIES) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET, "primaries were already set"); success = false; } if (!success) return false; builder->params.primaries = *primaries; builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_PRIMARIES; return true; } /** * Sets primaries for struct weston_color_profile_param_builder object using a * enum weston_color_primaries. * * See also weston_color_profile_param_builder_set_primaries(), which is another * way of setting the primaries. * * If the primaries are already set (with this function or the one mentioned * above), this should fail. Setting a parameter twice is forbidden. * * If this fails, users can call weston_color_profile_param_builder_get_error() * to get the error details. * * \param builder The builder object whose parameters will be set. * \param primaries The enum representing the primaries. * \return true on success, false otherwise. */ bool weston_color_profile_param_builder_set_primaries_named(struct weston_color_profile_param_builder *builder, enum weston_color_primaries primaries) { struct weston_compositor *compositor = builder->compositor; struct weston_color_manager *cm = compositor->color_manager; bool success = true; if (!((cm->supported_primaries_named >> primaries) & 1)) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_PRIMARIES, "named primaries %u not supported by the color manager", primaries); success = false; } if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_PRIMARIES) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET, "primaries were already set"); success = false; } if (!success) return false; builder->params.primaries_info = weston_color_primaries_info_from(compositor, primaries); builder->params.primaries = builder->params.primaries_info->color_gamut; builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_PRIMARIES; return true; } /** * Sets transfer function for struct weston_color_profile_param_builder object * using a enum weston_transfer_function. * * See also weston_color_profile_param_builder_set_tf_power_exponent(), which is * another way of setting the transfer function. * * If the transfer function is already set (with this function or the one * mentioned above), this should fail. Setting a parameter twice is forbidden. * * If this fails, users can call weston_color_profile_param_builder_get_error() * to get the error details. * * \param builder The builder object whose parameters will be set. * \param tf The enum representing the transfer function. * \return true on success, false otherwise. */ bool weston_color_profile_param_builder_set_tf_named(struct weston_color_profile_param_builder *builder, enum weston_transfer_function tf) { struct weston_compositor *compositor = builder->compositor; struct weston_color_manager *cm = compositor->color_manager; bool success = true; if (!((cm->supported_tf_named >> tf) & 1)) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_TF, "named tf %u not supported by the color manager", tf); success = false; } if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TF) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET, "tf was already set"); success = false; } if (!success) return false; builder->params.tf_info = weston_color_tf_info_from(compositor, tf); weston_assert_false(builder->compositor, builder->params.tf_info->has_parameters); builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_TF; return true; } /** * Sets transfer function for struct weston_color_profile_param_builder object * using a power law function exponent g. In such case, the transfer function is * y = x ^ g. The valid range for the given exponent is [1.0, 10.0]. * * See also weston_color_profile_param_builder_set_tf_named(), which is another * way of setting the transfer function. * * If the transfer function is already set (with this function or the one * mentioned above), this should fail. Setting a parameter twice is forbidden. * * If this fails, users can call weston_color_profile_param_builder_get_error() * to get the error details. * * \param builder The builder object whose parameters will be set. * \param power_exponent The power law function exponent. * \return true on success, false otherwise. */ bool weston_color_profile_param_builder_set_tf_power_exponent(struct weston_color_profile_param_builder *builder, float power_exponent) { struct weston_compositor *compositor = builder->compositor; struct weston_color_manager *cm = compositor->color_manager; bool success = true; if (!((cm->supported_color_features >> WESTON_COLOR_FEATURE_SET_TF_POWER) & 1)) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_TF, "set_tf_power not supported by the color manager"); success = false; } if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TF) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET, "tf was already set"); success = false; } /* The exponent should be at least 1.0 and at most 10.0. */ if (!(power_exponent >= 1.0 && power_exponent <= 10.0)) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_TF, "tf power exponent %f is not in the range [1.0, 10.0]", power_exponent); success = false; } if (!success) return false; builder->params.tf_info = weston_color_tf_info_from(compositor, WESTON_TF_POWER); builder->params.tf_params[0] = power_exponent; builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_TF; return true; } /** * Sets target primaries for struct weston_color_profile_param_builder object * using raw values. * * If the target primaries are already set, this should fail. Setting a * parameter twice is forbidden. * * If this fails, users can call weston_color_profile_param_builder_get_error() * to get the error details. * * \param builder The builder object whose parameters will be set. * \param target_primaries The object containing the target primaries. * \return true on success, false otherwise. */ bool weston_color_profile_param_builder_set_target_primaries(struct weston_color_profile_param_builder *builder, const struct weston_color_gamut *target_primaries) { struct weston_color_manager *cm = builder->compositor->color_manager; bool success = true; if (!((cm->supported_color_features >> WESTON_COLOR_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES) & 1)) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_TARGET_PRIMARIES, "set_mastering_display_primaries not supported by " \ "the color manager"); success = false; } if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TARGET_PRIMARIES) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET, "target primaries were already set"); success = false; } if (!success) return false; builder->params.target_primaries = *target_primaries; builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_TARGET_PRIMARIES; return true; } /** * Sets target luminance for struct weston_color_profile_param_builder object. * * If the target luminance is already set, this should fail. Setting a parameter * twice is forbidden. * * If this fails, users can call weston_color_profile_param_builder_get_error() * to get the error details. * * \param builder The builder object whose parameters will be set. * \param min_luminance The minimum luminance. * \param max_luminance The maximum luminance. * \return true on success, false otherwise. */ bool weston_color_profile_param_builder_set_target_luminance(struct weston_color_profile_param_builder *builder, float min_luminance, float max_luminance) { bool success = true; if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_LUMINANCE) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET, "target luminance was already set"); success = false; } if (min_luminance >= max_luminance) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INVALID_LUMINANCE, "min luminance %f shouldn't be greater than or equal to max %f", min_luminance, max_luminance); success = false; } if (!success) return false; builder->params.min_luminance = min_luminance; builder->params.max_luminance = max_luminance; builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_LUMINANCE; return true; } /** * Sets target maxFALL for struct weston_color_profile_param_builder object. * * If the target maxFALL is already set, this should fail. Setting a parameter * twice is forbidden. * * If this fails, users can call weston_color_profile_param_builder_get_error() * to get the error details. * * \param builder The builder object whose parameters will be set. * \param maxFALL The maxFALL. * \return true on success, false otherwise. */ bool weston_color_profile_param_builder_set_maxFALL(struct weston_color_profile_param_builder *builder, float maxFALL) { if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXFALL) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET, "max fall was already set"); return false; } builder->params.maxFALL = maxFALL; builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_MAXFALL; return true; } /** * Sets target maxCLL for struct weston_color_profile_param_builder object. * * If the target maxCLL is already set, this should fail. Setting a parameter * twice is forbidden. * * If this fails, users can call weston_color_profile_param_builder_get_error() * to get the error details. * * \param builder The builder object whose parameters will be set. * \param maxCLL The maxCLL. * \return true on success, false otherwise. */ bool weston_color_profile_param_builder_set_maxCLL(struct weston_color_profile_param_builder *builder, float maxCLL) { if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXCLL) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_ALREADY_SET, "max cll was already set"); return false; } builder->params.maxCLL = maxCLL; builder->group_mask |= WESTON_COLOR_PROFILE_PARAMS_MAXCLL; return true; } static void builder_validate_params_set(struct weston_color_profile_param_builder *builder) { /* Primaries are mandatory. */ if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_PRIMARIES)) store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INCOMPLETE_SET, "primaries not set"); /* TF is mandatory. */ if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TF)) store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INCOMPLETE_SET, "transfer function not set"); /* If luminance values were given, tf must be PQ. */ if (builder->params.tf_info->tf != WESTON_TF_ST2084_PQ && (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_LUMINANCE || builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXCLL || builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXFALL)) store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INCONSISTENT_SET, "luminance values were given but transfer function " \ "is not Rec. ITU-R BT.2100-2 (PQ)"); } static float triangle_area(float x1, float y1, float x2, float y2, float x3, float y3) { /* Based on the shoelace formula, also known as Gauss's area formula. */ return fabs((x1 - x3) * (y2 - y1) - (x1 - x2) * (y3 - y1)) / 2.0f; } static bool is_point_inside_triangle(float point_x, float point_y, float x1, float y1, float x2, float y2, float x3, float y3) { float A1, A2, A3; float A; const float PRECISION = 1e-5; A = triangle_area(x1, y1, x2, y2, x3, y3); /* Bail out if something that is not a triangle was given. */ if (A <= PRECISION) return false; A1 = triangle_area(point_x, point_y, x1, y1, x2, y2); A2 = triangle_area(point_x, point_y, x1, y1, x3, y3); A3 = triangle_area(point_x, point_y, x2, y2, x3, y3); if (fabs(A - (A1 + A2 + A3)) <= PRECISION) return true; return false; } static void validate_color_gamut(struct weston_color_profile_param_builder *builder, const struct weston_color_gamut *gamut, const char *gamut_name) { /* * We choose the legal range [-1.0, 2.0] for CIE xy values. It is * probably more than we'd ever need, but tight enough to not cause * mathematical issues. If wasn't for the ACES AP0 color space, we'd * probably choose the range [0.0, 1.0]. */ if (gamut->white_point.x < -1.0f || gamut->white_point.x > 2.0f || gamut->white_point.y < -1.0f || gamut->white_point.y > 2.0f || gamut->primary[0].x < -1.0f || gamut->primary[0].x > 2.0f || gamut->primary[0].y < -1.0f || gamut->primary[0].y > 2.0f || gamut->primary[1].x < -1.0f || gamut->primary[1].x > 2.0f || gamut->primary[1].y < -1.0f || gamut->primary[1].y > 2.0f || gamut->primary[2].x < -1.0f || gamut->primary[2].x > 2.0f || gamut->primary[2].y < -1.0f || gamut->primary[2].y > 2.0f) { store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_CIE_XY_OUT_OF_RANGE, "invalid %s", gamut_name); return; } /* * That is not sufficient. There are points inside the triangle that * would not be valid white points. But for now that's good enough. */ if (!is_point_inside_triangle(gamut->white_point.x, gamut->white_point.y, gamut->primary[0].x, gamut->primary[0].y, gamut->primary[1].x, gamut->primary[1].y, gamut->primary[2].x, gamut->primary[2].y)) store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_CIE_XY_OUT_OF_RANGE, "white point out of %s volume", gamut_name); } static void validate_maxcll(struct weston_color_profile_param_builder *builder) { if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_LUMINANCE)) return; if (builder->params.min_luminance >= builder->params.maxCLL) store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INCONSISTENT_LUMINANCES, "maxCLL (%f) should be greater or equal to min luminance (%f)", builder->params.maxCLL, builder->params.min_luminance); if (builder->params.max_luminance < builder->params.maxCLL) store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INCONSISTENT_LUMINANCES, "maxCLL (%f) should not be greater than max luminance (%f)", builder->params.maxCLL, builder->params.max_luminance); } static void validate_maxfall(struct weston_color_profile_param_builder *builder) { if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_LUMINANCE)) return; if (builder->params.min_luminance >= builder->params.maxFALL) store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INCONSISTENT_LUMINANCES, "maxFALL (%f) should be greater or equal to min luminance (%f)", builder->params.maxFALL, builder->params.min_luminance); if (builder->params.max_luminance < builder->params.maxFALL) store_error(builder, WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_INCONSISTENT_LUMINANCES, "maxFALL (%f) should not be greater than max luminance (%f)", builder->params.maxFALL, builder->params.max_luminance); } static void builder_validate_params(struct weston_color_profile_param_builder *builder) { if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_PRIMARIES) validate_color_gamut(builder, &builder->params.primaries, "primaries"); if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TARGET_PRIMARIES) validate_color_gamut(builder, &builder->params.target_primaries, "target primaries"); if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXCLL) validate_maxcll(builder); if (builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXFALL) validate_maxfall(builder); } static void builder_complete_params(struct weston_color_profile_param_builder *builder) { /* If no target primaries were set, it matches the primaries. */ if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_TARGET_PRIMARIES)) builder->params.target_primaries = builder->params.primaries; /* * If luminance is not set, set it to negative. Same applies to maxCLL * and maxFALL. */ if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_LUMINANCE)) { builder->params.min_luminance = -1.0f; builder->params.max_luminance = -1.0f; } if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXCLL)) builder->params.maxCLL = -1.0f; if (!(builder->group_mask & WESTON_COLOR_PROFILE_PARAMS_MAXFALL)) builder->params.maxFALL = -1.0f; } /** * Creates a color profile from a struct weston_color_profile_param_builder * object. * * After creating the weston_color_profile_param_builder and setting the * appropriate parameters, this function should be called to finally create the * color profile. It checks if the parameters are consistent and, if so, call * the color manager to create the color profile. * * Also, this is a destructor function. It destroys the builder object. * * \param builder The object that has the parameters set. * \param name_part A string to be used in describing the profile. * \param err Set if there's an error, untouched otherwise. The first error code caught. * \param err_msg Set if there's an error, untouched otherwise. Must be free()'d * by the caller. Combination of all error messages caught. Not terminated with * a new line character. * \return The color profile created, or NULL on failure. */ struct weston_color_profile * weston_color_profile_param_builder_create_color_profile(struct weston_color_profile_param_builder *builder, const char *name_part, enum weston_color_profile_param_builder_error *err, char **err_msg) { struct weston_color_manager *cm = builder->compositor->color_manager; struct weston_color_profile_params *params = &builder->params; struct weston_color_profile *cprof = NULL; bool ret; /* * See struct weston_color_profile_params description. That struct has * some rules that we need to fullfil (e.g. target primaries must be * set, even if client does not pass anything). In this function we * complete the param set in order to fullfil such rules. */ builder_complete_params(builder); /* Ensure that params make sense together. */ builder_validate_params_set(builder); /* Ensure that each param set is reasonable. */ builder_validate_params(builder); /* Something went wrong, so error out. */ if (builder->has_errors) { fflush(builder->err_fp); *err_msg = strdup(builder->err_msg); *err = builder->err; goto out; } ret = cm->get_color_profile_from_params(cm, params, name_part, &cprof, err_msg); if (!ret) *err = WESTON_COLOR_PROFILE_PARAM_BUILDER_ERROR_UNSUPPORTED; out: weston_color_profile_param_builder_destroy(builder); return cprof; }