// SPDX-License-Identifier: GPL-2.0-or-later /* * Colors. * * Author: * Lauris Kaplinski * bulia byak * Jon A. Cruz * * Copyright (C) 2001-2002 Lauris Kaplinski * Copyright (C) 2001 Ximian, Inc. * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include #include #include #include #include #include "color.h" #include "hsluv.h" #include "strneq.h" #include "svg/svg-color.h" #include "svg/css-ostringstream.h" #include "object/color-profile.h" #define return_if_fail(x) if (!(x)) { printf("assertion failed: " #x); return; } #define return_val_if_fail(x, val) if (!(x)) { printf("assertion failed: " #x); return val; } using Inkscape::CSSOStringStream; static bool profileMatches(SVGICCColor const &first, SVGICCColor const &second); static constexpr double PROFILE_EPSILON = 1e-8; SPColor::SPColor(SPColor const &other) { *this = other; } SPColor::SPColor(float r, float g, float b) { set( r, g, b ); } SPColor::SPColor(guint32 value) { set( value ); } SPColor &SPColor::operator=(SPColor const &other) { if (this == &other){ return *this; } set(other.v.c[0], other.v.c[1], other.v.c[2]); copyColors(other); return *this; } /** * Returns true if colors match. */ bool SPColor::operator == (SPColor const& other) const { bool match = (v.c[0] == other.v.c[0]) && (v.c[1] == other.v.c[1]) && (v.c[2] == other.v.c[2]); match &= profileMatches(_icc, other._icc); return match; } /** * Returns true if no RGB value differs epsilon or more in both colors, * false otherwise. */ bool SPColor::isClose(SPColor const& other, float epsilon) const { bool match = (fabs((v.c[0]) - (other.v.c[0])) < epsilon) && (fabs((v.c[1]) - (other.v.c[1])) < epsilon) && (fabs((v.c[2]) - (other.v.c[2])) < epsilon); match &= profileMatches(_icc, other._icc); return match; } /** * Matches two profile colors within PROFILE_EPSILON distance. */ static bool profileMatches(SVGICCColor const &first, SVGICCColor const &second) { if (first.colorProfile != second.colorProfile || first.colors.size() != second.colors.size()) { return false; } for (unsigned i = 0; i < first.colors.size(); i++) { if (fabs(first.colors[i] - second.colors[i]) > PROFILE_EPSILON) { return false; } } return true; } /** * Sets RGB values and colorspace in color. * \pre 0 <={r,g,b}<=1 */ void SPColor::set(float r, float g, float b) { return_if_fail(r >= 0.0); return_if_fail(r <= 1.0); return_if_fail(g >= 0.0); return_if_fail(g <= 1.0); return_if_fail(b >= 0.0); return_if_fail(b <= 1.0); v.c[0] = r; v.c[1] = g; v.c[2] = b; // Remove icc colors, but not the profile unsetColors(); } /** * Converts 32bit value to RGB floats and sets color. */ void SPColor::set(guint32 value) { v.c[0] = (value >> 24) / 255.0F; v.c[1] = ((value >> 16) & 0xff) / 255.0F; v.c[2] = ((value >> 8) & 0xff) / 255.0F; // Remove icc colors, but not the profile unsetColors(); } /** * Returns true if this color has a color profile set. */ bool SPColor::hasColorProfile() const { return !_icc.colorProfile.empty(); } /** * Returns true if there is a defined color for the set profile. */ bool SPColor::hasColors() const { return hasColorProfile() && !_icc.colors.empty() && _icc.colors[0] != -1.0; } void SPColor::setColorProfile(Inkscape::ColorProfile *profile) { unsetColorProfile(); if (profile) { _icc.colorProfile = profile->name; for (int i = 0; i < profile->getChannelCount(); i++) { _icc.colors.emplace_back(-1.0); } } } void SPColor::setColors(std::vector &&values) { if (values.size() != _icc.colors.size()) { g_error("Can't set profile-based color, wrong number of colors."); unsetColors(); return; } _icc.colors = std::move(values); } void SPColor::copyColors(const SPColor &other) { if (!profileMatches(_icc, other._icc)) { _icc = other._icc; // copy } } void SPColor::setColor(unsigned int index, double value) { if (index < 0 || index > _icc.colors.size()) { g_warning("Can't set profile-based color, index out of range."); } _icc.colors[index] = value; } /** * Clears the saved profile color, but retains the color profile for imediate reuse. */ void SPColor::unsetColors() { for (double &color : _icc.colors) { color = -1.0; } } /** * Remove the color profile and save color, reverting to sRGB only. */ void SPColor::unsetColorProfile() { _icc.colorProfile = ""; _icc.colors.clear(); } /** * Convert SPColor with integer alpha value to 32bit RGBA value. * \pre alpha < 256 */ guint32 SPColor::toRGBA32( int alpha ) const { return_val_if_fail (alpha <= 0xff, 0x0); if (!is_set()) { return SP_RGBA32_U_COMPOSE(0, 0, 0, alpha); } guint32 rgba = SP_RGBA32_U_COMPOSE( SP_COLOR_F_TO_U(v.c[0]), SP_COLOR_F_TO_U(v.c[1]), SP_COLOR_F_TO_U(v.c[2]), alpha ); return rgba; } /** * Convert SPColor with float alpha value to 32bit RGBA value. * \pre color != NULL && 0 <= alpha <= 1 */ guint32 SPColor::toRGBA32( double alpha ) const { return_val_if_fail(alpha >= 0.0, 0x0); return_val_if_fail(alpha <= 1.0, 0x0); return toRGBA32( static_cast(SP_COLOR_F_TO_U(alpha)) ); } std::string SPColor::toString() const { CSSOStringStream css; char tmp[64] = {0}; sp_svg_write_color(tmp, sizeof(tmp), toRGBA32(0x0ff)); css << tmp; if (hasColors()) { if ( !css.str().empty() ) { css << " "; } css << "icc-color(" << _icc.colorProfile; for (double color : _icc.colors) { css << ", " << color; } css << ')'; } return css.str(); } /** * Set the color from the given string. * * @param str - The CSS3 formatted string input (see toString) * @returns true if the color was parsed correctly. */ bool SPColor::fromString(char const *str) { guint32 const rgb0 = sp_svg_read_color(str, &str, 0xff); if (rgb0 == 0xff) { return false; } set(rgb0); while (g_ascii_isspace(*str)) { ++str; } if (strneq(str, "icc-color(", 10)) { if (!sp_svg_read_icc_color(str, &str, &_icc)) { g_warning("Couldn't parse icc-color format in css."); unsetColorProfile(); } } return true; } /** * Fill rgb float array with values from SPColor. * \pre color != NULL && rgb != NULL && rgb[0-2] is meaningful */ void SPColor::get_rgb_floatv(float *rgb) const { return_if_fail (rgb != nullptr); if (!is_set()) { return; } rgb[0] = v.c[0]; rgb[1] = v.c[1]; rgb[2] = v.c[2]; } /** * Fill cmyk float array with values from SPColor. * \pre color != NULL && cmyk != NULL && cmyk[0-3] is meaningful */ void SPColor::get_cmyk_floatv(float *cmyk) const { return_if_fail (cmyk != nullptr); if (!is_set()) { return; } SPColor::rgb_to_cmyk_floatv( cmyk, v.c[0], v.c[1], v.c[2] ); } /* Plain mode helpers */ /** * Fill hsv float array from r,g,b float values. */ void SPColor::rgb_to_hsv_floatv (float *hsv, float r, float g, float b) { float max, min, delta; max = MAX (MAX (r, g), b); min = MIN (MIN (r, g), b); delta = max - min; hsv[2] = max; if (max > 0) { hsv[1] = delta / max; } else { hsv[1] = 0.0; } if (hsv[1] != 0.0) { if (r == max) { hsv[0] = (g - b) / delta; } else if (g == max) { hsv[0] = 2.0 + (b - r) / delta; } else { hsv[0] = 4.0 + (r - g) / delta; } hsv[0] = hsv[0] / 6.0; if (hsv[0] < 0) hsv[0] += 1.0; } else hsv[0] = 0.0; } /** * Fill rgb float array from h,s,v float values. */ void SPColor::hsv_to_rgb_floatv (float *rgb, float h, float s, float v) { double f, w, q, t, d; d = h * 5.99999999; f = d - floor (d); w = v * (1.0 - s); q = v * (1.0 - (s * f)); t = v * (1.0 - (s * (1.0 - f))); if (d < 1.0) { *rgb++ = v; *rgb++ = t; *rgb++ = w; } else if (d < 2.0) { *rgb++ = q; *rgb++ = v; *rgb++ = w; } else if (d < 3.0) { *rgb++ = w; *rgb++ = v; *rgb++ = t; } else if (d < 4.0) { *rgb++ = w; *rgb++ = q; *rgb++ = v; } else if (d < 5.0) { *rgb++ = t; *rgb++ = w; *rgb++ = v; } else { *rgb++ = v; *rgb++ = w; *rgb++ = q; } } /** * Fill hsl float array from r,g,b float values. */ void SPColor::rgb_to_hsl_floatv (float *hsl, float r, float g, float b) { float max = MAX (MAX (r, g), b); float min = MIN (MIN (r, g), b); float delta = max - min; hsl[2] = (max + min)/2.0; if (delta == 0) { hsl[0] = 0; hsl[1] = 0; } else { if (hsl[2] <= 0.5) hsl[1] = delta / (max + min); else hsl[1] = delta / (2 - max - min); if (r == max) hsl[0] = (g - b) / delta; else if (g == max) hsl[0] = 2.0 + (b - r) / delta; else if (b == max) hsl[0] = 4.0 + (r - g) / delta; hsl[0] = hsl[0] / 6.0; if (hsl[0] < 0) hsl[0] += 1; if (hsl[0] > 1) hsl[0] -= 1; } } static float hue_2_rgb (float v1, float v2, float h) { if (h < 0) h += 6.0; if (h > 6) h -= 6.0; if (h < 1) return v1 + (v2 - v1) * h; if (h < 3) return v2; if (h < 4) return v1 + (v2 - v1) * (4 - h); return v1; } /** * Fill rgb float array from h,s,l float values. */ void SPColor::hsl_to_rgb_floatv (float *rgb, float h, float s, float l) { if (s == 0) { rgb[0] = l; rgb[1] = l; rgb[2] = l; } else { float v2; if (l < 0.5) { v2 = l * (1 + s); } else { v2 = l + s - l*s; } float v1 = 2*l - v2; rgb[0] = hue_2_rgb (v1, v2, h*6 + 2.0); rgb[1] = hue_2_rgb (v1, v2, h*6); rgb[2] = hue_2_rgb (v1, v2, h*6 - 2.0); } } /** * Fill cmyk float array from r,g,b float values. */ void SPColor::rgb_to_cmyk_floatv (float *cmyk, float r, float g, float b) { float c, m, y, k, kd; c = 1.0 - r; m = 1.0 - g; y = 1.0 - b; k = MIN (MIN (c, m), y); c = c - k; m = m - k; y = y - k; kd = 1.0 - k; if (kd > 1e-9) { c = c / kd; m = m / kd; y = y / kd; } cmyk[0] = c; cmyk[1] = m; cmyk[2] = y; cmyk[3] = k; } /** * Fill rgb float array from c,m,y,k float values. */ void SPColor::cmyk_to_rgb_floatv (float *rgb, float c, float m, float y, float k) { float kd; kd = 1.0 - k; c = c * kd; m = m * kd; y = y * kd; c = c + k; m = m + k; y = y + k; rgb[0] = 1.0 - c; rgb[1] = 1.0 - m; rgb[2] = 1.0 - y; } /** * Fill hsluv float array from r,g,b float values. */ void SPColor::rgb_to_hsluv_floatv(float *hsluv, float r, float g, float b) { auto tmp = Hsluv::rgb_to_hsluv(r, g, b); tmp[0] /= 360.0; tmp[1] /= 100.0; tmp[2] /= 100.0; for (size_t i : {0, 1, 2}) { hsluv[i] = std::clamp(tmp[i], 0.0, 1.0); } } /** * Fill rgb float array from h,s,l float values. */ void SPColor::hsluv_to_rgb_floatv(float *rgb, float h, float s, float l) { auto tmp = Hsluv::hsluv_to_rgb(h * 360, s * 100, l * 100); for (size_t i : {0, 1, 2}) { rgb[i] = tmp[i]; } } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :