diff --git a/buildtools/check_license_headers.py b/buildtools/check_license_headers.py index cf6796c454502669b46753022fa0fb1859459163..773a2250182ce61d6eecdf8b84330f92b59b7b4d 100755 --- a/buildtools/check_license_headers.py +++ b/buildtools/check_license_headers.py @@ -44,6 +44,7 @@ IGNORE_FILE_ENDINGS = [ ".dia", ".dll", ".eps", + ".icc", ".kate-swp", ".ods", ".otf", @@ -147,7 +148,7 @@ if __name__ == '__main__': main(files_all()) except LicenseCheckError as e: print(e, file=sys.stderr) - print("If you think this message is wrong, edit buildtools/check_license_header.py", file=sys.stderr) + print("If you think this message is wrong, edit buildtools/check_license_headers.py", file=sys.stderr) sys.exit(1) # vi:sw=4:expandtab: diff --git a/po/POTFILES.src.in b/po/POTFILES.src.in index 6af969bbb5c3f8b221cbb016953bcf20ae9ae694..61123a4e666fd27c9b085466402329bb9ca91f0b 100644 --- a/po/POTFILES.src.in +++ b/po/POTFILES.src.in @@ -41,7 +41,7 @@ ${_build_dir}/share/templates/templates.h ../src/actions/actions-view-window.cpp ../src/actions/actions-window.cpp ../src/auto-save.cpp -../src/color/cms-util.cpp +../src/colors/spaces/components.cpp ../src/context-fns.cpp ../src/desktop-events.cpp ../src/desktop-style.cpp @@ -391,7 +391,6 @@ ${_build_dir}/share/templates/templates.h ../src/ui/util.cpp ../src/ui/widget/canvas-grid.cpp ../src/ui/widget/color-entry.cpp -../src/ui/widget/color-icc-selector.cpp ../src/ui/widget/color-notebook.cpp ../src/ui/widget/color-palette.cpp ../src/ui/widget/color-scales.cpp @@ -431,5 +430,4 @@ ${_build_dir}/share/templates/templates.h ../src/util/font-collections.h ../src/util/paper.cpp ../src/vanishing-point.cpp -../src/widgets/paintdef.cpp ../src/widgets/sp-attribute-widget.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f454c988e44ae5d447ad7b9bc2cc406e30696942..a721d0e30296c049540bd601fcb5cb8785dadff9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,8 +12,6 @@ set(inkscape_SRC attributes.cpp auto-save.cpp axis-manip.cpp - hsluv.cpp - color.cpp composite-undo-stack-observer.cpp conditions.cpp conn-avoid-ref.cpp @@ -50,14 +48,12 @@ set(inkscape_SRC mod360.cpp object-hierarchy.cpp object-snapper.cpp - oklab.cpp page-manager.cpp path-chemistry.cpp path-prefix.cpp perspective-line.cpp preferences.cpp print.cpp - profile-manager.cpp proj_pt.cpp pure-transform.cpp rdf.cpp @@ -94,10 +90,6 @@ set(inkscape_SRC auto-save.h axis-manip.h bad-uri-exception.h - color-rgba.h - hsluv.h - color.h - colorspace.h composite-undo-stack-observer.h conditions.h conn-avoid-ref.h @@ -139,7 +131,6 @@ set(inkscape_SRC number-opt-number.h object-hierarchy.h object-snapper.h - oklab.h page-manager.h path-chemistry.h path-prefix.h @@ -151,7 +142,6 @@ set(inkscape_SRC preferences-skeleton.h preferences.h print.h - profile-manager.h proj_pt.h pure-transform.h rdf.h @@ -309,7 +299,7 @@ list(APPEND inkscape_SRC # these call add_inkscape_source add_subdirectory(actions) add_subdirectory(async) -add_subdirectory(color) +add_subdirectory(colors) add_subdirectory(debug) add_subdirectory(display) add_subdirectory(extension) diff --git a/src/attributes.cpp b/src/attributes.cpp index 55d07462139c78b33cc92e6d58d627cc73ee5358..99657120d1092dc3b451f10a8777d347f9e82085 100644 --- a/src/attributes.cpp +++ b/src/attributes.cpp @@ -130,7 +130,7 @@ static SPStyleProp const props[] = { {SPAttr::INKSCAPE_DOCUMENT_UNITS, "inkscape:document-units"}, // This setting sets the Display units, *not* the units used in SVG {SPAttr::INKSCAPE_LOCKGUIDES, "inkscape:lockguides"}, {SPAttr::UNITS, "units"}, - /* SPColorProfile */ + /* ColorProfile */ {SPAttr::LOCAL, "local"}, {SPAttr::NAME, "name"}, {SPAttr::RENDERING_INTENT, "rendering-intent"}, diff --git a/src/attributes.h b/src/attributes.h index 4c204b98fec92fd36fe00311cecfd6593b1bf370..f42ab8a93515d8696766e91260385f51046fd32e 100644 --- a/src/attributes.h +++ b/src/attributes.h @@ -129,7 +129,7 @@ enum class SPAttr { INKSCAPE_DOCUMENT_UNITS, INKSCAPE_LOCKGUIDES, UNITS, - /* SPColorProfile */ + /* ColorProfile */ LOCAL, NAME, RENDERING_INTENT, diff --git a/src/color-rgba.h b/src/color-rgba.h deleted file mode 100644 index a48d1e4e6d0f434e0cba230ba7c9446c058d0542..0000000000000000000000000000000000000000 --- a/src/color-rgba.h +++ /dev/null @@ -1,173 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - Authors: - bulia byak - - Copyright (C) 2004 Authors - - Released under GNU GPL v2+, read the file 'COPYING' for more information. -*/ -#ifndef SEEN_COLOR_RGBA_H -#define SEEN_COLOR_RGBA_H - -#include -#include - -/** - * A class to contain a floating point RGBA color as one unit. - */ -class ColorRGBA { -public: - - /** - * A constructor to create the color from four floating point values. - * Load the values into the array of floats in this object. - * - * @param c0 Red - * @param c1 Green - * @param c2 Blue - * @param c3 Alpha - */ - ColorRGBA(float c0, float c1, float c2, float c3) - { - _c[0] = c0; _c[1] = c1; - _c[2] = c2; _c[3] = c3; - } - - /** - * Create a quick ColorRGBA with all zeros. - */ - ColorRGBA() : _c{0.0,0.0,0.0,0.0} - { - } - - /** - * A constructor to create the color from an unsigned int, as found everywhere when dealing with colors. - * - * Separate the values and load them into the array of floats in this object. - * @param intcolor rgba32 "unsigned int representation (0xRRGGBBAA) - */ - ColorRGBA(unsigned int intcolor) - { - _c[0] = ((intcolor & 0xff000000) >> 24) / 255.0; - _c[1] = ((intcolor & 0x00ff0000) >> 16) / 255.0; - _c[2] = ((intcolor & 0x0000ff00) >> 8) / 255.0; - _c[3] = ((intcolor & 0x000000ff) >> 0) / 255.0; - - } - - /** - * Create a ColorRGBA using an array of floats. - * - * Go through each entry in the array and put it into \c _c. - * - * @param in_array The values to be placed into the object - */ - ColorRGBA(float in_array[4]) - { - for (int i = 0; i < 4; i++) - _c[i] = in_array[i]; - } - - /** - * Overwrite the values in this object with another \c ColorRGBA. - * Copy all the values from \c m into \c this. - * - * @param m Values to use for the array. - * @return This ColorRGBA object. - */ - ColorRGBA &operator=(ColorRGBA const &m) { - for (unsigned i = 0 ; i < 4 ; ++i) { - _c[i] = m._c[i]; - } - return *this; - } - - /** - * Grab a particular value from the ColorRGBA object. - * First checks to make sure that the value is within the array, - * and then return the value if it is. - * - * @param i Which value to grab. - * @return The requested value. - */ - float operator[](unsigned int const i) const { - assert( unsigned(i) < 4 ); - return _c[i]; - } - - /** - * Check to ensure that two \c ColorRGBA's are equal. - * Check each value to see if they are equal. If they all are, - * return true. - * - * @param other The guy to check against. - * @return Whether or not they are equal. - */ - bool operator== (const ColorRGBA &other) const { - for (int i = 0; i < 4; i++) { - if (_c[i] != other[i]) - return false; - } - return true; - } - - bool operator!=(ColorRGBA const &o) const { - return !(*this == o); - } - - /** - * Average two \c ColorRGBAs to create another one. - * This function goes through all the points in the two objects and - * merges them together based on the weighting. The current objects - * value are multiplied by 1.0 - weight and the second object by weight. - * This means that they should always be balanced by the parameter. - * - * @param second The second RGBA, with this being the first - * @param weight How much of each should be used. Zero is all - * this while one is all the second. Default is - * half and half. - */ - ColorRGBA average (const ColorRGBA &second, const float weight = 0.5) const { - float returnval[4]; - - for (int i = 0; i < 4; i++) { - returnval[i] = _c[i] * (1.0 - weight) + second[i] * weight; - } - - return ColorRGBA(returnval[0], returnval[1], returnval[2], returnval[3]); - } - - /** - * Give the rgba32 "unsigned int" representation of the color. - * round each components*255 and combine them (RRGGBBAA). - * WARNING : this reduces color precision (from float to 0->255 int per component) - * but it should be expected since we request this kind of output - */ - unsigned int getIntValue() const { - - return - (int(round(_c[0]*255)) << 24) | - (int(round(_c[1]*255)) << 16) | - (int(round(_c[2]*255)) << 8) | - (int(round(_c[3]*255))); - } - -private: - /** Array of values that are stored. */ - float _c[4]; -}; - - -#endif // SEEN_COLOR_RGBA_H - -/* - 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 : diff --git a/src/color.cpp b/src/color.cpp deleted file mode 100644 index f516161a8a66fd023c156738f0e90c7f4912719d..0000000000000000000000000000000000000000 --- a/src/color.cpp +++ /dev/null @@ -1,568 +0,0 @@ -// 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 : diff --git a/src/color.h b/src/color.h deleted file mode 100644 index 9a3fa133ff10c5e52357bc9c9fa250cf491a4cc1..0000000000000000000000000000000000000000 --- a/src/color.h +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#ifndef SEEN_SP_COLOR_H -#define SEEN_SP_COLOR_H - -/* - * Author: - * Lauris Kaplinski - * bulia byak - * Jon A. Cruz - * Martin Owens - * - * Copyright (C) 2001-2023 AUTHORS - * Copyright (C) 2001 Ximian, Inc. - * - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ - -#include - -#include "svg/svg-icc-color.h" - -typedef unsigned int guint32; // uint is guaranteed to hold up to 2^32 − 1 - -/* Useful composition macros */ - -#define SP_RGBA32_R_U(v) (((v) >> 24) & 0xff) -#define SP_RGBA32_G_U(v) (((v) >> 16) & 0xff) -#define SP_RGBA32_B_U(v) (((v) >> 8) & 0xff) -#define SP_RGBA32_A_U(v) ((v) & 0xff) -#define SP_COLOR_U_TO_F(v) ((v) / 255.0) -#define SP_COLOR_F_TO_U(v) ((unsigned int) ((v) * 255. + .5)) -#define SP_RGBA32_R_F(v) SP_COLOR_U_TO_F (SP_RGBA32_R_U (v)) -#define SP_RGBA32_G_F(v) SP_COLOR_U_TO_F (SP_RGBA32_G_U (v)) -#define SP_RGBA32_B_F(v) SP_COLOR_U_TO_F (SP_RGBA32_B_U (v)) -#define SP_RGBA32_A_F(v) SP_COLOR_U_TO_F (SP_RGBA32_A_U (v)) -#define SP_RGBA32_U_COMPOSE(r,g,b,a) ((((r) & 0xff) << 24) | (((g) & 0xff) << 16) | (((b) & 0xff) << 8) | ((a) & 0xff)) -#define SP_RGBA32_F_COMPOSE(r,g,b,a) SP_RGBA32_U_COMPOSE (SP_COLOR_F_TO_U (r), SP_COLOR_F_TO_U (g), SP_COLOR_F_TO_U (b), SP_COLOR_F_TO_U (a)) -#define SP_RGBA32_C_COMPOSE(c,o) SP_RGBA32_U_COMPOSE(SP_RGBA32_R_U(c),SP_RGBA32_G_U(c),SP_RGBA32_B_U(c),SP_COLOR_F_TO_U(o)) -#define SP_RGBA32_LUMINANCE(v) (SP_RGBA32_R_U(v) * 0.30 + SP_RGBA32_G_U(v) * 0.59 + SP_RGBA32_B_U(v) * 0.11 + 0.5) - -struct SVGICCColor; - -namespace Inkscape { - class ColorProfile; -}; - -/** - * An RGB color with optional icc-color part - */ -class SPColor final -{ -public: - SPColor() = default; - SPColor(SPColor const &other); - SPColor(float r, float g, float b); - SPColor(guint32 value); - - SPColor& operator= (SPColor const& other); - - bool operator == ( SPColor const& other ) const; - bool operator != ( SPColor const& other ) const { return !(*this == other); }; - operator bool() const { return is_set(); } - - bool isClose( SPColor const& other, float epsilon ) const; - - void set(float r, float g, float b); - void set(guint32 value); - - bool hasColorProfile() const; - void unsetColorProfile(); - void setColorProfile(Inkscape::ColorProfile *profile); - const std::string &getColorProfile() const { return _icc.colorProfile; } - - bool hasColors() const; - void unsetColors(); - void setColors(std::vector &&values); - void setColor(unsigned int index, double value); - void copyColors(const SPColor &other); - const std::vector &getColors() const { return _icc.colors; } - - guint32 toRGBA32( int alpha ) const; - guint32 toRGBA32( double alpha ) const; - - std::string toString() const; - bool fromString(const char *str); - - union { - float c[3] = { -1, 0, 0 }; - } v; - - guint32 get_rgba32_ualpha (guint32 alpha) const; - guint32 get_rgba32_falpha (float alpha) const; - - bool is_set() const { return v.c[0] > -1; } - void get_rgb_floatv (float *rgb) const; - void get_cmyk_floatv (float *cmyk) const; - - /* Plain mode helpers */ - - static void rgb_to_hsv_floatv (float *hsv, float r, float g, float b); - static void hsv_to_rgb_floatv (float *rgb, float h, float s, float v); - - static void rgb_to_hsl_floatv (float *hsl, float r, float g, float b); - static void hsl_to_rgb_floatv (float *rgb, float h, float s, float l); - - static void rgb_to_cmyk_floatv (float *cmyk, float r, float g, float b); - static void cmyk_to_rgb_floatv (float *rgb, float c, float m, float y, float k); - - static void rgb_to_hsluv_floatv (float *hsluv, float r, float g, float b); - static void hsluv_to_rgb_floatv (float *rgb, float h, float s, float l); - -private: - SVGICCColor _icc; -}; - -#endif // SEEN_SP_COLOR_H diff --git a/src/color/CMakeLists.txt b/src/color/CMakeLists.txt deleted file mode 100644 index 3cdda4a9d8334543ad8344b55c158e7cc62b2e80..0000000000000000000000000000000000000000 --- a/src/color/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later - -set(color_SRC - cms-system.cpp - cms-util.cpp - cmyk-conv.cpp - color-conv.cpp - - # ------- - # Headers - color-profile-cms-fns.h - cms-color-types.h - cms-system.h - cms-util.h - cmyk-conv.h - color-conv.h -) - -add_inkscape_source("${color_SRC}") diff --git a/src/color/cms-color-types.h b/src/color/cms-color-types.h deleted file mode 100644 index c6b90878b2f3f22c662bea5c098e0c746c2b5752..0000000000000000000000000000000000000000 --- a/src/color/cms-color-types.h +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file - * TODO: insert short description here - *//* - * Authors: see git history - * - * Copyright (C) 2018 Authors - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ -#ifndef SEEN_CMS_COLOR_TYPES_H -#define SEEN_CMS_COLOR_TYPES_H - -/** - * @file - * A simple abstraction to provide opaque compatibility with either lcms or lcms2. - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" // only include where actually required! -#endif - -#include // uint8_t, etc - -typedef unsigned int guint32; - -typedef void * cmsHPROFILE; -typedef void * cmsHTRANSFORM; - - -namespace Inkscape { - -/** - * Opaque holder of a 32-bit signature type. - */ -class FourCCSig { -public: - FourCCSig( FourCCSig const &other ) = default; - -protected: - FourCCSig( guint32 value ) : value(value) {}; - - guint32 value; -}; - -class ColorSpaceSig : public FourCCSig { -public: - ColorSpaceSig( ColorSpaceSig const &other ) = default; - -protected: - ColorSpaceSig( guint32 value ) : FourCCSig(value) {}; -}; - -class ColorProfileClassSig : public FourCCSig { -public: - ColorProfileClassSig( ColorProfileClassSig const &other ) = default; - -protected: - ColorProfileClassSig( guint32 value ) : FourCCSig(value) {}; -}; - -} // namespace Inkscape - - -#endif // SEEN_CMS_COLOR_TYPES_H - -/* - 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 : diff --git a/src/color/cms-system.cpp b/src/color/cms-system.cpp deleted file mode 100644 index 2dfb064976d2f7f3a29d55f1354e97689bcdb998..0000000000000000000000000000000000000000 --- a/src/color/cms-system.cpp +++ /dev/null @@ -1,470 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file - * A class to provide access to system/user ICC color profiles. - *//* - * Authors: see git history - * - * Copyright (C) 2018 Authors - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ -#ifdef HAVE_CONFIG_H -#include "config.h" // only include where actually required! -#endif - -#include "cms-system.h" - -#include - -#include - -#include "cms-util.h" -#include "document.h" -#include "preferences.h" - -#include "io/resource.h" -#include "object/color-profile.h" - -#ifdef _WIN32 -#undef NOGDI -#include -#include -#endif - -using Inkscape::ColorProfile; - -namespace Inkscape { - -/** - * Holds information about one ICC profile. - */ -class ProfileInfo -{ -public: - ProfileInfo(cmsHPROFILE prof, Glib::ustring &&path); - - Glib::ustring const &getName() const { return _name; } - Glib::ustring const &getPath() const { return _path; } - cmsColorSpaceSignature getSpace() const { return _profileSpace; } - cmsProfileClassSignature getClass() const { return _profileClass; } - -private: - Glib::ustring _path; - Glib::ustring _name; - cmsColorSpaceSignature _profileSpace; - cmsProfileClassSignature _profileClass; -}; - -ProfileInfo::ProfileInfo(cmsHPROFILE prof, Glib::ustring &&path) - : _path(std::move(path)) - , _name(get_color_profile_name(prof)) - , _profileSpace(cmsGetColorSpace(prof)) - , _profileClass(cmsGetDeviceClass(prof)) -{ -} - -CMSSystem::CMSSystem() -{ - // Read in profiles (move to refresh()?). - load_profiles(); - - // Create generic sRGB profile. - sRGB_profile = cmsCreate_sRGBProfile(); -} - -CMSSystem::~CMSSystem() -{ - if (current_monitor_profile) { - cmsCloseProfile(current_monitor_profile); - } - - if (current_proof_profile) { - cmsCloseProfile(current_proof_profile); - } - - if (sRGB_profile) { - cmsCloseProfile(sRGB_profile); - } -} - -/* - * We track last transform created so we can delete it later. - * - * This is OK since we only have one transform for all montiors/canvases. If we choose to allow the - * user to assign different profiles to different monitors or have CMS preferences that are not - * global, we'll need to have either one transform per monitor or one transform per canvas. - */ - -// Search for system ICC profile files and add them to list. -void CMSSystem::load_profiles() -{ - system_profile_infos.clear(); // Allows us to refresh list if necessary. - - // Get list of all possible file directories, with flag if they are "home" directories or not. - auto directory_paths = get_directory_paths(); - - // Look for icc files in specified directories. - for (auto const &directory_path : directory_paths) { - using Inkscape::IO::Resource::get_filenames; - for (auto &&filename : get_filenames(directory_path.first, {".icc", ".icm"})) { - // Check if files are ICC files and extract out basic information, add to list. - if (!is_icc_file(filename)) { - std::cerr << "CMSSystem::load_profiles: " << filename << " is not an ICC file!" << std::endl; - continue; - } - - cmsHPROFILE profile = cmsOpenProfileFromFile(filename.c_str(), "r"); - if (!profile) { - std::cerr << "CMSSystem::load_profiles: failed to load " << filename << std::endl; - continue; - } - - ICCProfileInfo info{profile, std::move(filename), directory_path.second}; - cmsCloseProfile(profile); - profile = nullptr; - - bool same_name = false; - for (auto const &profile_info : system_profile_infos) { - if (profile_info.get_name() == info.get_name() ) { - same_name = true; - std::cerr << "CMSSystem::load_profiles: ICC profile with duplicate name: " << profile_info.get_name() << ":" << std::endl; - std::cerr << " " << profile_info.get_path() << std::endl; - std::cerr << " " << info.get_path() << std::endl; - break; - } - } - - if (!same_name) { - system_profile_infos.emplace_back(std::move(info)); - } - } - } -} - -// Create list of all directories where ICC profiles are expected to be found. -std::vector> CMSSystem::get_directory_paths() -{ - std::vector> paths; - - // First try user's local directory. - paths.emplace_back(Glib::build_filename(Glib::get_user_data_dir(), "color", "icc"), true); - - // See https://github.com/hughsie/colord/blob/fe10f76536bb27614ced04e0ff944dc6fb4625c0/lib/colord/cd-icc-store.c#L590 - - // User store - paths.emplace_back(Glib::build_filename(Glib::get_user_data_dir(), "icc"), true); - - paths.emplace_back(Glib::build_filename(Glib::get_home_dir(), ".color", "icc"), true); - - // System store - paths.emplace_back("/var/lib/color/icc", false); - paths.emplace_back("/var/lib/colord/icc", false); - - auto data_directories = Glib::get_system_data_dirs(); - for (auto const &data_directory : data_directories) { - paths.emplace_back(Glib::build_filename(data_directory, "color", "icc"), false); - } - -#ifdef __APPLE__ - paths.emplace_back("/System/Library/ColorSync/Profiles", false); - paths.emplace_back("/Library/ColorSync/Profiles", false); - - paths.emplace_back(Glib::build_filename(Glib::get_home_dir(), "Library", "ColorSync", "Profiles"), true); -#endif // __APPLE__ - -#ifdef _WIN32 - wchar_t pathBuf[MAX_PATH + 1]; - pathBuf[0] = 0; - DWORD pathSize = sizeof(pathBuf); - g_assert(sizeof(wchar_t) == sizeof(gunichar2)); - if (GetColorDirectoryW(NULL, pathBuf, &pathSize)) { - auto utf8Path = g_utf16_to_utf8((gunichar2*)(&pathBuf[0]), -1, NULL, NULL, NULL); - if (!g_utf8_validate(utf8Path, -1, NULL)) { - g_warning( "GetColorDirectoryW() resulted in invalid UTF-8" ); - } else { - paths.emplace_back(utf8Path, false); - } - g_free(utf8Path); - } -#endif // _WIN32 - - return paths; -} - -// Get the user set monitor profile. -cmsHPROFILE CMSSystem::get_monitor_profile() -{ - static Glib::ustring current_monitor_uri; - static bool use_user_monitor_profile_old = false; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - bool use_user_monitor_profile = prefs->getBool("/options/displayprofile/use_user_profile", false); - - if (use_user_monitor_profile_old != use_user_monitor_profile) { - use_user_monitor_profile_old = use_user_monitor_profile; - current_monitor_profile_changed = true; - } - - if (!use_user_monitor_profile) { - if (current_monitor_profile) { - cmsCloseProfile(current_monitor_profile); - current_monitor_profile = nullptr; - current_monitor_uri.clear(); - } - return current_monitor_profile; - } - - Glib::ustring new_uri = prefs->getString("/options/displayprofile/uri"); - - if (!new_uri.empty()) { - - // User defined monitor profile. - if (new_uri != current_monitor_uri) { - - // Monitor profile changed - current_monitor_profile_changed = true; - current_monitor_uri.clear(); - - // Delete old profile - if (current_monitor_profile) { - cmsCloseProfile(current_monitor_profile); - } - - // Open new profile - current_monitor_profile = cmsOpenProfileFromFile(new_uri.data(), "r"); - if (current_monitor_profile) { - - // A display profile must be of the right type. - cmsColorSpaceSignature space = cmsGetColorSpace(current_monitor_profile); - cmsProfileClassSignature profClass = cmsGetDeviceClass(current_monitor_profile); - - if (profClass != cmsSigDisplayClass) { - std::cerr << "CMSSystem::get_monitor_profile: Not a display (monitor) profile: " << new_uri << std::endl; - cmsCloseProfile(current_monitor_profile); - current_monitor_profile = nullptr; - } else if (space != cmsSigRgbData) { - std::cerr << "CMSSystem::get_monitor_profile: Not an RGB profile: " << new_uri << std::endl; - cmsCloseProfile(current_monitor_profile); - current_monitor_profile = nullptr; - } else { - current_monitor_uri = new_uri; - } - } - } - } else if (current_monitor_profile) { - cmsCloseProfile(current_monitor_profile); - current_monitor_profile = nullptr; - current_monitor_uri.clear(); - current_monitor_profile_changed = true; - } - - return current_monitor_profile; -} - -// Get the user set proof profile. -cmsHPROFILE CMSSystem::get_proof_profile() -{ - static Glib::ustring current_proof_uri; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - Glib::ustring new_uri = prefs->getString("/options/softproof/uri"); - - if (!new_uri.empty()) { - - // User defined proof profile. - if (new_uri != current_proof_uri) { - - // Proof profile changed - current_proof_profile_changed = true; - current_proof_uri.clear(); - - // Delete old profile - if (current_proof_profile) { - cmsCloseProfile(current_proof_profile); - } - - // Open new profile - current_proof_profile = cmsOpenProfileFromFile(new_uri.data(), "r"); - if (current_proof_profile) { - - // We don't check validity of proof profile! - current_proof_uri = new_uri; - } - } - } else if (current_proof_profile) { - cmsCloseProfile(current_proof_profile); - current_proof_profile = nullptr; - current_proof_uri.clear(); - current_proof_profile_changed = true; - } - - return current_proof_profile; -} - -// Get a color profile handle corresponding to "name" from the document. Also, optionally, get intent. -cmsHPROFILE CMSSystem::get_document_profile(SPDocument *document, unsigned *intent, char const *name) -{ - cmsHPROFILE profile_handle = nullptr; - - // Search through elements for one with matching name. - ColorProfile *color_profile = nullptr; - auto color_profiles = document->getResourceList("iccprofile"); - for (auto *object : color_profiles) { - if (auto color_profile_test = cast(object)) { - if (color_profile_test->name && (strcmp(color_profile_test->name, name) == 0)) { - color_profile = color_profile_test; - } - } - } - - // If found, set profile_handle pointer. - if (color_profile) { - profile_handle = color_profile->getHandle(); - } - - // If requested, fill "RENDERING_INTENT" value. - if (intent) { - *intent = color_profile ? color_profile->rendering_intent : (guint)RENDERING_INTENT_UNKNOWN; - } - - return profile_handle; -} - -// Returns vector of names to list in Preferences dialog: display (monitor) profiles. -std::vector CMSSystem::get_monitor_profile_names() const -{ - std::vector result; - - for (auto const &profile_info : system_profile_infos) { - if (profile_info.get_profileclass() == cmsSigDisplayClass && - profile_info.get_colorspace() == cmsSigRgbData) - { - result.emplace_back(profile_info.get_name()); - } - } - std::sort(result.begin(), result.end()); - - return result; -} - -// Returns vector of names to list in Preferences dialog: proofing profiles. -std::vector CMSSystem::get_softproof_profile_names() const -{ - std::vector result; - - for (auto const &profile_info : system_profile_infos) { - if (profile_info.get_profileclass() == cmsSigOutputClass) { - result.emplace_back(profile_info.get_name()); - } - } - std::sort(result.begin(), result.end()); - - return result; -} - -// Returns location of a profile. -std::string CMSSystem::get_path_for_profile(Glib::ustring const &name) const -{ - std::string result; - - for (auto const &profile_info : system_profile_infos) { - if (name.raw() == profile_info.get_name()) { - result = profile_info.get_path(); - break; - } - } - - return result; -} - -// Static, doesn't rely on class. Simply calls lcms' cmsDoTransform. -// Called from Canvas and icc_color_to_sRGB in sgv-color.cpp. -void CMSSystem::do_transform(cmsHTRANSFORM transform, unsigned char *inBuf, unsigned char *outBuf, unsigned size) -{ - cmsDoTransform(transform, inBuf, outBuf, size); -} - -// Called by Canvas to obtain transform. -// Currently there is one transform for all monitors. -// Transform immutably shared between CMSSystem and Canvas. -std::shared_ptr const &CMSSystem::get_cms_transform() -{ - bool preferences_changed = false; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - bool warn = prefs->getBool( "/options/softproof/gamutwarn"); - int intent = prefs->getIntLimited("/options/displayprofile/intent", 0, 0, 3); - int proofIntent = prefs->getIntLimited("/options/softproof/intent", 0, 0, 3); - bool bpc = prefs->getBool( "/options/softproof/bpc"); - Glib::ustring colorStr = prefs->getString( "/options/softproof/gamutcolor"); - Gdk::RGBA gamutColor(colorStr.empty() ? "#808080" : colorStr); - - if (gamutWarn != warn || - lastIntent != intent || - lastProofIntent != proofIntent || - lastBPC != bpc || - lastGamutColor != gamutColor ) - { - preferences_changed = true; - - gamutWarn = warn; - lastIntent = intent; - lastProofIntent = proofIntent; - lastBPC = bpc; - lastGamutColor = gamutColor; - } - - auto monitor_profile = get_monitor_profile(); - auto proof_profile = get_proof_profile(); - - bool need_to_update = preferences_changed || current_monitor_profile_changed || current_proof_profile_changed; - - if (need_to_update) { - if (proof_profile) { - cmsUInt32Number dwFlags = cmsFLAGS_SOFTPROOFING; - - if (gamutWarn) { - dwFlags |= cmsFLAGS_GAMUTCHECK; - - auto gamutColor_r = gamutColor.get_red_u(); - auto gamutColor_g = gamutColor.get_green_u(); - auto gamutColor_b = gamutColor.get_blue_u(); - - cmsUInt16Number newAlarmCodes[cmsMAXCHANNELS] = {0}; - newAlarmCodes[0] = gamutColor_r; - newAlarmCodes[1] = gamutColor_g; - newAlarmCodes[2] = gamutColor_b; - newAlarmCodes[3] = ~0; - cmsSetAlarmCodes(newAlarmCodes); - } - - if (bpc) { - dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION; - } - - current_transform = CMSTransform::create( - cmsCreateProofingTransform(sRGB_profile, TYPE_BGRA_8, monitor_profile, TYPE_BGRA_8, - proof_profile, intent, proofIntent, dwFlags)); - - } else if (monitor_profile) { - current_transform = CMSTransform::create( - cmsCreateTransform(sRGB_profile, TYPE_BGRA_8, monitor_profile, TYPE_BGRA_8, intent, 0)); - } - } - - return current_transform; -} - -CMSSystem *CMSSystem::_instance = nullptr; - -} // namespace Inkscape - -/* - 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 : diff --git a/src/color/cms-system.h b/src/color/cms-system.h deleted file mode 100644 index a040146eca851417dbb7455d10cfff8fe75e9027..0000000000000000000000000000000000000000 --- a/src/color/cms-system.h +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file - * TODO: insert short description here - *//* - * Authors: see git history - * - * Copyright (C) 2017 Authors - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ -#ifndef INKSCAPE_COLOR_CMS_SYSTEM_H -#define INKSCAPE_COLOR_CMS_SYSTEM_H - -/** \file - * Access to ICC profiles provided by system. - * Track which profile to use for proofing.(?) - * Track which profile to use on which monitor. - */ - -#include -#include -#include - -#include -#include - -#include // cmsHTRANSFORM - -#include "cms-color-types.h" // cmsColorSpaceSignature, cmsProfileClassSignature -#include "cms-util.h" - -class SPDocument; -class ProfileInfo; - -namespace Inkscape { - -class ColorProfile; - -class CMSTransform -{ -public: - explicit CMSTransform(cmsHTRANSFORM handle) : _handle(handle) { assert(_handle); } - CMSTransform(CMSTransform const &) = delete; - CMSTransform &operator=(CMSTransform const &) = delete; - ~CMSTransform() { cmsDeleteTransform(_handle); } - - cmsHTRANSFORM getHandle() const { return _handle; } - - static std::shared_ptr create(cmsHTRANSFORM handle) - { - return handle ? std::make_shared(handle) : nullptr; - } - -private: - cmsHTRANSFORM _handle; -}; - -class CMSSystem -{ -public: - /** - * Access the singleton CMSSystem object. - */ - static CMSSystem *get() { - if (!_instance) { - _instance = new CMSSystem(); - } - return _instance; - } - - static void unload() { - if (_instance) { - delete _instance; - _instance = nullptr; - } - } - - static std::vector> get_directory_paths(); - std::vector const &get_system_profile_infos() const { return system_profile_infos; } - std::vector get_monitor_profile_names() const; - std::vector get_softproof_profile_names() const; - std::string get_path_for_profile(Glib::ustring const &name) const; - std::shared_ptr const &get_cms_transform(); - static cmsHPROFILE get_document_profile(SPDocument *document, unsigned *intent, char const *name); - - static void do_transform(cmsHTRANSFORM transform, unsigned char *inBuf, unsigned char *outBuf, unsigned size); - -private: - CMSSystem(); - ~CMSSystem(); - - void load_profiles(); // Should this be public (e.g., if a new ColorProfile is created). - cmsHPROFILE get_monitor_profile(); // Get the user set monitor profile. - cmsHPROFILE get_proof_profile(); // Get the user set proof profile. - void clear_transform(); // Clears current_transform. - - static CMSSystem* _instance; - - // List of ICC profiles on system - std::vector system_profile_infos; - - // We track last transform settings. If there is a change, we delete create new transform. - bool gamutWarn = false; - Gdk::RGBA lastGamutColor = Gdk::RGBA("#808080"); - bool lastBPC = false; - int lastIntent = INTENT_PERCEPTUAL; - int lastProofIntent = INTENT_PERCEPTUAL; - bool current_monitor_profile_changed = true; // Force at least one update. - bool current_proof_profile_changed = true; - - // Shared immutably with all canvases. - std::shared_ptr current_transform; - - // So we can delete them later. - cmsHPROFILE current_monitor_profile = nullptr; - cmsHPROFILE current_proof_profile = nullptr; - cmsHPROFILE sRGB_profile = nullptr; // Genric sRGB profile, find it once on inititialization. -}; - -} // namespace Inkscape - -#endif // INKSCAPE_COLOR_CMS_SYSTEM_H - -/* - 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 : diff --git a/src/color/cms-util.cpp b/src/color/cms-util.cpp deleted file mode 100644 index 36546820b3aa366094deb53a1c86bb11552bec43..0000000000000000000000000000000000000000 --- a/src/color/cms-util.cpp +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file - * Utilities to for working with ICC profiles. Used by CMSSystem and ColorProfile classes. - *//* - * Authors: see git history - * - * Copyright (C) 2018 Authors - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ - -#include -#include // Debug output -#include -#include // File open flags -#include // Posix read, close. - -#include -#include - -#include "cms-util.h" - -namespace Inkscape { - -ICCProfileInfo::ICCProfileInfo(cmsHPROFILE profile, std::string &&path, bool in_home) - : _path(std::move(path)) - , _in_home(in_home) -{ - assert(profile); - - _name = get_color_profile_name(profile); - _colorspace = cmsGetColorSpace(profile); - _profileclass = cmsGetDeviceClass(profile); -} - -bool is_icc_file(std::string const &filepath) -{ - bool is_icc_file = false; - GStatBuf st; - if (g_stat(filepath.c_str(), &st) == 0 && st.st_size > 128) { - // 0-3 == size - // 36-39 == 'acsp' 0x61637370 - int fd = g_open(filepath.c_str(), O_RDONLY, S_IRWXU); - if (fd != -1) { - guchar scratch[40] = {0}; - size_t len = sizeof(scratch); - - ssize_t got = read(fd, scratch, len); - if (got != -1) { - size_t calcSize = - (scratch[0] << 24) | - (scratch[1] << 16) | - (scratch[2] << 8) | - (scratch[3]); - if ( calcSize > 128 && calcSize <= static_cast(st.st_size) ) { - is_icc_file = - (scratch[36] == 'a') && - (scratch[37] == 'c') && - (scratch[38] == 's') && - (scratch[39] == 'p'); - } - } - close(fd); - - if (is_icc_file) { - cmsHPROFILE profile = cmsOpenProfileFromFile(filepath.c_str(), "r"); - if (profile) { - cmsProfileClassSignature profClass = cmsGetDeviceClass(profile); - if (profClass == cmsSigNamedColorClass) { - is_icc_file = false; // Ignore named color profiles for now. - } - cmsCloseProfile(profile); - } - } - } - } - - return is_icc_file; -} - -std::string get_color_profile_name(cmsHPROFILE profile) -{ - std::string name; - - if (profile) { - cmsUInt32Number byteLen = cmsGetProfileInfoASCII(profile, cmsInfoDescription, "en", "US", nullptr, 0); - if (byteLen > 0) { - std::vector data(byteLen); - cmsUInt32Number readLen = cmsGetProfileInfoASCII(profile, cmsInfoDescription, - "en", "US", - data.data(), data.size()); - if (readLen < data.size()) { - std::cerr << "get_color_profile_name(): read less than expected!" << std::endl; - data.resize(readLen); - } - - // Remove nulls at end which will cause an invalid utf8 string. - while (!data.empty() && data.back() == 0) { - data.pop_back(); - } - - name = std::string(data.begin(), data.end()); - } - - if (name.empty()) { - name = _("(Unnamed)"); - } - } - - return name; -} - -} // namespace Inkscape - -/* - 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 : diff --git a/src/color/cms-util.h b/src/color/cms-util.h deleted file mode 100644 index e9262d9fa6842deda3b9a201cd830e06ca2fdd1f..0000000000000000000000000000000000000000 --- a/src/color/cms-util.h +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file - * Utilities to for working with ICC profiles. Used by CMSSystem and ColorProfile classes. - *//* - * Authors: see git history - * - * Copyright (C) 2018 Authors - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ - -#ifndef INKSCAPE_COLOR_CMS_UTIL_H -#define INKSCAPE_COLOR_CMS_UTIL_H - -#include - -#include - -namespace Inkscape { - -// Helper class to store info -class ICCProfileInfo -{ -public: - ICCProfileInfo(cmsHPROFILE profile, std::string &&path, bool in_home); - bool operator<(ICCProfileInfo const &other) const; - std::string const &get_path() const { return _path; } - std::string const &get_name() const { return _name; } - cmsColorSpaceSignature get_colorspace() const { return _colorspace; } - cmsProfileClassSignature get_profileclass() const { return _profileclass; } - bool in_home() const { return _in_home; } - -private: - std::string _path; - std::string _name; - bool _in_home; - cmsColorSpaceSignature _colorspace; - cmsProfileClassSignature _profileclass; -}; - -bool is_icc_file(std::string const &filepath); -std::string get_color_profile_name(cmsHPROFILE profile); // Read as ASCII from profile. - -} // namespace Inkscape - -#endif // INKSCAPE_COLOR_CMS_UTIL_H - -/* - 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 : diff --git a/src/color/cmyk-conv.cpp b/src/color/cmyk-conv.cpp deleted file mode 100644 index e565bd37f9ed19d8329ab2055f024d9a8103fb12..0000000000000000000000000000000000000000 --- a/src/color/cmyk-conv.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** - * CMYK to sRGB conversion routines - * - * Author: - * Michael Kowalski - * - * Copyright (C) 2023 Michael Kowalski - * - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ - -#include "cmyk-conv.h" -#include -#include -#include - -namespace Inkscape { - -CmykConverter::CmykConverter(cmsHPROFILE profile, int intent) { - _intent = intent; - auto color_space = cmsGetColorSpace(profile); - if (color_space != cmsSigCmykData && color_space != cmsSigCmyData) { - g_warning("Select CMYK ICC profile to convert CMYK to sRGB"); - return; - } - //todo - if (cmsIsIntentSupported(_profile, intent, LCMS_USED_AS_OUTPUT)) { - } - _profile = profile; - _srgb = cmsCreate_sRGBProfile(); - auto fmt = color_space == cmsSigCmykData ? TYPE_CMYK_16 : TYPE_CMY_16; - _cmy = color_space == cmsSigCmyData; - _transform = cmsCreateTransform(profile, fmt, _srgb, TYPE_RGBA_8, intent, 0); -} - -CmykConverter::~CmykConverter() { - if (_transform) cmsDeleteTransform(_transform); - // _srgb is virtual and probably not something that can be freed -} - -// Simple CMYK to sRGB conversion using interpolation from plain cyan, magenta and yellow colors -std::array simple_cmyk_to_rgb(float c, float m, float y, float k) { - auto invlerp = [](float f1, float p) { - // CSS Color module 5 allows for a wide range of CMYK values, but clamps them to 0%-100% - p = std::clamp(p, 0.0f, 100.0f); - f1 = (255 - f1) / 255.0f; - return 1.0f - (f1 * p / 100.0f); - }; - - // interpolate cyan - auto cr = invlerp(0x00, c); - auto cg = invlerp(0xa4, c); - auto cb = invlerp(0xdb, c); - // magenta - auto mr = invlerp(0xd7, m); - auto mg = invlerp(0x15, m); - auto mb = invlerp(0x7e, m); - // and yellow - auto yr = invlerp(0xff, y); - auto yg = invlerp(0xf1, y); - auto yb = invlerp(0x08, y); - // and combine them - auto bk = 1 - k / 100.0f; - uint8_t r = (cr * mr * yr) * bk * 255; - uint8_t g = (cg * mg * yg) * bk * 255; - uint8_t b = (cb * mb * yb) * bk * 255; - return {r, g, b}; -} - -std::array CmykConverter::cmyk_to_rgb(float c, float m, float y, float k) { - if (_profile) { - cmsUInt16Number tmp[4] = { cmsUInt16Number(c / 100.0f * 0xffff), cmsUInt16Number(m / 100.0f * 0xffff), cmsUInt16Number(y / 100.0f * 0xffff), cmsUInt16Number(k / 100.0f * 0xffff) }; - - uint8_t post[4] = { 0, 0, 0, 0 }; - cmsDoTransform(_transform, tmp, post, 1); - - if (_cmy && k > 0) { - // if profile cannot transform black, then this is only a crude approximation - auto black = 1 - k / 100.0f; - post[0] *= black; - post[1] *= black; - post[2] *= black; - } - return { post[0], post[1], post[2] }; - } - else { - // no ICC profile available - return simple_cmyk_to_rgb(c, m, y, k); - } -} - -} // namespace diff --git a/src/color/cmyk-conv.h b/src/color/cmyk-conv.h deleted file mode 100644 index 524c9bddf238a12979ff7de004d65cd694f021ff..0000000000000000000000000000000000000000 --- a/src/color/cmyk-conv.h +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -#ifndef INKSCAPE_CMYK_H -#define INKSCAPE_CMYK_H - -#include -#include -#include "color/cms-color-types.h" - -namespace Inkscape { - -// Convert CMYK to sRGB -class CmykConverter { -public: - // Conversion using ICC profile gives the best results and should always be used - // whenever there is a profile selected/available - CmykConverter(cmsHPROFILE profile, int intent = INTENT_PERCEPTUAL); - - // Simple (but not simplistic) CMYK to sRGB conversion to show approximately what - // CMYK colors may look like on an sRGB device (without ICC profile) - CmykConverter() {} - - ~CmykConverter(); - - // if profile has been selected and can decode cmy/k, then return true - bool profile_used() const { return _profile != nullptr; } - - // CMYK channels from 0 to 100 (percentage) to sRGB (channels 0..255) - std::array cmyk_to_rgb(float c, float m, float y, float k); - -private: - cmsHPROFILE _profile = nullptr; - cmsHTRANSFORM _transform = nullptr; - cmsHPROFILE _srgb = nullptr; - bool _cmy = false; - int _intent = 0; -}; - -} // namespace - -#endif diff --git a/src/color/color-conv.cpp b/src/color/color-conv.cpp deleted file mode 100644 index 8ce3bcbac4b2cf93fefe7cc34e21377783868348..0000000000000000000000000000000000000000 --- a/src/color/color-conv.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include - -#include "color-conv.h" - -namespace Inkscape { -namespace Util { - -std::string rgba_color_to_string(unsigned int rgba) { - std::ostringstream ost; - ost << "#" << std::setfill ('0') << std::setw(8) << std::hex << rgba; - return ost.str(); -} - -std::optional string_to_rgba_color(const char* str) { - if (!str || *str != '#') { - return std::optional(); - } - try { - return static_cast(std::stoul(str + 1, nullptr, 16)); - } - catch (...) { - return std::optional(); - } -} - -} -} \ No newline at end of file diff --git a/src/color/color-conv.h b/src/color/color-conv.h deleted file mode 100644 index 0b87704c6d2d1f1864c56caae70e9b02e9cdc584..0000000000000000000000000000000000000000 --- a/src/color/color-conv.h +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2022 Authors - * - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ -#ifndef INKSCAPE_UTIL_COLOR_CONV_H -#define INKSCAPE_UTIL_COLOR_CONV_H - -#include -#include - -namespace Inkscape { -namespace Util { - -// Convert RGBA color to '#rrggbbaa' hex string -std::string rgba_color_to_string(unsigned int rgba); - -// Parse hex string '#rrgbbaa' and return RGBA color -std::optional string_to_rgba_color(const char* str); - -} } // namespace - -#endif diff --git a/src/color/color-profile-cms-fns.h b/src/color/color-profile-cms-fns.h deleted file mode 100644 index 06258c73e13f312c172ad3ca867dca07e9e9e746..0000000000000000000000000000000000000000 --- a/src/color/color-profile-cms-fns.h +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file - * TODO: insert short description here - *//* - * Authors: see git history - * - * Copyright (C) 2012 Authors - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ -#ifndef SEEN_COLOR_PROFILE_CMS_FNS_H -#define SEEN_COLOR_PROFILE_CMS_FNS_H - -#ifdef HAVE_CONFIG_H -# include "config.h" // only include where actually required! -#endif - -#include - -#include "cms-color-types.h" - -namespace Inkscape { - -// Note: these can later be adjusted to adapt for lcms2: - -class ColorSpaceSigWrapper : public ColorSpaceSig { -public : - ColorSpaceSigWrapper( cmsColorSpaceSignature sig ) : ColorSpaceSig( static_cast(sig) ) {} - ColorSpaceSigWrapper( ColorSpaceSig const &other ) : ColorSpaceSig( other ) {} - - operator cmsColorSpaceSignature() const { return static_cast(value); } -}; - -class ColorProfileClassSigWrapper : public ColorProfileClassSig { -public : - ColorProfileClassSigWrapper( cmsProfileClassSignature sig ) : ColorProfileClassSig( static_cast(sig) ) {} - ColorProfileClassSigWrapper( ColorProfileClassSig const &other ) : ColorProfileClassSig( other ) {} - - operator cmsProfileClassSignature() const { return static_cast(value); } -}; - -cmsColorSpaceSignature asICColorSpaceSig(ColorSpaceSig const & sig); -cmsProfileClassSignature asICColorProfileClassSig(ColorProfileClassSig const & sig); - -} // namespace Inkscape - - -#endif // !SEEN_COLOR_PROFILE_CMS_FNS_H - -/* - 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 : diff --git a/src/colors/CMakeLists.txt b/src/colors/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..97dc58f39ffba94983cb2e8917a7f20c1b83bc12 --- /dev/null +++ b/src/colors/CMakeLists.txt @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +set(colors_SRC + cms/profile.cpp + cms/system.cpp + cms/transform.cpp + color.cpp + document-cms.cpp + dragndrop.cpp + manager.cpp + parser.cpp + printer.cpp + color-set.cpp + spaces/base.cpp + spaces/cms.cpp + spaces/cmyk.cpp + spaces/components.cpp + spaces/gray.cpp + spaces/hsl.cpp + spaces/hsluv.cpp + spaces/hsv.cpp + spaces/lab.cpp + spaces/lch.cpp + spaces/linear-rgb.cpp + spaces/luv.cpp + spaces/okhsl.cpp + spaces/oklab.cpp + spaces/oklch.cpp + spaces/named.cpp + spaces/rgb.cpp + spaces/xyz.cpp + utils.cpp + xml-color.cpp + + # ------- + # Headers + cms/profile.h + cms/system.h + cms/transform.h + color.h + document-cms.h + dragndrop.h + manager.h + parser.h + printer.h + color-set.h + spaces/base.h + spaces/cms.h + spaces/cmyk.h + spaces/components.h + spaces/enum.h + spaces/gray.h + spaces/hsl.h + spaces/hsluv.h + spaces/hsv.h + spaces/lab.h + spaces/lch.h + spaces/linear-rgb.h + spaces/luv.h + spaces/okhsl.h + spaces/oklab.h + spaces/oklch.h + spaces/named.h + spaces/rgb.h + spaces/xyz.h + utils.h + xml-color.h +) + +add_inkscape_source("${colors_SRC}") diff --git a/src/colors/cms/profile.cpp b/src/colors/cms/profile.cpp new file mode 100644 index 0000000000000000000000000000000000000000..41a374d477adf15f036ec4d645176dfc04005a27 --- /dev/null +++ b/src/colors/cms/profile.cpp @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * A C++ wrapper for lcms2 profiles + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "profile.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "system.h" + +namespace Inkscape::Colors::CMS { + +/** + * Construct a color profile object from the lcms2 object. + */ +std::shared_ptr Profile::create(cmsHPROFILE handle, std::string path, bool in_home) +{ + return handle ? std::make_shared(handle, std::move(path), in_home) : nullptr; +} + +/** + * Construct a color profile object from a uri. Ownership of the lcms2 object is contained + * within the Profile object and will be destroyed when it is. + */ +std::shared_ptr Profile::create_from_uri(std::string path, bool in_home) +{ + if (cmsHPROFILE profile = cmsOpenProfileFromFile(path.c_str(), "r")) + return Profile::create(profile, std::move(path), in_home); + return nullptr; +} + +/** + * Construct a color profile object from the raw data. + */ +std::shared_ptr Profile::create_from_data(std::string const &contents) +{ + if (cmsHPROFILE profile = cmsOpenProfileFromMem(contents.data(), contents.size())) + return Profile::create(profile, "", false); + return nullptr; +} + +/** + * Construct the default lcms sRGB color profile and return. + */ +std::shared_ptr Profile::create_srgb() +{ + return Profile::create(cmsCreate_sRGBProfile()); +} + +Profile::Profile(cmsHPROFILE handle, std::string path, bool in_home) + : _handle(handle) + , _path(std::move(path)) + , _id(generate_id()) + , _checksum(generate_checksum()) + , _in_home(in_home) +{ + assert(_handle); +} + +/** + * Return true if this profile is for display/monitor correction. + */ +bool Profile::isForDisplay() const +{ + // If the profile has a Video Card Gamma Table (VCGT), then it's very likely to + // be an actual monitor/display icc profile, and not just a display RGB profile. + return getProfileClass() == cmsSigDisplayClass && getColorSpace() == cmsSigRgbData && + cmsIsTag(_handle, cmsSigVcgtTag); +} + +/** + * Cleans up name to remove disallowed characters. + * + * Allowed ASCII first characters: ':', 'A'-'Z', '_', 'a'-'z' + * Allowed ASCII remaining chars add: '-', '.', '0'-'9', + * + * @param str the string to clean up. + */ +static void sanitize_name(std::string &str) +{ + if (str.empty()) + return; + auto val = str[0]; + if ((val < 'A' || val > 'Z') && (val < 'a' || val > 'z') && val != '_' && val != ':') { + str.insert(0, "_"); + } + for (std::size_t i = 1; i < str.size(); i++) { + auto val = str[i]; + if ((val < 'A' || val > 'Z') && (val < 'a' || val > 'z') && (val < '0' || val > '9') && val != '_' && + val != ':' && val != '-' && val != '.') { + if (str.at(i - 1) == '-') { + str.erase(i, 1); + i--; + } else { + str[i] = '-'; + } + } + } + if (str.back() == '-') { + str.pop_back(); + } +} + +/** + * Returns the name inside the icc profile, or empty string if it couldn't be + * parsed out of the icc data correctly. + */ +std::string Profile::getName(bool sanitize) const +{ + std::string name; + cmsUInt32Number byteLen = cmsGetProfileInfoASCII(_handle, cmsInfoDescription, "en", "US", nullptr, 0); + if (byteLen > 0) { + std::vector data(byteLen); + cmsUInt32Number readLen = + cmsGetProfileInfoASCII(_handle, cmsInfoDescription, "en", "US", data.data(), data.size()); + if (readLen < data.size()) { + g_warning("Profile::get_name(): icc data read less than expected!"); + data.resize(readLen); + } + // Remove nulls at end which will cause an invalid utf8 string. + while (!data.empty() && data.back() == 0) { + data.pop_back(); + } + name = std::string(data.begin(), data.end()); + } + if (sanitize) + sanitize_name(name); + return name; +} + +cmsColorSpaceSignature Profile::getColorSpace() const +{ + return cmsGetColorSpace(_handle); +} + +cmsProfileClassSignature Profile::getProfileClass() const +{ + return cmsGetDeviceClass(_handle); +} + +struct InputFormatMap +{ + cmsColorSpaceSignature space; + cmsUInt32Number inForm; +}; + +/** + * Returns the number of channels this profile stores for color information. + */ +unsigned int Profile::getSize() const +{ + switch (getColorSpace()) { + case cmsSigGrayData: + return 1; + case cmsSigCmykData: + return 4; + default: + return 3; + } +} + +bool Profile::isIccFile(std::string const &filepath) +{ + bool is_icc_file = false; + GStatBuf st; + if (g_stat(filepath.c_str(), &st) == 0 && st.st_size > 128) { + // 0-3 == size + // 36-39 == 'acsp' 0x61637370 + int fd = g_open(filepath.c_str(), O_RDONLY, S_IRWXU); + if (fd != -1) { + guchar scratch[40] = {0}; + size_t len = sizeof(scratch); + + ssize_t got = read(fd, scratch, len); + if (got != -1) { + size_t calcSize = (scratch[0] << 24) | (scratch[1] << 16) | (scratch[2] << 8) | (scratch[3]); + if (calcSize > 128 && calcSize <= static_cast(st.st_size)) { + is_icc_file = + (scratch[36] == 'a') && (scratch[37] == 'c') && (scratch[38] == 's') && (scratch[39] == 'p'); + } + } + close(fd); + + if (is_icc_file) { + cmsHPROFILE profile = cmsOpenProfileFromFile(filepath.c_str(), "r"); + if (profile) { + cmsProfileClassSignature profClass = cmsGetDeviceClass(profile); + if (profClass == cmsSigNamedColorClass) { + is_icc_file = false; // Ignore named color profiles for now. + } + cmsCloseProfile(profile); + } + } + } + } + return is_icc_file; +} + +/** + * Get or generate a profile Id, and save in the object for later use. + */ +std::string Profile::generate_id() const +{ + // 1. get the id from the cms header itself, ususally correct. + cmsUInt8Number tmp[16]; + cmsGetHeaderProfileID(_handle, tmp); + + std::ostringstream oo; + oo << std::hex << std::setfill('0'); + for (auto &digit : tmp) { + // Setw must happen each loop + oo << std::setw(2) << static_cast(digit); + } +#ifdef __APPLE__ + auto s = oo.str(); + if (std::count_if(s.begin(), s.end(), [](char c){ return c == '0'; }) < 24) +#else + if (std::ranges::count(oo.str(), '0') < 24) +#endif + return oo.str(); // Done + // If there's no path, then what we have is a generated or in-memory profile + // which is unlikely to ever need to be matched with anything via id but it's + // also true that this id would change between computers, and creation date. + if (_path.empty()) + return ""; + return generate_checksum(); +} + +/** + * Generate a checksum of the data according to the ICC specification + */ +std::string Profile::generate_checksum() const +{ + // 2. If the id is empty, for some reason, we're going to generate it + // from the data using the same method that should have been used originally + // See ICC.1-2022-05 7.2.18 Profile ID field. + auto data = dumpData(); + if (data.size() < 100) { + g_warning("Bad icc profile data when generating profile id."); + return "~"; + } + // Zero out the required bytes as per the above specification + for (unsigned i = 64; i < 68; i++) + data[i] = 0; + for (unsigned i = 84; i < 100; i++) + data[i] = 0; + return Glib::Checksum::compute_checksum(Glib::Checksum::Type::MD5, std::string(data.begin(), data.end())); +} + +/** + * Dump the entire profile as a base64 encoded string. This is used for color-profile href data. + */ +std::string Profile::dumpBase64() const +{ + auto buf = dumpData(); + return Glib::Base64::encode(std::string(buf.begin(), buf.end())); +} + +/** + * Dump the entire profile as raw data. Used in dumpBase64 and generateId + */ +std::vector Profile::dumpData() const +{ + cmsUInt32Number len = 0; + if (!cmsSaveProfileToMem(_handle, nullptr, &len)) { + throw CmsError("Can't extract profile data"); + } + auto buf = std::vector(len); + cmsSaveProfileToMem(_handle, &buf.front(), &len); + return buf; +} + +} // namespace Inkscape::Colors::CMS + +/* + 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 : diff --git a/src/colors/cms/profile.h b/src/colors/cms/profile.h new file mode 100644 index 0000000000000000000000000000000000000000..b6b40fd99309f388922c692e2baca5c107c83d53 --- /dev/null +++ b/src/colors/cms/profile.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * + * Authors: see git history + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_COLORS_CMS_PROFILE_H +#define SEEN_COLORS_CMS_PROFILE_H + +#include +#include // cmsHPROFILE +#include +#include +#include + +namespace Inkscape::Colors::CMS { + +class Profile +{ +public: + static std::shared_ptr create(cmsHPROFILE handle, std::string path = "", bool in_home = false); + static std::shared_ptr create_from_uri(std::string path, bool in_home = false); + static std::shared_ptr create_from_data(std::string const &contents); + static std::shared_ptr create_srgb(); + + static bool sortByName(std::shared_ptr const &p1, std::shared_ptr const &p2) + { + return p1->getName() < p2->getName(); + } + static bool sortById(std::shared_ptr const &p1, std::shared_ptr const &p2) + { + return p1->_id < p2->_id; + } + + Profile(cmsHPROFILE handle, std::string path, bool in_home); + ~Profile() { cmsCloseProfile(_handle); } + Profile(Profile const &) = delete; + Profile &operator=(Profile const &) = delete; + bool operator==(Profile const &other) const { return _checksum == other._checksum; } + + cmsHPROFILE getHandle() const { return _handle; } + std::string const &getPath() const { return _path; } + bool inHome() const { return _in_home; } + + bool isForDisplay() const; + const std::string &getId() const { return _id; } + const std::string &getChecksum() const { return _checksum; } + std::string getName(bool sanitize = false) const; + unsigned int getSize() const; + cmsColorSpaceSignature getColorSpace() const; + cmsProfileClassSignature getProfileClass() const; + + static bool isIccFile(std::string const &filepath); + std::string dumpBase64() const; + std::vector dumpData() const; + +private: + cmsHPROFILE _handle; + std::string _path; + std::string _id; + std::string _checksum; + bool _in_home = false; + + std::string generate_id() const; + std::string generate_checksum() const; +}; + +} // namespace Inkscape::Colors::CMS + +#endif // SEEN_COLORS_CMS_PROFILE_H + +/* + 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 : diff --git a/src/colors/cms/system.cpp b/src/colors/cms/system.cpp new file mode 100644 index 0000000000000000000000000000000000000000..75c1cffd61d98d6ef559d8978126dbbb7bb2d6a6 --- /dev/null +++ b/src/colors/cms/system.cpp @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * A class to provide access to system/user ICC color profiles. + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "system.h" + +#include // home-dir and filename building +#include + +#include "io/resource.h" +#include "profile.h" +#include "transform.h" + +// clang-format off +#ifdef _WIN32 +#undef NOGDI +#include +#include +#endif +// clang-format on + +namespace Inkscape::Colors::CMS { + +System::System() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + _prefs_observer = prefs->createObserver("/options/displayprofile", [this]() { + _display_profile.reset(); + _display_transform.reset(); + }); +} + +/** + * Search for system ICC profile files and add them to list. + */ +void System::refreshProfiles() +{ + _profiles.clear(); // Allows us to refresh list if necessary. + + // Get list of all possible file directories, with flag if they are "home" directories or not. + // Look for icc files in specified directories. + for (auto const &directory_path : getDirectoryPaths()) { + using Inkscape::IO::Resource::get_filenames; + for (auto &&filename : get_filenames(directory_path.first, {".icc", ".icm"})) { + // Check if files are ICC files and extract out basic information, add to list. + if (!Profile::isIccFile(filename)) { + g_warning("System::load_profiles: '%s' is not an ICC file!", filename.c_str()); + continue; + } + + auto profile = Profile::create_from_uri(std::move(filename), directory_path.second); + + for (auto const &other : _profiles) { + if (other->getName() == profile->getName() && other->getId() != profile->getId()) { + std::cerr << "System::load_profiles: Different ICC profile with duplicate name: " + << profile->getName() << ":" << std::endl; + std::cerr << " " << profile->getPath() << " (" << profile->getId() << ")" << std::endl; + std::cerr << " " << other->getPath() << " (" << other->getId() << ")" << std::endl; + return; + } + } + _profiles.emplace_back(std::move(profile)); + } + } +} + +static DirPaths get_directory_paths() +{ + DirPaths paths; + + // First try user's local directory. + paths.emplace_back(Glib::build_filename(Glib::get_user_data_dir(), "color", "icc"), true); + + // See + // https://github.com/hughsie/colord/blob/fe10f76536bb27614ced04e0ff944dc6fb4625c0/lib/colord/cd-icc-store.c#L590 + + // User store + paths.emplace_back(Glib::build_filename(Glib::get_user_data_dir(), "icc"), true); + paths.emplace_back(Glib::build_filename(Glib::get_home_dir(), ".color", "icc"), true); + + // System store + paths.emplace_back("/var/lib/color/icc", false); + paths.emplace_back("/var/lib/colord/icc", false); + + auto data_directories = Glib::get_system_data_dirs(); + for (auto const &data_directory : data_directories) { + paths.emplace_back(Glib::build_filename(data_directory, "color", "icc"), false); + } + +#ifdef __APPLE__ + paths.emplace_back("/System/Library/ColorSync/Profiles", false); + paths.emplace_back("/Library/ColorSync/Profiles", false); + + paths.emplace_back(Glib::build_filename(Glib::get_home_dir(), "Library", "ColorSync", "Profiles"), true); +#endif // __APPLE__ + +#ifdef _WIN32 + wchar_t pathBuf[MAX_PATH + 1]; + pathBuf[0] = 0; + DWORD pathSize = sizeof(pathBuf); + g_assert(sizeof(wchar_t) == sizeof(gunichar2)); + if (GetColorDirectoryW(NULL, pathBuf, &pathSize)) { + auto utf8Path = g_utf16_to_utf8((gunichar2 *)(&pathBuf[0]), -1, NULL, NULL, NULL); + if (!g_utf8_validate(utf8Path, -1, NULL)) { + g_warning("GetColorDirectoryW() resulted in invalid UTF-8"); + } else { + paths.emplace_back(utf8Path, false); + } + g_free(utf8Path); + } +#endif // _WIN32 + + return paths; +} + +/** + * Create list of all directories where ICC profiles are expected to be found. + */ +DirPaths const &System::getDirectoryPaths() +{ + if (_paths.empty()) { + _paths = get_directory_paths(); + } + return _paths; +} + +/** + * Remove all directory paths that might have been generated (useful for refreshing) + */ +void System::clearDirectoryPaths() +{ + _paths.clear(); +} + +/** + * Replace all generated profile paths with this single path, useful for testing. + */ +void System::addDirectoryPath(std::string path, bool is_user) +{ + _paths.emplace_back(std::move(path), is_user); +} + +/** + * Returns a list of profiles sorted by their internal names. + */ +std::vector> System::getProfiles() const +{ + auto result = _profiles; // copy + std::sort(result.begin(), result.end(), Profile::sortByName); + return result; +} + +/** + * Get the user set display profile, if set. + */ +const std::shared_ptr &System::getDisplayProfile(bool &updated) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + std::string uri = prefs->getString("/options/displayprofile/uri"); + + if (!uri.empty() && (!_display_profile || _display_profile->getPath() != uri)) { + auto profile = Profile::create_from_uri(uri, false); + if (!profile->isForDisplay()) { + g_warning("System::get_display_profile: Not a display (display) profile: %s", uri.c_str()); + } else { + updated = true; + _display_profile = profile; + } + } + return _display_profile; +} + +/** + * Returns a list of profiles that can apply to the display (display), sorted by their internal names. + */ +std::vector> System::getDisplayProfiles() const +{ + std::vector> result; + result.reserve(_profiles.size()); + + for (auto const &profile : _profiles) { + if (profile->isForDisplay()) { + result.push_back(profile); + } + } + std::sort(result.begin(), result.end(), Profile::sortByName); + return result; +} + +/** + * Return vector of profiles which can be used for cms output + */ +std::vector> System::getOutputProfiles() const +{ + std::vector> result; + result.reserve(_profiles.size()); + + for (auto const &profile : _profiles) { + if (profile->getProfileClass() == cmsSigOutputClass) { + result.push_back(profile); + } + } + std::sort(result.begin(), result.end(), Profile::sortByName); + return result; +} + +/** + * Return the profile object which is matched by the given name, id or path + * + * @arg name - A string that can contain either the profile name as stored in the icc file + * the ID which is a hex value different for each version of the profile also + * stored in the icc file. Or the path where the profile was found. + * @returns A pointer to the profile object, or nullptr + */ +const std::shared_ptr &System::getProfile(std::string const &name) const +{ + for (auto const &profile : _profiles) { + if (name == profile->getName() || name == profile->getId() || name == profile->getPath()) { + return profile; + } + } + static std::shared_ptr not_found; + return not_found; +} + +/** + * Get the color managed trasform for the screen. + * + * There is one transform for all displays, anything more complex and the user should + * use their operating system CMS configurations instead of the Inkscape display cms. + * + * Transform immutably shared between System and Canvas. + */ +const std::shared_ptr &System::getDisplayTransform() +{ + bool need_to_update = false; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool display = prefs->getIntLimited("/options/displayprofile/enabled", false); + int display_intent = prefs->getIntLimited("/options/displayprofile/intent", 0, 0, 3); + + if (_display != display || _display_intent != display_intent) { + need_to_update = true; + _display = display; + _display_intent = display_intent; + } + + auto display_profile = display ? getDisplayProfile(need_to_update) : nullptr; + + if (need_to_update) { + if (display_profile) { + _display_transform = Transform::create_for_cairo(Profile::create_srgb(), display_profile); + } else { + _display_transform = nullptr; + } + } + return _display_transform; +} + +} // namespace Inkscape::Colors::CMS + +/* + 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 : diff --git a/src/colors/cms/system.h b/src/colors/cms/system.h new file mode 100644 index 0000000000000000000000000000000000000000..94379142e0aab2da6a4a5de076a776a508095df3 --- /dev/null +++ b/src/colors/cms/system.h @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Access operating system wide information about color management. + *//* + * Authors: see git history + * + * Copyright (C) 2017 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_COLORS_CMS_SYSTEM_H +#define SEEN_COLORS_CMS_SYSTEM_H + +#include +#include +#include +#include +#include + +#include "preferences.h" + +using DirPaths = std::vector>; + +namespace Inkscape::Colors::CMS { + +class Profile; +class Transform; +class System +{ +private: + System(System const &) = delete; + void operator=(System const &) = delete; + +public: + // Access the singleton CMS::System object. + static System &get() + { + static System instance; + return instance; + } + + DirPaths const &getDirectoryPaths(); + void addDirectoryPath(std::string path, bool is_user); + void clearDirectoryPaths(); + + std::vector> getProfiles() const; + const std::shared_ptr &getProfile(std::string const &name) const; + + std::vector> getDisplayProfiles() const; + const std::shared_ptr &getDisplayProfile(bool &updated); + const std::shared_ptr &getDisplayTransform(); + + std::vector> getOutputProfiles() const; + + void refreshProfiles(); + + System(); + ~System() = default; + + // Used by testing to add profiles when needed + void addProfile(std::shared_ptr profile) { _profiles.emplace_back(profile); } + +private: + // List of ICC profiles found on system + std::vector> _profiles; + + // Paths to search for icc profiles + DirPaths _paths; + + // We track last display transform settings. If there is a change, we delete create new transform. + std::shared_ptr _display_profile; + std::shared_ptr _display_transform; + bool _display = false; + int _display_intent = -1; + + Inkscape::PrefObserver _prefs_observer; +}; + +class CmsError : public std::exception +{ +public: + CmsError(std::string &&msg) + : _msg(msg) + {} + char const *what() const noexcept override { return _msg.c_str(); } + +private: + std::string _msg; +}; + +} // namespace Inkscape::Colors::CMS + +#endif // SEEN_COLORS_CMS_SYSTEM_H + +/* + 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 : diff --git a/src/colors/cms/transform.cpp b/src/colors/cms/transform.cpp new file mode 100644 index 0000000000000000000000000000000000000000..78b670ff898e34f96cd617cb418524ae2ca08711 --- /dev/null +++ b/src/colors/cms/transform.cpp @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * A C++ wrapper for lcms2 transforms + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "transform.h" + +#include +#include +#include +#include + +#include "colors/color.h" +#include "profile.h" + +namespace Inkscape::Colors::CMS { + +/** + * Construct a color transform object from the lcms2 object. + */ +std::shared_ptr const Transform::create(cmsHTRANSFORM handle, bool global) +{ + return handle ? std::make_shared(handle, global) : nullptr; +} + +/** + * Construct a transformation suitable for display conversion in a cairo buffer + * + * @arg from - The RGB CMS Profile the cairo data will start in. + * @arg to - The target RGB CMS Profile the cairo data needs to end up in. + * @arg proof - A profile to apply a proofing step to, this can be CMYK for example. + */ +std::shared_ptr const Transform::create_for_cairo(std::shared_ptr const &from, + std::shared_ptr const &to, + std::shared_ptr const &proof, + RenderingIntent proof_intent, bool with_gamut_warn) +{ + if (!to || !from) + return nullptr; + + auto cms_context = cmsCreateContext(nullptr, nullptr); + + if (proof) { + unsigned int flags = cmsFLAGS_SOFTPROOFING | (with_gamut_warn ? cmsFLAGS_GAMUTCHECK : 0); + unsigned int lt = lcms_intent(proof_intent, flags); + + return create(cmsCreateProofingTransformTHR(cms_context, from->getHandle(), TYPE_BGRA_8, to->getHandle(), + TYPE_BGRA_8, proof->getHandle(), INTENT_PERCEPTUAL, lt, flags)); + } + return create(cmsCreateTransformTHR(cms_context, from->getHandle(), TYPE_BGRA_8, to->getHandle(), TYPE_BGRA_8, + INTENT_PERCEPTUAL, 0)); +} + +/** + * Construct a transformation suitable for Space::CMS transformations using the given rendering intent + * + * @arg from - The CMS Profile the color data will start in + * @arg to - The target CMS Profile the color data needs to end up in. + * @arg intent - The rendering intent to use when changing the gamut and white balance. + */ +std::shared_ptr const Transform::create_for_cms(std::shared_ptr const &from, + std::shared_ptr const &to, RenderingIntent intent) +{ + if (!to || !from) + return nullptr; + unsigned int flags = 0; + unsigned int lt = lcms_intent(intent, flags); + + // Format is 16bit integer in whatever color space it's in. + auto from_format = cmsFormatterForColorspaceOfProfile(from->getHandle(), 2, false); + auto to_format = cmsFormatterForColorspaceOfProfile(to->getHandle(), 2, false); + return create(cmsCreateTransform(from->getHandle(), from_format, to->getHandle(), to_format, lt, flags)); +} + +/** + * Construct a transformation suitable for gamut checking Space::CMS colors. + * + * @arg from - The CMS Profile the color data will start in + * @arg to - The target CMS Profile the color data needs to end up in. + */ +std::shared_ptr const Transform::create_for_cms_checker(std::shared_ptr const &from, + std::shared_ptr const &to) +{ + if (!to || !from) + return nullptr; + + static cmsContext check_context = nullptr; + if (!check_context) { + // Create an lcms context just for checking out of gamut colors, this can live as long as inkscape. + check_context = cmsCreateContext(nullptr, nullptr); + cmsUInt16Number alarmCodes[cmsMAXCHANNELS] = {0, 0, 0, 0, 0}; + cmsSetAlarmCodesTHR(check_context, alarmCodes); + } + // Format is 16bit integer in whatever color space it's in. + auto from_format = cmsFormatterForColorspaceOfProfile(from->getHandle(), 2, false); + return create(cmsCreateProofingTransformTHR(check_context, from->getHandle(), from_format, + from->getHandle(), from_format, to->getHandle(), + INTENT_RELATIVE_COLORIMETRIC, INTENT_RELATIVE_COLORIMETRIC, + cmsFLAGS_GAMUTCHECK | cmsFLAGS_SOFTPROOFING), + true); +} + +/** + * Set the gamut alarm code for this cms transform (and only this one). + * + * NOTE: If the transform doesn't have a context because it was created for cms color + * transforms instead of cairo transforms, this won't do anything. + * + * @arg input - The values per channel in the _output_ to use. For example if the transform + * is RGB to CMYK, the input vector should be four channels in size. + * + */ +void Transform::set_gamut_warn(std::vector const &input) +{ + std::vector color(_channels_in); + for (unsigned int i = 0; i < _channels_in; i++) { + // double to uint16 conversion + color[i] = input.size() > i ? input[i] * 65535 : 0; + } + if (_context) { + cmsSetAlarmCodesTHR(_context, &color.front()); + } +} + +/** + * Wrap lcms2 cmsDoTransform to transform the pixel buffer's color channels. + * + * @arg inBuf - The input pixel buffer to transform. + * @arg outBug - The output pixel buffer, which can be the same as inBuf. + * @arg size - The size of the buffer to transform. + */ +void Transform::do_transform(unsigned char *inBuf, unsigned char *outBuf, unsigned size) const +{ + if ((cmsGetTransformInputFormat(_handle) & TYPE_BGRA_8) != TYPE_BGRA_8 || + (cmsGetTransformOutputFormat(_handle) & TYPE_BGRA_8) != TYPE_BGRA_8) { + throw ColorError("Using a color-channel transform object to do a cairo transform operation!"); + } + + cmsDoTransform(_handle, inBuf, outBuf, size); +} + +/** + * Apply the CMS transform to the cairo surface and paint it into the output surface. + * + * @arg in - The source cairo surface with the pixels to transform. + * @arg out - The destination cairo surface which may be the same as in. + */ +void Transform::do_transform(cairo_surface_t *in, cairo_surface_t *out) const +{ + cairo_surface_flush(in); + + auto px_in = cairo_image_surface_get_data(in); + auto px_out = cairo_image_surface_get_data(out); + + int stride = cairo_image_surface_get_stride(in); + int width = cairo_image_surface_get_width(in); + int height = cairo_image_surface_get_height(in); + + if (stride != cairo_image_surface_get_stride(out) || width != cairo_image_surface_get_width(out) || + height != cairo_image_surface_get_height(out)) { + throw ColorError("Different image formats while applying CMS!"); + } + + for (int i = 0; i < height; i++) { + auto row_in = px_in + i * stride; + auto row_out = px_out + i * stride; + do_transform(row_in, row_out, width); + } + + cairo_surface_mark_dirty(out); +} + +/** + * Apply the CMS transform to the cairomm surface and paint it into the output surface. + * + * @arg in - The source cairomm surface with the pixels to transform. + * @arg out - The destination cairomm surface which may be the same as in. + */ +void Transform::do_transform(Cairo::RefPtr &in, Cairo::RefPtr &out) const +{ + do_transform(in->cobj(), out->cobj()); +} + +/** + * Apply the CMS transform to a single Color object's data. + * + * @arg io - The input/output color as a vector of numbers between 0.0 and 1.0 + * + * @returns the modified color in io + */ +bool Transform::do_transform(std::vector &io) const +{ + std::vector input(_channels_in); + std::vector output(_channels_out); + + for (unsigned int a = 0; a < _channels_in; a++) { + // double to uint16 conversion + input[a] = io[0] * 65535; + io.erase(io.begin()); + } + cmsDoTransform(_handle, &input.front(), &output.front(), 1); + + // Preserve any extra non-color channels (i.e. transparency) by inserting + // into the front of the vector instead of the end. + for (auto &val : boost::adaptors::reverse(output)) { + io.insert(io.begin(), val / 65535.0); + } + return true; +} + +/** + * Return true if the input color is outside of the gamut if it was transformed using + * this cms transform. + * + * @arg input - The input color as a vector of numbers between 0.0 and 1.0 + */ +bool Transform::check_gamut(std::vector const &input) const +{ + cmsUInt16Number in[cmsMAXCHANNELS]; + cmsUInt16Number out[cmsMAXCHANNELS]; + for (unsigned int i = 0; i < cmsMAXCHANNELS; i++) { + in[i] = input.size() > i ? input[i] * 65535 : 0; + out[i] = 0; + } + cmsDoTransform(_handle, &in, &out, 1); + + // All channels are set to zero in the alarm context, so the result is zero if out of gamut. + return std::accumulate(std::begin(out), std::end(out), 0, std::plus()) == 0; +} + +/** + * Get the lcms2 intent enum from the inkscape intent enum + * + * @args intent - The Inkscape RenderingIntent enum + * + * @returns flags - Any flags modifications for the given intent + * @returns lcms intent enum, default is INTENT_PERCEPTUAL + */ +unsigned int Transform::lcms_intent(RenderingIntent intent, unsigned int &flags) +{ + switch (intent) { + case RenderingIntent::RELATIVE_COLORIMETRIC: + // Black point compensation only matters to relative colorimetric + flags |= cmsFLAGS_BLACKPOINTCOMPENSATION; + case RenderingIntent::RELATIVE_COLORIMETRIC_NOBPC: + return INTENT_RELATIVE_COLORIMETRIC; + case RenderingIntent::SATURATION: + return INTENT_SATURATION; + case RenderingIntent::ABSOLUTE_COLORIMETRIC: + return INTENT_ABSOLUTE_COLORIMETRIC; + case RenderingIntent::PERCEPTUAL: + case RenderingIntent::UNKNOWN: + case RenderingIntent::AUTO: + default: + return INTENT_PERCEPTUAL; + } +} + +} // namespace Inkscape::Colors::CMS + +/* + 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 : diff --git a/src/colors/cms/transform.h b/src/colors/cms/transform.h new file mode 100644 index 0000000000000000000000000000000000000000..337791455334b568840032e8eafdb32ec14723d7 --- /dev/null +++ b/src/colors/cms/transform.h @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: see git history + * + * Copyright (C) 2017 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_COLORS_CMS_TRANSFORM_H +#define SEEN_COLORS_CMS_TRANSFORM_H + +#include +#include +#include // cmsHTRANSFORM +#include +#include + +#include "colors/spaces/enum.h" + +namespace Inkscape::Colors::CMS { + +class Profile; +class Transform +{ +public: + static std::shared_ptr const create(cmsHTRANSFORM handle, bool global = false); + static std::shared_ptr const create_for_cairo(std::shared_ptr const &from, + std::shared_ptr const &to, + std::shared_ptr const &proof = nullptr, + RenderingIntent proof_intent = RenderingIntent::AUTO, + bool with_gamut_warn = false); + static std::shared_ptr const create_for_cms(std::shared_ptr const &from, + std::shared_ptr const &to, RenderingIntent intent); + static std::shared_ptr const create_for_cms_checker(std::shared_ptr const &from, + std::shared_ptr const &to); + + Transform(cmsHTRANSFORM handle, bool global = false) + : _handle(handle) + , _context(!global ? cmsGetTransformContextID(handle) : nullptr) + , _channels_in(T_CHANNELS(cmsGetTransformInputFormat(handle))) + , _channels_out(T_CHANNELS(cmsGetTransformOutputFormat(handle))) + { + assert(_handle); + } + ~Transform() + { + cmsDeleteTransform(_handle); + if (_context) + cmsDeleteContext(_context); + } + Transform(Transform const &) = delete; + Transform &operator=(Transform const &) = delete; + + cmsHTRANSFORM getHandle() const { return _handle; } + + void do_transform(unsigned char *inBuf, unsigned char *outBuf, unsigned size) const; + void do_transform(cairo_surface_t *in, cairo_surface_t *out) const; + void do_transform(Cairo::RefPtr &in, Cairo::RefPtr &out) const; + bool do_transform(std::vector &io) const; + + void set_gamut_warn(std::vector const &input); + bool check_gamut(std::vector const &input) const; + +private: + cmsHTRANSFORM _handle; + cmsContext _context; + + static unsigned int lcms_intent(RenderingIntent intent, unsigned int &flags); + +public: + unsigned int _channels_in; + unsigned int _channels_out; +}; + +} // namespace Inkscape::Colors::CMS + +#endif // SEEN_COLORS_CMS_TRANSFORM_H + +/* + 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 : diff --git a/src/colors/color-set.cpp b/src/colors/color-set.cpp new file mode 100644 index 0000000000000000000000000000000000000000..90152ae9611eb8c46e2978ee200f5a515990ed9b --- /dev/null +++ b/src/colors/color-set.cpp @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * A set of colors which can be modified together used for color pickers + *//* + * Copyright (C) 2024 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "color-set.h" + +#include + +#include "colors/color.h" +#include "colors/spaces/base.h" +#include "colors/spaces/components.h" + +namespace Inkscape::Colors { + +/** + * Construct a new ColorSet object to contain a group of colors which will + * be modified collectively. + * + * @arg space - optionally contrain each color added to be in this color space + * @arg alpha - optionally contrain each color to have or not have an alpha channel + */ +ColorSet::ColorSet(std::shared_ptr space, std::optional alpha) + : _space_constraint(std::move(space)) + , _alpha_constraint(alpha) +{} + +/** + * Get a list of components for the color space set to this color set. + */ +Space::Components const &ColorSet::getComponents() const +{ + if (!_space_constraint) + throw ColorError("Components are only available on a color space constrained ColorSet."); + return _space_constraint->getComponents(_alpha_constraint && *_alpha_constraint); +} + +/** + * Reset the color set and remove all colors from it. + */ +void ColorSet::clear() +{ + if (!_colors.empty()) { + _colors.clear(); + colors_cleared(); + } +} + +/** + * Set this color to being grabbed for a continuous set of changes. + */ +void ColorSet::grab() +{ + if (!_blocked && !_grabbed) { + block(); + signal_grabbed.emit(); + unblock(); + _grabbed = true; + } +} + +/** + * Set the color as being released from continuous changes. + */ +void ColorSet::release() +{ + if (!_blocked && _grabbed) { + _grabbed = false; + block(); + signal_released.emit(); + unblock(); + } +} + +/** + * Called when the colors change in some way. + */ +void ColorSet::colors_changed() +{ + // Send signals about this change + if (!_blocked) { + block(); + signal_changed.emit(); + unblock(); + } +} + +/** + * Called when the list of colors changes (add or clear) + */ +void ColorSet::colors_cleared() +{ + if (!_blocked) { + block(); + signal_cleared.emit(); + unblock(); + } +} + +/** + * Returns true if all of the colors are the same color. + */ +bool ColorSet::isSame() const +{ + if (_colors.empty()) + return true; + for (auto const &[id, color] : _colors) { + if (color != _colors[0].second) + return false; + } + return true; +} + +/** + * Overwrite all colors so they equal the the new color + * + * @arg other - The other color to replace all colors in this set with. + * + * @returns the number of colors changed. + */ +unsigned ColorSet::setAll(Color const &other) +{ + unsigned changed = 0; + for (auto &[id, color] : _colors) { + auto was = color; + color.set(other, true); + // Comparison after possible color space conversion + changed += (was != color); + } + if (changed) { + colors_changed(); + } + return changed; +} + +/** + * Set each of the colors from the other color set by id. Creating new entries where the id is not found. + * + * @arg other - The other color set to find colors from. + * + * @returns the number of colors changed or added + */ +unsigned ColorSet::setAll(ColorSet const &other) +{ + unsigned changed = 0; + for (auto &[id, color] : other) { + changed += _set(id, color); + } + if (changed > 0) { + colors_changed(); + } + return changed; +} + +/** + * Set a single color in the color set by its id. + * + * @arg id - The id to use, if this id isn't set a new item is created. + * @arg other - The other color to replace all colors in this set with. + * + * @returns true if this color was changed. + */ +bool ColorSet::set(std::string id, Color const &other) +{ + if (_set(std::move(id), other)) { + colors_changed(); + return true; + } + return false; +} + +/** + * Remove any other colors and set to just this one color. + * + * @arg color - The color to set this color-set to. + * + * @returns true if the color was new or changed. + */ +bool ColorSet::set(Color const &other) +{ + // Always clear the colors if it's being used differently + if (_colors.size() != 1 || _colors[0].first != "single") + _colors.clear(); + return set("single", other); +} + +/** + * Get the color if there is only one color set with set(Color) + * + * @returns the available color, normalized + */ +std::optional ColorSet::get() const +{ + return get("single"); +} + +/* + * Internal function for setting a color by id without calling the changed singal. + */ +bool ColorSet::_set(std::string id, Color const &other) +{ + for (auto &[cid, color] : _colors) { + if (cid == id) { + auto was = color; + color.set(other, true); + return was != color; + } + } + // Add a new entry for this id + Color copy = other; + + // Enforce constraints on space and alpha if any + if (_space_constraint) + copy.convert(_space_constraint); + + if (_alpha_constraint) { + copy.enableOpacity(*_alpha_constraint); + } + + _colors.emplace_back(std::move(id), copy); + return true; +} + +/** + * Return a single color by it's index. The color will be normalized + * before returning a copy as some functions can modify colors out + * of bounds. + * + * @arg index - The id of the color used in ColorSet::set() + * + * @returns a normalized color object from the given index or none if the id is not found. + */ +std::optional ColorSet::get(std::string const &id) const +{ + for (auto &[cid, color] : _colors) { + if (cid == id) + return color.normalized(); + } + return {}; +} + +/** + * Set this one component to this specific value for all colors. + * + * @arg index - The index to replace, will cause an error if out of bounds. + * @arg other - The other color to replace all colors in this set with. + * + * @returns the number of colors changed. + */ +unsigned ColorSet::setAll(Space::Component const &c, double value) +{ + if (!_space_constraint || _space_constraint->getType() != c.type) + throw ColorError("Incompatible color component used in ColorSet::set."); + unsigned changed = 0; + for (auto &[id, color] : _colors) { + auto was = color; + color.set(c.index, value); + // Comparison after possible color space conversion + changed += (was != color); + } + if (changed) { + colors_changed(); + } + return changed; +} + +/** + * Set the average value in this component by taking the average + * finding the delta and moving all colors by the given delta. + * + * This will not run normalization so out of bound changes can remember + * their values until the mutation period is finished and normalization + * is run on the returned colors. see ColorSet::get(). + * + * @arg c - The component to change the average for + * @arg value - The new average value that the set will calculate to + */ +void ColorSet::setAverage(Space::Component const &c, double value) +{ + if (!_space_constraint || _space_constraint->getType() != c.type) + throw ColorError("Incompatible color component used in ColorSet::moveAverageTo."); + auto delta = value - getAverage(c); + for (auto &[id, color] : _colors) { + color.set(c.index, color[c.index] + delta); + } + colors_changed(); +} + +/** + * Get the average value for this component across all colors. + * + * @arg c - The component to get the average for + * + * @returns the normalized average value for all colors in this set. + */ +double ColorSet::getAverage(Space::Component const &c) const +{ + if (!_space_constraint || _space_constraint->getType() != c.type) + throw ColorError("Incompatible color component used in ColorSet::get."); + double value = 0.0; + for (auto const &[id, color] : _colors) { + value += color[c.index]; + } + return c.normalize(value / _colors.size()); +} + +/** + * Get a list of all normalized values for this one component. + * + * @arg c - The component to collect all the values for. + * + * @returns a list of normalized values in this component. + */ +std::vector ColorSet::getAll(Space::Component const &c) const +{ + if (!_space_constraint || _space_constraint->getType() != c.type) + throw ColorError("Incompatible color component used in ColorSet::getAll."); + std::vector ret(_colors.size()); + std::transform(_colors.begin(), _colors.end(), ret.begin(), + [c](auto &iter) { return c.normalize(iter.second[c.index]); }); + return ret; +} + +/** + * Return the best color space from this collection of colors. If the color + * space is constrained then the result will be that space. Otherwise picks + * the space with the most colors. + */ +std::shared_ptr ColorSet::getBestSpace() const +{ + if (_space_constraint) + return _space_constraint; + + unsigned biggest = 0; + std::shared_ptr ret; + std::map, unsigned> counts; + for (auto const &[id, color] : _colors) { + if (++counts[color.getSpace()] > biggest) { + biggest = counts[color.getSpace()]; + ret = color.getSpace(); + } + } + return ret; +} + +/** + * Return the average color between this set of colors. + * + * If space is not constrained, it will return the average in the best color space. + * If alpha is not constrained, the average will always include an alpha channel + */ +Color ColorSet::getAverage() const +{ + if (isEmpty()) + throw ColorError("Can't get the average color of no colors."); + + auto avg_space = getBestSpace(); + auto avg_alpha = _alpha_constraint.value_or(true); + + std::vector values(avg_space->getComponentCount() + avg_alpha); + + for (auto const &[id, color] : _colors) { + // Colors::Color will return the alpha channel as 1.0 if it doesn't exist. + if (color.getSpace() == avg_space) { + for (unsigned int i = 0; i < values.size(); i++) { + values[i] += color[i]; + } + } else if (auto copy = color.converted(avg_space)) { + for (unsigned int i = 0; i < values.size(); i++) { + values[i] += (*copy)[i]; + } + } + } + for (double &value : values) { + value /= _colors.size(); + } + return Color(avg_space, values); +} + +/** + * Return the number of items in the color set. + */ +unsigned ColorSet::size() const +{ + return _colors.size(); +} + +} // namespace Inkscape::Colors + +/* + 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 : diff --git a/src/colors/color-set.h b/src/colors/color-set.h new file mode 100644 index 0000000000000000000000000000000000000000..9cfdf3fb557658199a9684b4e31fd2b2bf40e67c --- /dev/null +++ b/src/colors/color-set.h @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * A set of colors which can be modified together used for color pickers + *//* + * Copyright (C) 2024 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_COLORS_COLOR_SET +#define SEEN_COLORS_COLOR_SET + +#include +#include +#include +#include + +#include "helper/auto-connection.h" + +namespace Inkscape::Colors { +class Color; +namespace Space { +class AnySpace; +class Components; +class Component; +} // namespace Space + +using IdColors = std::vector>; + +class ColorSet +{ +public: + ColorSet(std::shared_ptr space = {}, std::optional alpha = {}); + + // By default, disallow copy constructor + ColorSet(ColorSet const &obj) = delete; + + IdColors::const_iterator begin() const { return std::begin(_colors); } + IdColors::const_iterator end() const { return std::end(_colors); } + IdColors::iterator begin() { return std::begin(_colors); } + IdColors::iterator end() { return std::end(_colors); } + + // Signals allow the set to be tied into various interfaces + bool isBlocked() const { return _blocked; } + bool isGrabbed() const { return _grabbed; } + + sigc::signal signal_grabbed; + sigc::signal signal_released; + sigc::signal signal_changed; + sigc::signal signal_cleared; + + void grab(); + void release(); + void block() { _blocked = true; } + void unblock() { _blocked = false; } + + // Information about the color set's meta data + Space::Components const &getComponents() const; + std::shared_ptr const getSpaceConstraint() const { return _space_constraint; } + std::optional const getAlphaConstraint() const { return _alpha_constraint; } + + // Set and get a single color (i.e. not a list of colors) + bool set(Color const &color); + std::optional get() const; + void clear(); + + // Control the list of colors by id + std::optional get(std::string const &id) const; + bool set(std::string id, Color const &color); + + // Change the colors in some way and send signals + unsigned setAll(ColorSet const &other); + unsigned setAll(Color const &other); + unsigned setAll(Space::Component const &c, double value); + std::vector getAll(Space::Component const &c) const; + + // Set and get specific color components + void setAverage(Space::Component const &c, double value); + double getAverage(Space::Component const &c) const; + Color getAverage() const; + + // Ask for information about the whole set of colors + unsigned size() const; + + bool isEmpty() const { return _colors.empty(); } + bool isSame() const; + std::shared_ptr getBestSpace() const; + +private: + bool _set(std::string id, Color const &color); + void colors_changed(); + void colors_cleared(); + + IdColors _colors; + + // Constraints can only be set up at construction so are immutable. + std::shared_ptr const _space_constraint; + std::optional const _alpha_constraint; + + // States which are set during the lifetime of the set + bool _grabbed = false; + bool _blocked = false; +}; + +} // namespace Inkscape::Colors + +#endif +/* + 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 : diff --git a/src/colors/color.cpp b/src/colors/color.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d77a7df9777f9ae548c9095cb6291d3bda27f28f --- /dev/null +++ b/src/colors/color.cpp @@ -0,0 +1,629 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Martin Owens + * + * Copyright (C) 2023 AUTHORS + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/color.h" + +#include +#include +#include + +#include "colors/manager.h" +#include "spaces/base.h" +#include "spaces/components.h" + +namespace Inkscape::Colors { +namespace { + +template +static decltype(auto) assert_nonnull(T &&t) +{ + assert(t); + return std::forward(t); +} + +} // namespace + +/** + * Create a color, given the type of the space and the values to store. + * + * @arg space_type - The type of the color space these values are in. + * @arg values - A vector of numbers usually between 0.0 and 1.0 per channel + * which will be moved to the new Color object. + */ +Color::Color(Space::Type space_type, std::vector values) + : Color(assert_nonnull(Manager::get().find(space_type)), std::move(values)) +{} + +/** + * Compatability layer for making blind RGB colors + */ +Color::Color(uint32_t rgba, bool opacity) + : Color(Space::Type::RGB, rgba_to_values(rgba, opacity)) +{} + +/** + * Construct a color in the given color space. + * + * @arg space - The color space these channel values exist in. + * @arg colors - Each channel in the color space must have a value between 0.0 and 1.0 + * an extra value may be appended to indicate the opacity to support CSS + * formatted opacity parsing but is not expected to be writen by Inkscape + * when being generated. + */ +Color::Color(std::shared_ptr space, std::vector colors) + : _values(std::move(colors)) + , _space(std::move(space)) +{ + assert(_space->isValidData(_values)); +} + +/** + * Return true if the two colors are the same. + * + * The color space AND the values must be the same. But the name doesn't not + * have to be the same in both colors. + */ +bool Color::operator==(Color const &other) const +{ + return _space == other._space && _isnear(other._values); +} + +/** + * Get a single channel from this color. + */ +double Color::get(unsigned index) const +{ + assert(index <= getOpacityChannel()); + if (index < _values.size()) { + return _values[index]; + } else { + return 1.0; + } +} + +/** + * Format the color as a css string and return it. + * + * @arg opacity - If set to false the opacity will be ignored, even if present. + * + * Note: Choose the color space for your color carefully before printing. If you + * are outputting to a CSS version that only supports RGB hex codes, then convert + * the color to that color space before printing. + */ +std::string Color::toString(bool opacity) const +{ + return _space->toString(_values, opacity); +} + +/** + * Return an sRGB conversion of the color in RGBA int32 format. + * + * @args opacity - optional opacity to be mixed into any existing + * opacity in this color. + */ +uint32_t Color::toRGBA(double opacity) const +{ + return _space->toRGBA(_values, opacity); +} + +/** + * Return the RGBA int32 as an ARGB format number. + */ +uint32_t Color::toARGB(double opacity) const +{ + auto value = toRGBA(opacity); + return (value >> 8) | ((value & 0xff) << 24); +} + +/** + * Return the RGBA int32 as an ABGR format color. + */ +uint32_t Color::toABGR(double opacity) const +{ + auto value = toRGBA(opacity); + return (value << 24) | ((value << 8) & 0x00ff0000) | ((value >> 8) & 0x0000ff00) | (value >> 24); +} + +/** + * Convert to the same format as the other color. + * + * @arg other - Another color to copy the space and opacity from. + */ +bool Color::convert(Color const &other) +{ + if (convert(other._space)) { + enableOpacity(other.hasOpacity()); + return true; + } + return false; +} + +/** + * Convert this color into a different color space. + * + * @arg to_space - The target space to convert the color values to + */ +bool Color::convert(std::shared_ptr to_space) +{ + if (!to_space || !to_space->isValid()) { + return false; + } + + if (_space != to_space) { + _space->convert(_values, to_space); + _space = std::move(to_space); + assert(_space->isValidData(_values)); + } + _name = ""; + + return true; +} + +/** + * Convert this color into the first matched color space of the given type. + */ +bool Color::convert(Space::Type type) +{ + if (auto space = Manager::get().find(type)) { + return convert(space); + } + return false; +} + +/** + * Return a copy of this color converted to the same format as the other color. + */ +std::optional Color::converted(Color const &other) const +{ + Color copy = *this; + if (copy.convert(other)) { + return copy; + } + return {}; +} + +/** + * Convert a copy of this color into a different color space. + */ +std::optional Color::converted(std::shared_ptr to_space) const +{ + Color copy = *this; + if (copy.convert(std::move(to_space))) { + return copy; + } + return {}; +} + +/** + * Convert a copy of this color into the first matching color space type. + * + * if the space is not available in this color manager, an empty color is returned. + */ +std::optional Color::converted(Space::Type type) const +{ + Color copy = *this; + if (copy.convert(type)) { + return copy; + } + return {}; +} + +/** + * Set the channels directly without checking if the space is correct. + * + * @arg values - A vector of doubles, one value between 0.0 and 1.0 for + * each channel. An extra channel can be included for opacity. + */ +void Color::setValues(std::vector values) +{ + _name = ""; + _values = std::move(values); + assert(_space->isValidData(_values)); +} + +/** + * Set this color to the values from another color. + * + * @arg other - The other color which is a source for the values. + * if the other color is from an unknown color space which + * has never been seen before, it will cause an error. + * @arg keep_space - If true, this color's color-space will stay the same + * and the new values will be converted. This includes + * discarding opacity if this color didn't have opacity. + * + * @returns true if the new value if different from the old value + */ +bool Color::set(Color const &other, bool keep_space) +{ + if (keep_space) { + auto prev_space = _space; + auto prev_values = _values; + bool prev_opacity = hasOpacity(); + + if (set(other, false)) { + // Convert back to the previous space if needed. + convert(prev_space); + enableOpacity(prev_opacity); + // Return true if the converted result is different + return !_isnear(prev_values); + } + } else if (*this != other) { + _space = other._space; + _values = other._values; + _name = other._name; + return true; + } + return false; +} + +/** + * Set this color by parsing the given string. If there's a parser error + * it will not change the existing color. + * + * @arg parsable - A string with a typical css color value. + * @arg keep_space - If true, the existing space will stay the same (see previous Color::set) + * + * @returns true if the new value if different from the old value + */ +bool Color::set(std::string const &parsable, bool keep_space) +{ + if (auto color = Color::parse(parsable)) { + return set(*color, keep_space); + } + return false; +} + +/** + * Returns true if the values are near to the other values + */ +bool Color::_isnear(std::vector const &other, double epsilon) const +{ + bool is_near = _values.size() == other.size(); + for (size_t i = 0; is_near && i < _values.size(); i++) { + is_near &= std::abs(_values[i] - other[i]) < epsilon; + } + return is_near; +} + +/** + * Create an optional color if value is valid. + */ +std::optional Color::parse(char const *value) +{ + if (!value) { + return {}; + } + return parse(std::string(value)); +} + +/** + * Create an optional color, if possible, from the given string. + */ +std::optional Color::parse(std::string const &value) +{ + Space::Type space_type; + std::string cms_name; + std::vector values; + std::vector fallback; + if (Parsers::get().parse(value, space_type, cms_name, values, fallback)) { + return ifValid(space_type, std::move(values)); + } + // Couldn't be parsed as a color at all + return {}; +} + +/** + * Construct a color from the space type and values, if the values are valid + */ +std::optional Color::ifValid(Space::Type space_type, std::vector values) +{ + if (auto space = Manager::get().find(space_type)) { + if (space->isValidData(values)) { + return std::make_optional(std::move(space), std::move(values)); + } + } + // Invalid color data, return empty optional + return {}; +} + +/** + * Set a specific channel in the color. + * + * @arg index - The channel/component index to set + * @arg value - The new value to set it to. + * + * @returns true if the new value if different from the old value + */ +bool Color::set(unsigned int index, double value) +{ + assert(index <= getOpacityChannel()); + if (index == _values.size()) { + _values.push_back(1.0); + } + auto const changed = std::abs(_values[index] - value) >= 0.001; + _values[index] = value; + return changed; +} + +/** + * Set this color from an RGBA unsigned int. + * + * @arg rgba - The RGBA color encoded as a single unsigned integer, 8bpc + * @arg opacity - True if the opacity (Alpha) should be stored too. + * + * @returns true if the new value if different from the old value + */ +bool Color::set(uint32_t rgba, bool opacity) +{ + if (_space->getType() != Space::Type::RGB) { + // Ensure we are in RGB + _space = assert_nonnull(Manager::get().find(Space::Type::RGB)); + } else if (rgba == toRGBA(opacity)) { + return false; // nothing to do. + } + _name = ""; + _values = std::move(rgba_to_values(rgba, opacity)); + return true; +} + +/** + * Enables or disables the opacity channel. + */ +void Color::enableOpacity(bool enable) +{ + auto const has_opacity = hasOpacity(); + if (enable && !has_opacity) { + _values.push_back(1.0); + } else if (!enable && has_opacity) { + _values.pop_back(); + } +} + +/** + * Returns true if there is an opacity channel in this color. + */ +bool Color::hasOpacity() const +{ + return _values.size() > getOpacityChannel(); +} + +/** + * Get the opacity in this color, if it's stored. Returns 1.0 if no + * opacity exists in this color or 0.0 if this color is empty. + */ +double Color::getOpacity() const +{ + return hasOpacity() ? _values.back() : 1.0; +} + +/** + * Get the opacity, and remove it from this color. This is useful when setting a color + * into an svg css property that has it's own opacity property but you aren't ready to + * create a string (see toString(false) for that use) + */ +double Color::stealOpacity() +{ + auto ret = getOpacity(); + enableOpacity(false); + return ret; +} + +/** + * Get the opacity channel index + */ +unsigned int Color::getOpacityChannel() const +{ + return _space->getComponentCount(); +} + +/** + * Return the pin number (pow2) of the channel index to pin + * that channel in a mutation. + */ +unsigned int Color::getPin(unsigned int channel) const +{ + return 1 << channel; +} + +/** + * Set the opacity of this color object. + */ +bool Color::setOpacity(double opacity) +{ + if (hasOpacity()) { + if (opacity == _values.back()) { + return false; + } + _values.back() = opacity; + } else { + _values.emplace_back(opacity); + } + return true; +} + +/** + * Make sure the values for this color are within acceptable ranges. + */ +void Color::normalize() +{ + for (auto const &comp : _space->getComponents(hasOpacity())) { + _values[comp.index] = comp.normalize(_values[comp.index]); + } +} + +/** + * Return a normalized copy of this color so the values are within acceptable ranges. + */ +Color Color::normalized() const +{ + Color copy = *this; + copy.normalize(); + return copy; +} + +/** + * Invert the color for each channel. + * + * @arg pin - Bit field, which channels should not change if not specified + * the opacity pin is used as this is the most reasonable default. + */ +void Color::invert(unsigned int pin) +{ + for (unsigned int i = 0; i < _values.size(); i++) { + if (pin & (1 << i)) { + continue; + } + _values[i] = 1.0 - _values[i]; + } +} + +/** + * Jitter the color for each channel. + * + @ @arg force - The amount of jitter to add to each channel. + * @arg pin - Bit field, which channels should not change (see invert). + */ +void Color::jitter(double force, unsigned int pin) +{ + for (unsigned int i = 0; i < _values.size(); i++) { + if (pin & (1 << i)) { + continue; + } + // Random number between -0.5 and 0.5 times the force. + double r = (static_cast(std::rand()) / RAND_MAX - 0.5); + _values[i] += r * force; + } + normalize(); +} + +/* + * Modify this color to be the average between two colors, modifying the first. + * + * @arg other - The other color to average with + * @arg pos - The position (i.e. t) between the two colors. + * @pin pin - Bit field, which channels should not change (see invert) + */ +void Color::average(Color const &other, double pos, unsigned int pin) +{ + _color_mutate_inplace(other, pin, + [pos](auto &value, auto otherValue) { value = value * (1 - pos) + otherValue * pos; }); +} + +/** + * Return the average between this and another color. + * + * @arg other - The other color to average with + * @arg pos - The weighting to give each color + */ +Color Color::averaged(Color const &other, double pos) const +{ + Color copy = *this; + copy.average(other, pos); + return copy; +} + +/** + * Get the mean square difference between this color and another. + */ +double Color::difference(Color const &other) const +{ + double ret = 0.0; + if (auto copy = other.converted(*this)) { + for (unsigned int i = 0; i < _values.size(); i++) { + ret += std::pow(_values[i] - (*copy)[i], 2); + } + } + return ret; +} + +/** + * Find out if a color is a close match to another color of the same type. + * + * @returns true if the colors are the same structure (space, opacity) + * and have values which are no more than epison apart. + */ +bool Color::isClose(Color const &other, double epsilon) const +{ + bool match = _space == other._space && _values.size() == other._values.size(); + for (unsigned int i = 0; match && i < _values.size(); i++) { + match &= (std::fabs(_values[i] - other._values[i]) < epsilon); + } + return match; +} + +/** + * Find out if a color is similar to another color, converting it + * first if it's a different type. If one has opacity and the other does not + * it always returns false. + * + * @returns true if the colors are similar when converted to the same space. + */ +bool Color::isSimilar(Color const &other, double epsilon) const +{ + if (other._space != _space) { + if (auto copy = other.converted(_space)) { + return isClose(*copy, epsilon); + } + return false; // bad color conversion + } + return isClose(other, epsilon); +} + +/** + * Return true if this color would be out of gamut when converted to another space. + * + * @arg other - The other color space to compare against. + */ +bool Color::isOutOfGamut(std::shared_ptr other) const +{ + return _space->outOfGamut(_values, other); +} + +/** + * Return true if this color would be considered over-inked. + */ +bool Color::isOverInked() const +{ + return _space->overInk(_values); +} + +// Color-color in-place modification template +template +void Color::_color_mutate_inplace(Color const &other, unsigned int pin, Func func) +{ + // Convert the other's space and opacity if it's different + if (other._space != _space || other.hasOpacity() != hasOpacity()) { + if (auto copy = other.converted(*this)) { + return _color_mutate_inplace(*copy, pin, func); + } + return; // Bad conversion + } + + // Both are good, so average each channel + for (unsigned int i = 0; i < _values.size(); i++) { + if (pin & (1 << i)) { + continue; + } + func(_values[i], other[i]); + } +} + +}; // namespace Inkscape::Colors + +/* + 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 : diff --git a/src/colors/color.h b/src/colors/color.h new file mode 100644 index 0000000000000000000000000000000000000000..62b6094f8eeb2bee30edb29d26f06e9d2e570096 --- /dev/null +++ b/src/colors/color.h @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Martin Owens + * + * Copyright (C) 2023 AUTHORS + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_COLORS_COLOR_H +#define SEEN_COLORS_COLOR_H + +#include +#include +#include + +#include "colors/spaces/enum.h" +#include "utils.h" + +namespace Inkscape::Colors { +namespace Space { +class AnySpace; +} // namespace Space + +class Color final +{ +public: + Color(std::shared_ptr space, std::vector colors); + Color(Space::Type space_type, std::vector values); + explicit Color(uint32_t color, bool alpha = true); + + static std::optional parse(char const *value); + static std::optional parse(std::string const &value); + static std::optional ifValid(Space::Type space_type, std::vector values); + + bool operator==(Color const &other) const; + double operator[](unsigned int index) const { return get(index); } + + std::shared_ptr const &getSpace() const { return _space; } + const std::vector &getValues() const { return _values; } + void setValues(std::vector values); + size_t size() const { return _values.size(); } + + double get(unsigned int index) const; + bool set(unsigned int index, double value); + bool set(Color const &other, bool keep_space = true); + bool set(std::string const &parsable, bool keep_space = true); + bool set(uint32_t rgba, bool opacity = true); + + bool hasOpacity() const; + void enableOpacity(bool enabled); + unsigned int getOpacityChannel() const; + double getOpacity() const; + double stealOpacity(); + bool setOpacity(double opacity); + bool addOpacity(double opacity = 1.0) { return setOpacity(opacity * getOpacity()); } + + unsigned int getPin(unsigned int channel) const; + + static constexpr double EPSILON = 1e-4; + + double difference(Color const &other) const; + bool isClose(Color const &other, double epsilon = EPSILON) const; + bool isSimilar(Color const &other, double epsilon = EPSILON) const; + + bool convert(Color const &other); + bool convert(std::shared_ptr space); + bool convert(Space::Type type); + std::optional converted(Color const &other) const; + std::optional converted(std::shared_ptr to_space) const; + std::optional converted(Space::Type type) const; + + std::string toString(bool opacity = true) const; + uint32_t toRGBA(double opacity = 1.0) const; + uint32_t toARGB(double opacity = 1.0) const; + uint32_t toABGR(double opacity = 1.0) const; + + std::string getName() const { return _name; } + void setName(std::string name) { _name = std::move(name); } + + bool isOutOfGamut(std::shared_ptr other) const; + bool isOverInked() const; + + void normalize(); + Color normalized() const; + + void average(Color const &other, double pos = 0.5, unsigned int pin = 0); + Color averaged(Color const &other, double pos = 0.5) const; + + void invert(unsigned int pin); + void invert() { invert(getPin(getOpacityChannel())); } + void jitter(double force, unsigned int pin = 0); + +private: + std::string _name; + std::vector _values; + std::shared_ptr _space; + + template + void _color_mutate_inplace(Color const &other, unsigned int pin, Func avgFunc); + + bool _isnear(std::vector const &other, double epsilon = 0.001) const; +}; + +class ColorError : public std::exception +{ +public: + ColorError(std::string &&msg) + : _msg(msg) + {} + char const *what() const noexcept override { return _msg.c_str(); } + +private: + std::string _msg; +}; + +} // namespace Inkscape::Colors + +#endif // SEEN_COLORS_COLOR_H diff --git a/src/colors/document-cms.cpp b/src/colors/document-cms.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b2b74560b7f5859e0ce095790c676b44853aef52 --- /dev/null +++ b/src/colors/document-cms.cpp @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * DocumentCMS - Look after all a document's icc profiles and lists of used colors. + * + * Copyright 2023 Martin Owens + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "document-cms.h" + +#include "cms/profile.h" +#include "cms/system.h" +#include "colors/color.h" +#include "colors/manager.h" +#include "colors/spaces/cms.h" +#include "document.h" +#include "io/sys.h" +#include "object/color-profile.h" +#include "object/sp-defs.h" +#include "object/sp-root.h" + +namespace Inkscape::Colors { + +class ColorProfileLink +{ +public: + ColorProfileLink(DocumentCMS *man, ColorProfile *elem); + ~ColorProfileLink(); + + DocumentCMS *tracker = nullptr; + ColorProfile *cp = nullptr; + std::shared_ptr space; + +private: + Inkscape::auto_connection _modified_connection; + + bool generateSpace(); + bool updateSpace(); +}; + +/** + * A local private class for tracking the signals between the color-profile xml in an SPDocument + * and the Space::CMS object which is the functional end of the color system. + */ +ColorProfileLink::ColorProfileLink(DocumentCMS *man, ColorProfile *elem) + : tracker(man) + , cp(elem) +{ + _modified_connection = cp->connectModified([this](SPObject *obj, guint flags) { + if (space ? updateSpace() : generateSpace()) { + tracker->_modified_signal.emit(space); + } + }); + generateSpace(); +} + +ColorProfileLink::~ColorProfileLink() +{ + _modified_connection.disconnect(); + if (space) + tracker->removeProfile(space); +} + +/** + * Attempt to turn the data in the ColorProfile into a Space::CMS object + * + * @returns true if a space was generated + */ +bool ColorProfileLink::generateSpace() +{ + if (space) + throw ColorError("Unexpected generation of CMS profile space"); + + std::shared_ptr profile; + + auto data = cp->getProfileData(); + auto local_id = cp->getLocalProfileId(); + if (!data.empty()) { + profile = CMS::Profile::create_from_data(data); + } else if (!local_id.empty()) { + profile = CMS::System::get().getProfile(local_id); + } + + if (profile) { + space = tracker->addProfile(profile, cp->getName(), cp->getRenderingIntent()); + } else { + g_warning("Incomplete CMS profile, no color space created for '%s'", cp->getName().c_str()); + } + return (bool)space; +} + +/** + * Update the space, this typically means the intent has changed. + */ +bool ColorProfileLink::updateSpace() +{ + if (space->getName() != cp->getName()) { + return generateSpace(); + } + if (space->getIntent() != cp->getRenderingIntent()) { + space->setIntent(cp->getRenderingIntent()); + return true; + } + return false; +} + +DocumentCMS::DocumentCMS(SPDocument *document) + : _document(document) +{ + if (document) + _resource_connection = + _document->connectResourcesChanged("iccprofile", sigc::mem_fun(*this, &DocumentCMS::refreshResources)); +} + +DocumentCMS::~DocumentCMS() +{ + _document = nullptr; +} + +/** + * Create an optional color, like Color::parse but with the document's cms spaces. + */ +std::optional DocumentCMS::parse(char const *value) const +{ + if (!value) + return {}; + return parse(std::string(value)); +} + +/** + * Create an optional color, like Color::parse, but match to document cms profiles where needed. + */ +std::optional DocumentCMS::parse(std::string const &value) const +{ + Space::Type space_type; + std::string cms_name; + std::vector values; + std::vector fallback; + if (Parsers::get().parse(value, space_type, cms_name, values, fallback)) { + if (cms_name.empty()) + return Color::ifValid(space_type, std::move(values)); + + // Find a space or construct an anonymous one so we don't lose data. + if (!_spaces.contains(cms_name)) { + _spaces[cms_name] = std::make_shared(cms_name, values.size()); + } + auto space = _spaces.find(cms_name)->second; + + if (!space->isValid()) { + for (int i = 2; i >= 0; i--) { + // Assume RGB fallback data if three doubles. Else black. + values.insert(values.begin(), fallback.size() == 3 ? fallback[i] : 0.0); + } + } + return Color(std::move(space), std::move(values)); + } + // Couldn't be parsed as a color at all + return {}; +} + +/** + * Make sure the icc-profile resource list is linked and up to date + * with the color manager's list of available color spaces. + */ +void DocumentCMS::refreshResources() +{ + bool changed = false; + + // 1. Look for color profile which have been created + std::vector objs; + for (auto obj : _document->getResourceList("iccprofile")) { + if (!obj->getId()) + continue; + if (auto cp = cast(obj)) { + objs.push_back(cp); + bool found = false; + for (auto &link : _links) { + found = found || (link->cp == cp); + } + if (!found) { + _links.emplace_back(new ColorProfileLink(this, cp)); + changed = true; + } + } + } + // 2. Look for color profiles which have been deleted + for (auto iter = _links.begin(); iter != _links.end();) { + if (std::find(objs.begin(), objs.end(), (*iter)->cp) == objs.end()) { + iter = _links.erase(iter); + changed = true; + } else + ++iter; + } + + // 3. Tell the rest of inkscape if something is added or removed + if (changed) { + _changed_signal.emit(); + } +} + +/** + * Add the icc profile via a URI as a color space with the attending settings. + */ +std::shared_ptr DocumentCMS::addProfileURI(std::string uri, std::string name, RenderingIntent intent) +{ + return addProfile(Inkscape::Colors::CMS::Profile::create_from_uri(std::move(uri)), std::move(name), intent); +} + +/** + * Add the icc profile as a color space with the attending settings. + */ +std::shared_ptr DocumentCMS::addProfile(std::shared_ptr profile, std::string name, + RenderingIntent intent) +{ + auto space = std::make_shared(profile); + + if (!name.empty()) { + // The name from the color-profile xml element overrides any internal name + space->setName(std::move(name)); + } + name = space->getName(); + if (_spaces.contains(name)) + throw ColorError("Color profile with that name already exists."); + + space->setIntent(intent != RenderingIntent::UNKNOWN ? intent : RenderingIntent::PERCEPTUAL); + _spaces[name] = space; + return space; +} + +/** + * Remove the icc profile as a color space + */ +void DocumentCMS::removeProfile(std::shared_ptr space) +{ + auto result = std::find_if(_spaces.begin(), _spaces.end(), [space](const auto &it) { return it.second == space; }); + + if (result != _spaces.end()) + _spaces.erase(result); +} + +/** + * Attach the named profile to the document. The name is used as a look-up in + * the CMS::System database then attached to the manager's document using the + * given storage mechanism. + * + * @args lookup - The string name, Id or path to look up in the systems database. + * @args storage - The mechanism to use when storing the profile in the document. + * @args name - The new name to use, if empty the name from the profile is used. + * @args intent - The rendering intent to use when transforming colors in this profile. + */ +void DocumentCMS::attachProfileToDoc(std::string const &lookup, ColorProfileStorage storage, RenderingIntent intent, + std::string name) +{ + auto &cms = Inkscape::Colors::CMS::System::get(); + if (auto profile = cms.getProfile(lookup)) { + std::string new_name = name.empty() ? profile->getName() : std::move(name); + if (auto cp = Inkscape::ColorProfile::createFromProfile(_document, *profile, std::move(new_name), storage)) { + cp->setRenderingIntent(intent); + _document->ensureUpToDate(); + } + } else { + g_error("Couldn't get the icc profile '%s'", lookup.c_str()); + } +} + +/** + * Get the specific color space from the list of available spaces. + * + * @arg name - The name of the color space + */ +std::shared_ptr DocumentCMS::getSpace(std::string const &name) const +{ + auto it = _spaces.find(name); + if (it != _spaces.end()) + return it->second; + return nullptr; +} + +/** + * Get the document color-profile SPObject for the named cms profile. Returns nullptr + * if the name is not found, if the link has not yet been made or the space is + * not a CMS color space (i.e. sRGB). + */ +ColorProfile *DocumentCMS::getColorProfileForSpace(std::string const &name) const +{ + return getColorProfileForSpace(getSpace(name)); +} + +/** + * Get the document color-profile SPObject for the given space. + * Returns nullptr if the space is not a CMS color space. + */ +ColorProfile *DocumentCMS::getColorProfileForSpace(std::shared_ptr space) const +{ + for (auto &link : _links) { + if (space && link->space && link->space->getName() == space->getName()) { + return link->cp; + } + } + return nullptr; +} + +/** + * Sets the rendering intent for the given color space in an agnostic way. + * + * If the space is a CMS space then the intent is updated in the SPObject. + */ +void DocumentCMS::setRenderingIntent(std::string const &name, RenderingIntent intent) +{ + if (auto cp = getColorProfileForSpace(name)) { + cp->setRenderingIntent(intent); + _document->ensureUpToDate(); + } +} + +/** + * Generate a list of CMS spaces linked in this tracker + */ +std::vector> DocumentCMS::getSpaces() const +{ + std::vector> ret; + for (auto &link : _links) { + if (link->space) { + ret.push_back(link->space); + } + } + return ret; +} + +/** + * Generate a list of SP-objects linked in this tracker + */ +std::vector DocumentCMS::getObjects() const +{ + std::vector ret; + for (auto &link : _links) { + if (link->cp) { + ret.push_back(link->cp); + } + } + return ret; +} + +} // namespace Inkscape::Colors + +/* + 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 : diff --git a/src/colors/document-cms.h b/src/colors/document-cms.h new file mode 100644 index 0000000000000000000000000000000000000000..0361951c139a2aea9f8e4aecfd410a123163c0b4 --- /dev/null +++ b/src/colors/document-cms.h @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Colors::DocumentCMS - Look after a document's icc profiles and keep + * track of all the colors in use and their color spaces. + * + * Copyright 2023 Martin Owens + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_DOCUMENTCMS_H +#define SEEN_COLORS_DOCUMENTCMS_H + +#include +#include +#include +#include +#include + +#include "color.h" +#include "helper/auto-connection.h" + +class SPDocument; + +namespace Inkscape { +class ColorProfile; +enum class ColorProfileStorage; + +namespace Colors { +namespace CMS { +class Profile; +} +namespace Space { +enum class Type; +class AnySpace; +class CMS; +} // namespace Space +class ColorProfileLink; +enum class RenderingIntent; + +class DocumentCMS +{ +public: + DocumentCMS() = delete; + ~DocumentCMS(); + + DocumentCMS(DocumentCMS const &) = delete; + void operator=(DocumentCMS const &) = delete; + + DocumentCMS(SPDocument *document); + + std::optional parse(char const *value) const; + std::optional parse(std::string const &value) const; + + std::shared_ptr addProfileURI(std::string uri, std::string name, RenderingIntent intent); + std::shared_ptr addProfile(std::shared_ptr profile, std::string name, + RenderingIntent intent); + void removeProfile(std::shared_ptr space); + + sigc::connection connectChanged(const sigc::slot &slot) { return _changed_signal.connect(slot); } + sigc::connection connectModified(const sigc::slot)> &slot) + { + return _modified_signal.connect(slot); + } + + void attachProfileToDoc(std::string const &lookup, ColorProfileStorage storage, RenderingIntent intent, + std::string name = ""); + void setRenderingIntent(std::string const &name, RenderingIntent intent); + + std::shared_ptr getSpace(std::string const &name) const; + + ColorProfile *getColorProfileForSpace(std::string const &name) const; + ColorProfile *getColorProfileForSpace(std::shared_ptr space) const; + + std::vector> getSpaces() const; + std::vector getObjects() const; + +private: + void refreshResources(); + + SPDocument *_document = nullptr; + std::vector> _links; + + Inkscape::auto_connection _resource_connection; + sigc::signal _changed_signal; + + mutable std::map> _spaces; + +protected: + friend class ColorProfileLink; + + sigc::signal)> _modified_signal; +}; + +} // namespace Colors +} // namespace Inkscape + +#endif // SEEN_COLORS_DOCUMENTCMS_H + +/* + 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 : diff --git a/src/colors/dragndrop.cpp b/src/colors/dragndrop.cpp new file mode 100644 index 0000000000000000000000000000000000000000..350b4fdda2465aaef0cde325b6a7beeeaff73301 --- /dev/null +++ b/src/colors/dragndrop.cpp @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Jon A. Cruz + * Martin Owens + * + * Copyright (C) 2009-2023 author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "dragndrop.h" + +#include +#include + +#include "colors/color.h" + +namespace Inkscape::Colors { + +/** + * Convert a paint into a draggable object. + */ +std::vector getMIMEData(Paint const &paint, char const *mime_type) +{ + // XML Handles all types of paint + if (std::strcmp(mime_type, mimeOSWB_COLOR) == 0) { + auto const xml = paint_to_xml_string(paint); + return {xml.begin(), xml.end()}; + } + + // Handle NoColor first + if (std::holds_alternative(paint)) { + if (std::strcmp(mime_type, mimeTEXT) == 0) + return {'n', 'o', 'n', 'e'}; + if (std::strcmp(mime_type, mimeX_COLOR) == 0) + return std::vector(8); // transparent black + return {}; + } + + auto &color = std::get(paint); + if (std::strcmp(mime_type, mimeTEXT) == 0) { + auto const str = color.toString(); + return {str.begin(), str.end()}; + } else if (std::strcmp(mime_type, mimeX_COLOR) == 0) { + // X-color is only ever in RGBA + auto const rgb = color.toRGBA(); + return { + (char)SP_RGBA32_R_U(rgb), (char)SP_RGBA32_R_U(rgb), (char)SP_RGBA32_G_U(rgb), (char)SP_RGBA32_G_U(rgb), + (char)SP_RGBA32_B_U(rgb), (char)SP_RGBA32_B_U(rgb), (char)SP_RGBA32_A_U(rgb), (char)SP_RGBA32_A_U(rgb), + }; + } + return {}; +} + +/** + * Convert a dropped object into a color, if possible. + */ +Paint fromMIMEData(std::span data, char const *mime_type) +{ + if (std::strcmp(mime_type, mimeX_COLOR) == 0) { + if (data.size() != 8) + throw ColorError("Data is the wrong size for color mime type"); + return Color{SP_RGBA32_U_COMPOSE(data[0], data[2], data[4], data[6])}; + } + + auto const str = std::string{data.data(), data.size()}; + if (std::strcmp(mime_type, mimeTEXT) == 0) { + if (str == "none") { + return NoColor(); + } + if (auto color = Color::parse(str)) { + return *color; + } + } + if (std::strcmp(mime_type, mimeOSWB_COLOR) == 0) { + return xml_string_to_paint(str, nullptr); + } + throw ColorError("Unknown color data found"); +} + +} // namespace Inkscape::Colors + +/* + 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 : diff --git a/src/colors/dragndrop.h b/src/colors/dragndrop.h new file mode 100644 index 0000000000000000000000000000000000000000..d7a30ea6c9e4215442ee5c3c8d8ed9cc8fdc1226 --- /dev/null +++ b/src/colors/dragndrop.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Jon A. Cruz + * Martin Owens + * + * Copyright (C) 2009-2023 author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_COLORS_DRAGNDROP +#define INKSCAPE_COLORS_DRAGNDROP + +#include +#include +#include + +#include "xml-color.h" + +namespace Inkscape::Colors { + +inline constexpr auto mimeOSWB_COLOR = "application/x-oswb-color"; +inline constexpr auto mimeX_COLOR = "application/x-color"; +inline constexpr auto mimeTEXT = "text/plain"; + +std::vector getMIMEData(Paint const &paint, char const *mime_type); +Paint fromMIMEData(std::span data, char const *mime_type); + +} // namespace Inkscape::Colors + +#endif // INKSCAPE_COLORS_DRAGNDROP + +/* + 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 : diff --git a/src/colors/manager.cpp b/src/colors/manager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..74569d04257ceb43a5cc5d260189d88044c8b957 --- /dev/null +++ b/src/colors/manager.cpp @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Manager - Look after all a document's icc profiles. + * + * Copyright 2023 Martin Owens + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "manager.h" + +#include +#include + +#include "colors/color.h" +#include "colors/parser.h" +#include "document.h" + +// Each Internal space should be imported here. +#include "spaces/cms.h" +#include "spaces/cmyk.h" +#include "spaces/gray.h" +#include "spaces/hsl.h" +#include "spaces/hsluv.h" +#include "spaces/hsv.h" +#include "spaces/lab.h" +#include "spaces/lch.h" +#include "spaces/linear-rgb.h" +#include "spaces/luv.h" +#include "spaces/named.h" +#include "spaces/okhsl.h" +#include "spaces/oklab.h" +#include "spaces/oklch.h" +#include "spaces/rgb.h" +#include "spaces/xyz.h" + +namespace Inkscape::Colors { + +Manager::Manager() +{ + // Regular SVG 1.1 Colors + addSpace(new Space::RGB()); + addSpace(new Space::NamedColor()); + + // Color module 4 and 5 support + addSpace(new Space::DeviceCMYK()); + addSpace(new Space::Gray()); + addSpace(new Space::HSL()); + addSpace(new Space::HSLuv()); + addSpace(new Space::HSV()); + addSpace(new Space::Lab()); + addSpace(new Space::LinearRGB()); + addSpace(new Space::Lch()); + addSpace(new Space::Luv()); + addSpace(new Space::OkHsl()); + addSpace(new Space::OkLab()); + addSpace(new Space::OkLch()); + addSpace(new Space::XYZ()); +} + +/** + * Add the given space and assume ownership over it. + */ +std::shared_ptr Manager::addSpace(Space::AnySpace *space) +{ + if (find(space->getType())) { + throw ColorError("Can not add the same color space twice."); + } + _spaces.emplace_back(space); + return _spaces.back(); +} + +/** + * Removes the given space from the list of available spaces. + */ +bool Manager::removeSpace(std::shared_ptr space) +{ + return std::erase(_spaces, space); +} + +/** + * Finds the first global color space matching the given type + * + * @arg type - The type enum to match + */ +std::shared_ptr Manager::find(Space::Type type) const +{ + auto it = std::find_if(_spaces.begin(), _spaces.end(), [type](auto &v) { return v->getType() == type; }); + return it != _spaces.end() ? *it : nullptr; +} + +/** + * Finds the global space matching the given name + * + * @arg name - The name of the space as given by getName + */ +std::shared_ptr Manager::find(std::string const &name) const +{ + auto it = std::find_if(_spaces.begin(), _spaces.end(), [name](auto &v) { return v->getName() == name; }); + return it != _spaces.end() ? *it : nullptr; +} +} // namespace Inkscape::Colors + +/* + 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 : diff --git a/src/colors/manager.h b/src/colors/manager.h new file mode 100644 index 0000000000000000000000000000000000000000..b4434a8eea3df28fc75b920c1cf629a6783d698a --- /dev/null +++ b/src/colors/manager.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Colors::Manager - Look after all a document's icc profiles. + * + * Copyright 2023 Martin Owens + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_MANAGER_H +#define SEEN_COLORS_MANAGER_H + +#include +#include +#include +#include + +namespace Inkscape::Colors { +namespace Space { +enum class Type; +class AnySpace; +} // namespace Space +class Color; +class Parser; + +class Manager +{ +private: + Manager(Manager const &) = delete; + void operator=(Manager const &) = delete; + +public: + + static Manager &get() + { + static Manager instance; + return instance; + } + + std::shared_ptr find(Space::Type type) const; + std::shared_ptr find(std::string const &name) const; + +protected: + Manager(); + ~Manager() = default; + + std::shared_ptr addSpace(Space::AnySpace *space); + bool removeSpace(std::shared_ptr space); + +private: + std::vector> _spaces; +}; + +} // namespace Inkscape::Colors + +#endif // SEEN_COLORS_MANAGER_H + +/* + 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 : diff --git a/src/colors/parser.cpp b/src/colors/parser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eb68de53fc4ccb0e5be5392ef70f916dfa4026f1 --- /dev/null +++ b/src/colors/parser.cpp @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 AUTHORS + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parser.h" + +#include +#include + +#include "spaces/cms.h" +#include "spaces/cmyk.h" +#include "spaces/gray.h" +#include "spaces/hsl.h" +#include "spaces/hsluv.h" +#include "spaces/hsv.h" +#include "spaces/lab.h" +#include "spaces/lch.h" +#include "spaces/linear-rgb.h" +#include "spaces/luv.h" +#include "spaces/named.h" +#include "spaces/okhsl.h" +#include "spaces/oklab.h" +#include "spaces/oklch.h" +#include "spaces/rgb.h" +#include "spaces/xyz.h" +#include "utils.h" + +namespace Inkscape::Colors { + +Parsers::Parsers() +{ + addParser(new HexParser()); + addParser(new Space::NamedColor::NameParser()); + addParser(new Space::CMS::CmsParser()); + addParser(new Space::RGB::Parser(false)); + addParser(new Space::RGB::Parser(true)); + addParser(new Space::HSL::Parser(false)); + addParser(new Space::HSL::Parser(true)); + addParser(new Space::HSV::fromHwbParser(false)); + addParser(new Space::HSV::fromHwbParser(true)); + addParser(new Space::Lab::Parser()); + addParser(new Space::Lch::Parser()); + addParser(new Space::OkLab::Parser()); + addParser(new Space::OkLch::Parser()); + addParser(new CssParser("srgb", Space::Type::RGB, 3)); + addParser(new CssParser("srgb-linear", Space::Type::linearRGB, 3)); + addParser(new CssParser("device-cmyk", Space::Type::CMYK, 4)); + addParser(new CssParser("xyz", Space::Type::XYZ, 3)); +} + +/** + * Turn a string into a color data, used in Color object creation. + * + * Each available color parser will be asked to parse the color in turn + * and the successful parser will return object data. + * + * @arg input - The string to parse. + * @retval type - The type (enum) of the color space to understand the values + * @retval cms - The name of the cms color space to understand the values + * @retval values - A list of values for use with the color space. + * @retval fallback - A returned list of values for fallback colors. + * + * @returns true if the color was parsed. + */ +bool Parsers::parse(std::string const &input, Space::Type &type, std::string &cms, std::vector &values, + std::vector &fallback) const +{ + std::istringstream ss(input); + return _parse(ss, type, cms, values, fallback); +} + +/** + * Internal recursive parser that scans through a string stream. + * + * @arg ss - The string stream, parsing starts at it's set position + * @retval type - The type (enum) of the color space to understand the values + * @retval cms - The return name of the color space to understand the values + * @retval values - A returned list of values for use with the color space. + * @retval fallback - A returned list of values for the fallback (sRGB) color + * + * @returns true if the color was parsed. + */ +bool Parsers::_parse(std::istringstream &ss, Space::Type &type, std::string &name, std::vector &values, + std::vector &fallback) const +{ + auto ptype = Parser::getCssPrefix(ss); + auto iter = _parsers.find(ptype); + if (iter == _parsers.end()) + return false; + + for (auto &parser : iter->second) { + auto pos = ss.tellg(); + bool more = false; + values.clear(); + + name = parser->parseColor(ss, values, more); + + // We have an RGB color but there's more string, look for an icc-color next. + if (more && ptype == "#") { + std::vector icc_values; + if (_parse(ss, type, name, icc_values, fallback)) { + if (type == Space::Type::CMS) { + fallback = std::move(values); + values = std::move(icc_values); + return true; + } + } + } + + if (!values.empty()) { + type = parser->getType(); + return true; + } + + ss.clear(); + ss.seekg(pos); + } + return false; +} + +/** + * Add a prser to the list of parser objects used when parsing color strings. + */ +void Parsers::addParser(Parser *parser) +{ + // Map parsers by their expected prefix for quicker lookup + auto const [it, inserted] = _parsers.emplace(parser->getPrefix(), std::vector>{}); + it->second.emplace_back(parser); +} + +/** + * Parse this specific color format into output values + * + * @arg ss - The stream stream to parse + * @arg output - The returned list of values + * @arg more - Indicates if there is more string to parse. + * + * @returns The type of the space found by this parser (if any). + */ +std::string Parser::parseColor(std::istringstream &ss, std::vector &output, bool &more) const +{ + if (!parse(ss, output, more)) { + output.clear(); + } + return ""; +} + +bool HueParser::parse(std::istringstream &ss, std::vector &output) const +{ + bool end = false; + return append_css_value(ss, output, end, ',', 360) && append_css_value(ss, output, end, ',') && + append_css_value(ss, output, end, !_alpha ? '/' : ',') && (append_css_value(ss, output, end) || !_alpha) && + end; +} + +/** + * Parse either a hex code or an rgb() css string. + */ +bool HexParser::parse(std::istringstream &ss, std::vector &output, bool &more) const +{ + unsigned int hex; + unsigned int size = 0; + + size = ss.tellg(); + ss >> std::hex >> hex; + // This mess is required because istream countg is inconsistant + size = (ss.tellg() == -1 ? ss.str().size() : (int)ss.tellg()) - size; + + if (size == 3 || size == 4) { // #rgb(a) + for (int p = (4 * (size - 1)); p >= 0; p -= 4) { + auto val = ((hex & (0xf << p)) >> p); + output.emplace_back((val + (val << 4)) / 255.0); + } + } else if (size == 6 || size == 8) { // #rrggbb(aa) + if (size == 6) + hex <<= 8; + output.emplace_back(SP_RGBA32_R_F(hex)); + output.emplace_back(SP_RGBA32_G_F(hex)); + output.emplace_back(SP_RGBA32_B_F(hex)); + if (size == 8) + output.emplace_back(SP_RGBA32_A_F(hex)); + } + ss >> std::ws; + more = (ss.peek() == 'i'); // icc-color is next + return !output.empty(); +} + +/** + * Prase the given string stream as a CSS Color Module Level 4/5 string. + */ +bool CssParser::parse(std::istringstream &ss, std::vector &output) const +{ + bool end = false; + while (!end && output.size() < _channels + 1) { + if (!append_css_value(ss, output, end, output.size() == _channels - 1 ? '/' : 0x0)) + break; + } + return end; +} + +/** + * Parse CSS color numbers after the function name + * + * @arg ss - The string stream to read + * + * @returns - The color prefix or color name detected in this color function. + * either the first part of the function, for example rgb or hsla + * or the first variable in the case of color(), icc-color() and var() + */ +std::string Parser::getCssPrefix(std::istringstream &ss) +{ + std::string token; + ss >> std::ws; + if (ss.peek() == '#') { + return {(char)ss.get()}; + } + auto pos = ss.tellg(); + if (!std::getline(ss, token, '(') || ss.eof()) { + ss.seekg(pos); + return ""; // No paren + } + + if (token == "color") { + // CSS Color module 4 color() function + ss >> token; + } + return token; +} + +/** + * Parse a CSS color number after the function name + * + * @arg ss - The string stream to read + * @returns value - The value read in without adjustment + * @returns unit - The unit, ususally an empty string + * @returns end - True if this is the end of the css function + * @arg sep - An optional seperator argument + * + * @returns true if a number and unit was parsed correctly. + */ +bool Parser::css_number(std::istringstream &ss, double &value, std::string &unit, bool &end, char const sep) +{ + ss.imbue(std::locale("C")); + + /* + * LLVM uses libc++ and this C library has a broken double parser compared to GCC/libstdc++ + * So we have written our own double parser to get around the limitations for APPLE + * + * Detailed description can be found at: + * https://github.com/tardate/LittleCodingKata/blob/main/cpp/DoubleTrouble/README.md + */ +#ifdef __APPLE__ + bool parsed = false; + bool dot = false; + + ss >> std::ws; + std::string result; + while (true) { + auto c = ss.peek(); + + if (c == '-' && result.empty()) { + result += ss.get(); + } else if (c == '.') { + if (dot) + break; + dot = true; + result += ss.get(); + } else if (c >= '0' && c <= '9') { + result += ss.get(); + parsed = true; + } else { + break; + } + } + if (parsed) { + value = std::stod(result); + } +#else + bool parsed = (bool)(ss >> value); +#endif + if (!parsed) { + ss.clear(); + return false; + } + + unit.clear(); + auto c = ss.peek(); + if (c == '.' || (c >= '0' && c <= '9')) { + return true; + } + while (ss && (c = ss.get())) { + if (c == ')') { + end = true; + break; + } else if (c == sep) { + break; + } + if (c == ' ') { + auto p = ss.peek(); + if (p != ' ' && p != sep && p != ')') { + break; + } + } else { + unit += c; + } + } + return true; +} + +/** + * Parse a CSS color number and format it according to it's unit. + * + * @arg ss - The string stream set at the location of the next number. + * @arg output - The vector to append the new number to. + * @arg end - Is set to true if this number is the last one. + * @arg sep - The separator to expect after this number (consumed) + * @arg scale - The default scale of the number of no unit is detected. + * + * @returns True if a number was found and appended. + */ +bool Parser::append_css_value(std::istringstream &ss, std::vector &output, bool &end, char const sep, + double scale) +{ + double value; + std::string unit; + if (!end && css_number(ss, value, unit, end, sep)) { + if (unit == "%") { + value /= 100; + } else if (unit == "deg") { + value /= 360; + } else if (unit == "turn") { + // no need to modify + } else if (!unit.empty()) { + std::cerr << "Unknown unit in css color parsing '" << unit.c_str() << "'\n"; + return false; + } else { + value /= scale; + } + output.emplace_back(value); + return true; + } + return false; +} + +}; // namespace Inkscape::Colors + +/* + 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 : diff --git a/src/colors/parser.h b/src/colors/parser.h new file mode 100644 index 0000000000000000000000000000000000000000..2a526e32bf5beee0a5129784efe4855f4ad0cbe3 --- /dev/null +++ b/src/colors/parser.h @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 AUTHORS + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_COLORS_PARSING_H +#define SEEN_COLORS_PARSING_H + +#include +#include +#include +#include + +#include "spaces/enum.h" + +namespace Inkscape::Colors { + +class Parser +{ +public: + Parser(std::string prefix, Space::Type type) + : _prefix(std::move(prefix)) + , _type(type) + {} + virtual ~Parser() = default; + + Space::Type getType() const { return _type; } + std::string const getPrefix() const { return _prefix; } + static std::string getCssPrefix(std::istringstream &ss); + static bool css_number(std::istringstream &ss, double &value, std::string &unit, bool &end, char const sep = 0x0); + static bool append_css_value(std::istringstream &ss, std::vector &output, bool &end, char const sep = 0x0, + double scale = 1.0); + + // TODO message: return reason for parsing failure. Otherwise we cannot tell the user why we reject certain colors. + virtual bool parse(std::istringstream &ss, std::vector &output) const { return false; }; + virtual bool parse(std::istringstream &ss, std::vector &output, bool &more) const + { + return parse(ss, output); + }; + virtual std::string parseColor(std::istringstream &ss, std::vector &output, bool &more) const; + +private: + std::string _prefix; + Space::Type _type; +}; + +class LegacyParser : public Parser +{ +public: + LegacyParser(std::string const &prefix, Space::Type type, bool alpha) + : Parser(alpha ? prefix + "a" : prefix, type) + , _alpha(alpha) + {} + +protected: + bool _alpha = false; +}; + +class HueParser : public LegacyParser +{ +public: + HueParser(std::string const &prefix, Space::Type type, bool alpha) + : LegacyParser(prefix, type, alpha) + {} + bool parse(std::istringstream &ss, std::vector &output) const override; +}; + +class HexParser : public Parser +{ +public: + HexParser() + : Parser("#", Space::Type::RGB) + {} + bool parse(std::istringstream &input, std::vector &output, bool &more) const override; +}; + +class CssParser : public Parser +{ +public: + CssParser(std::string prefix, Space::Type type, unsigned int channels) + : Parser(prefix, type) + , _channels(channels) + {} + +private: + bool parse(std::istringstream &ss, std::vector &output) const override; + + unsigned int _channels; +}; + +class Parsers +{ +private: + Parsers(Parsers const &) = delete; + void operator=(Parsers const &) = delete; + +public: + Parsers(); + ~Parsers() = default; + + static Parsers &get() + { + static Parsers instance; + return instance; + } + + bool parse(std::string const &input, Space::Type &type, std::string &cms, std::vector &values, + std::vector &fallback) const; + +protected: + friend class Manager; + + void addParser(Parser *parser); + +private: + bool _parse(std::istringstream &ss, Space::Type &type, std::string &cms, std::vector &values, + std::vector &fallback) const; + + std::map>> _parsers; +}; + +} // namespace Inkscape::Colors + +#endif // SEEN_COLORS_PARSING_H diff --git a/src/colors/printer.cpp b/src/colors/printer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5412a7b72c46985f51ea628584659e2123b15a7d --- /dev/null +++ b/src/colors/printer.cpp @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 AUTHORS + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "printer.h" + +namespace Inkscape::Colors { + +CssPrinter &CssPrinter::operator<<(double value) +{ + if (!_done) { + if (_count == _channels && _slash_opacity) { + // We print opacity as a percentage + *this << " / " << (int)(value * 100) << "%"; + } else if (_count < _channels) { + std::ostringstream oo; + oo.imbue(getloc()); + oo.precision(precision()); + oo << std::fixed << value; + + // To bypass the lack of significant-figures option we strip trailing zeros in a sub-process. + auto number = oo.str(); + while (number.find(".") != std::string::npos && + (number.substr(number.length() - 1, 1) == "0" || number.substr(number.length() - 1, 1) == ".")) + number.pop_back(); + + *this << (_count ? _sep : "") << number; + } + _count++; + } + return *this; +} + +CssPrinter &CssPrinter::operator<<(int value) +{ + if (!_done && _count < _channels) { + *this << (_count ? _sep : "") << value; + _count++; + } + return *this; +} + +CssPrinter &CssPrinter::operator<<(std::vector const &values) +{ + for (auto &value : values) { + if (_count < _channels) + *this << value; + } + return *this; +} + +}; // namespace Inkscape::Colors + +/* + 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 : diff --git a/src/colors/printer.h b/src/colors/printer.h new file mode 100644 index 0000000000000000000000000000000000000000..32fded543a4c0f7aefa1fda834307bf34b5c9786 --- /dev/null +++ b/src/colors/printer.h @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 AUTHORS + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_COLORS_PRINTER_H +#define SEEN_COLORS_PRINTER_H + +#include +#include +#include +#include +#include + +namespace Inkscape::Colors { + +class CssPrinter : public std::ostringstream +{ +public: + CssPrinter(unsigned channels, std::string prefix, std::string ident = "", std::string sep = " ") + : _channels(channels) + , _sep(std::move(sep)) + { + imbue(std::locale("C")); + precision(3); + *this << prefix << "("; + if (!ident.empty()) { + *this << ident; + _count = 1; + _channels += 1; + } + } + + CssPrinter &operator<<(double); + CssPrinter &operator<<(int); + CssPrinter &operator<<(std::vector const &); + + operator std::string() + { + if (!_done) { + _done = true; + *this << ")"; + } + if (_count < _channels) { + std::cerr << " Expected " << _channels << " channels but got " << _count << "\n"; + return ""; + } + return str(); + } + +protected: + bool _slash_opacity = false; + bool _done = false; + unsigned _count = 0; + unsigned _channels = 0; + std::string _sep; +}; + +class IccColorPrinter : public CssPrinter +{ +public: + IccColorPrinter(unsigned channels, std::string ident) + : CssPrinter(channels, "icc-color", std::move(ident), ", ") + {} +}; + +class CssLegacyPrinter : public CssPrinter +{ +public: + CssLegacyPrinter(unsigned channels, std::string prefix, bool opacity) + : CssPrinter(channels + (int)opacity, prefix + (opacity ? "a" : ""), "", ", ") + {} +}; + +class CssFuncPrinter : public CssPrinter +{ +public: + CssFuncPrinter(unsigned channels, std::string prefix) + : CssPrinter(channels, std::move(prefix)) + { + _slash_opacity = true; + } +}; + +class CssColorPrinter : public CssPrinter +{ +public: + CssColorPrinter(unsigned channels, std::string ident) + : CssPrinter(channels, "color", std::move(ident)) + { + _slash_opacity = true; + } +}; + +} // namespace Inkscape::Colors + +#endif // SEEN_COLORS_PRINTER_H diff --git a/src/colors/spaces/base.cpp b/src/colors/spaces/base.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1537e7cc71cf2bdfcde5983a571d1dc066a928c0 --- /dev/null +++ b/src/colors/spaces/base.cpp @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Manage color spaces + *//* + * Authors: see git history + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "base.h" + +#include + +#include "colors/cms/profile.h" +#include "colors/cms/transform.h" +#include "colors/color.h" +#include "components.h" + +namespace Inkscape::Colors::Space { + +AnySpace::AnySpace() +{ + if (!srgb_profile) { + srgb_profile = Colors::CMS::Profile::create_srgb(); + } +} + +/** + * Return true if the given data would be valid for this color space. + */ +bool AnySpace::isValidData(std::vector const &values) const +{ + auto const n_values = values.size(); + auto const n_space = getComponentCount(); + return n_values == n_space || n_values == n_space + 1; +} + +/** + * In place conversion of a color object to the given space. + * + * This three part conversion may not mutate the input at all, depending on + * the space it's already in and the format of the data. + */ +bool AnySpace::convert(std::vector &io, std::shared_ptr to_space) const +{ + // Firstly convert from the formatted values (i.e. hsl) into the profile values (i.e. sRGB) + spaceToProfile(io); + // Secondly convert the color profile itself using lcms2 if the profiles are different + if (profileToProfile(io, to_space)) { + // Thirdly convert to the formatted values (i.e. hsl) from the profile values (i.e. sRGB) + to_space->profileToSpace(io); + return true; + } + profileToSpace(io); + return false; +} + +/** + * Convert from the space's format, to the profile's data format. + */ +void AnySpace::spaceToProfile(std::vector &io) const {} + +/** + * Convert from the profile's format, to the space's data format. + */ +void AnySpace::profileToSpace(std::vector &io) const {} + +/** + * Step two in coverting a color, convert it's profile to another profile (if needed) + */ +bool AnySpace::profileToProfile(std::vector &io, std::shared_ptr to_space) const +{ + auto from_profile = getProfile(); + auto to_profile = to_space->getProfile(); + if (*to_profile == *from_profile) + return true; + + // Choose best rendering intent, first ours, then theirs, finally a default + auto intent = getIntent(); + if (intent == RenderingIntent::UNKNOWN) + intent = to_space->getIntent(); + if (intent == RenderingIntent::UNKNOWN) + intent = RenderingIntent::PERCEPTUAL; + + // Look in the transform cache for the color profile + auto to_profile_id = to_profile->getChecksum() + intentIds[intent]; + + if (!_transforms.contains(to_profile_id)) { + // Create a new transform for this one way profile-pair + _transforms.emplace(to_profile_id, Colors::CMS::Transform::create_for_cms(from_profile, to_profile, intent)); + } + + // Use the transform to convert the output colors. + if (auto &tr = _transforms[to_profile_id]) { + return tr->do_transform(io); + } + return false; +} + +/** + * Return true if the color would be out of gamut in the target color space. + * + * NOTE: This can NOT work if the base color spaces are exactly the same. i.e. device-cmyk(sRGB) + * will always return false despite not being reversable with RGB (which is also sRGB). + * + * If you want gamut checking via lcms2, you must use different icc profiles. + * + * @arg input - Channel values in this space. + * @arg to_space - The target space to compare against. + */ +bool AnySpace::outOfGamut(std::vector const &input, std::shared_ptr to_space) const +{ + auto from_profile = getProfile(); + auto to_profile = to_space->getProfile(); + if (*to_profile == *from_profile) + return false; + // 1. Look in the checker cache for the color profile + auto to_profile_id = to_profile->getId(); + if (_gamut_checkers.find(to_profile_id) == _gamut_checkers.end()) { + // 2. Create a new transform for this one way profile-pair + _gamut_checkers.emplace(to_profile_id, + Colors::CMS::Transform::create_for_cms_checker(from_profile, to_profile)); + } + + return _gamut_checkers[to_profile_id]->check_gamut(input); +} + +/** + * Return a list of Component objects, in order of the channels in this color space + * + * @arg alpha - If true, returns the alpha component as well + */ +Components const &AnySpace::getComponents(bool alpha) const +{ + return Space::Components::get(getComponentType(), alpha); +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/base.h b/src/colors/spaces/base.h new file mode 100644 index 0000000000000000000000000000000000000000..e9678f9fa0794e54ff538c8314b64a150def8028 --- /dev/null +++ b/src/colors/spaces/base.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_BASE_H +#define SEEN_COLORS_SPACES_BASE_H + +#include +#include +#include +#include +#include + +#include "colors/parser.h" +#include "enum.h" + +constexpr double SCALE_UP(double v, double a, double b) +{ + return (v * (b - a)) + a; +} +constexpr double SCALE_DOWN(double v, double a, double b) +{ + return (v - a) / (b - a); +} + +namespace Inkscape::Colors { +namespace CMS { +class Profile; +class Transform; +} // namespace CMS +class Color; +class Manager; + +namespace Space { + +class Components; + +class AnySpace +{ +public: + virtual ~AnySpace() = default; + + bool operator==(AnySpace const &other) const { return other.getName() == getName(); } + bool operator!=(AnySpace const &other) const { return !(*this == other); }; + + virtual Type getType() const = 0; + virtual std::string const getName() const = 0; + virtual std::string const getIcon() const = 0; + virtual Type getComponentType() const { return getType(); } + virtual unsigned int getComponentCount() const = 0; + virtual std::shared_ptr const getProfile() const = 0; + virtual RenderingIntent getIntent() const { return RenderingIntent::UNKNOWN; } + + Components const &getComponents(bool alpha = false) const; + std::string const getPrefsPath() const { return "/colorselector/" + getName() + "/"; } + + virtual bool isValid() const { return true; } + +protected: + friend class Colors::Color; + + AnySpace(); + bool isValidData(std::vector const &values) const; + virtual std::vector getParsers() const { return {}; } + virtual std::string toString(std::vector const &values, bool opacity = true) const = 0; + virtual uint32_t toRGBA(std::vector const &values, double opacity = 1.0) const = 0; + + bool convert(std::vector &io, std::shared_ptr to_space) const; + bool profileToProfile(std::vector &io, std::shared_ptr to_space) const; + virtual void spaceToProfile(std::vector &io) const; + virtual void profileToSpace(std::vector &io) const; + virtual bool overInk(std::vector const &input) const { return false; } + + std::shared_ptr srgb_profile; + + bool outOfGamut(std::vector const &input, std::shared_ptr to_space) const; + +private: + mutable std::map> _transforms; + mutable std::map> _gamut_checkers; +}; + +} // namespace Space +} // namespace Inkscape::Colors + +#endif // SEEN_COLORS_SPACES_BASE_H diff --git a/src/colors/spaces/cms.cpp b/src/colors/spaces/cms.cpp new file mode 100644 index 0000000000000000000000000000000000000000..75c537e8bfc2d2ab501d604aa3f355ffa22c910c --- /dev/null +++ b/src/colors/spaces/cms.cpp @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: see git history + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "cms.h" + +#include + +#include "colors/cms/profile.h" +#include "colors/cms/transform.h" +#include "colors/color.h" +#include "colors/manager.h" +#include "colors/printer.h" + +namespace Inkscape::Colors::Space { + +// When we support a color space that lcms2 does not, record here +static cmsUInt32Number customSigOKLabData = 0x4f4b4c42; // 'OKLB'; + +static std::map _lcmssig_to_space = { + {cmsSigRgbData, Space::Type::RGB}, {cmsSigHlsData, Space::Type::HSL}, + {cmsSigCmykData, Space::Type::CMYK}, {cmsSigCmyData, Space::Type::CMY}, + {cmsSigHsvData, Space::Type::HSV}, {cmsSigLuvData, Space::Type::HSLUV}, + {customSigOKLabData, Space::Type::OKLAB}, {cmsSigXYZData, Space::Type::XYZ}, + {cmsSigXYZData, Space::Type::YXY}, {cmsSigLabData, Space::Type::LAB}, + {cmsSigYCbCrData, Space::Type::YCbCr}, {cmsSigGrayData, Space::Type::Gray}, +}; + +CMS::CMS(std::shared_ptr profile) + : AnySpace() + , _profile_name(profile->getName(true)) + , _profile_size(profile->getSize()) + , _profile_type(_lcmssig_to_space[profile->getColorSpace()]) + , _profile(profile) +{} + +/** + * Naked CMS space for testing and data retention where the profile is unavailable. + */ +CMS::CMS(std::string profile_name, unsigned profile_size, Space::Type profile_type) + : AnySpace() + , _profile_name(std::move(profile_name)) + , _profile_size(profile_size) + , _profile_type(profile_type) + , _profile(nullptr) +{} + +/** + * Return the profile for this cms space. If this is anonymous, it returns srgb + * so the transformation on the fallback color is transparent. + */ +std::shared_ptr const CMS::getProfile() const +{ + if (!isValid()) + return srgb_profile; + return _profile; +} + +/** + * If this space lacks a profile, it's really the sRGB fallback values, + * so we strip out any other values from the io. + */ +void CMS::spaceToProfile(std::vector &io) const +{ + if (isValid()) + return; // Do nothing for valid spaces + + bool has_opacity = io.size() == _profile_size + 4; + + // Keep deleting the icc color values + while (io.size() > (3 + has_opacity)) { + io.erase(io.begin() + 3); + } +} + +/** + * Get the number of components for this cms color space. + * + * If the color space is not valid, three extra channels are + * used to hold the red green and blue values. + */ +unsigned int CMS::getComponentCount() const +{ + return _profile ? _profile_size : _profile_size + 3; +} + +/** + * Parse a string stream into a vector of doubles which are always values in + * this CMS space / icc profile. + * + * @args ss - String input which doesn't have to be at the start of the string. + * @returns output - The vector to populate with the numbers found in the string. + * @returns the name of the cms profile requested. + */ +std::string CMS::CmsParser::parseColor(std::istringstream &ss, std::vector &output, bool &more) const +{ + std::string icc_name; + ss >> icc_name; + + if (!icc_name.empty() && icc_name.back() == ',') + icc_name.pop_back(); + + bool end = false; + while (!end && append_css_value(ss, output, end, ',')) + continue; + + if (output.size() == 0) { + std::string named; + ss >> named; + if (!named.empty() && ss.get() == ')') { + std::cerr << "Found SVG2 ICC named color '" << named.c_str() << "' for profile '" << icc_name.c_str() + << "', which not supported yet.\n"; + } + } + + return icc_name; +} + +/** + * Output these values into this CMS space. + * + * @args values - The values for each channel in the icc profile. + * @args opacity - Should opacity be included. This is ignored since cms + * output is ALWAYS without opacity. + * + * @returns the string suitable for css and style use. + */ +std::string CMS::toString(std::vector const &values, bool /*opacity*/) const +{ + if (values.size() < _profile_size) + return ""; + + // RGBA Hex fallback plus icc-color section + auto oo = IccColorPrinter(_profile_size, _profile_name); + + // When an icc color was parsed, but there is no profile + if (!isValid()) { + // Not enough values for a fallback option (maybe corrupt?) + if (values.size() < _profile_size + 3) + return ""; + std::vector remains(values.begin() + 3, values.end()); + oo << remains; + } else { + oo << values; + } + // opacity is never added to the printer here, always ignored + return rgba_to_hex(toRGBA(values), false) + " " + (std::string)oo; +} + +/** + * Convert this CMS color into a simple sRGB based RGBA value. + * + * @arg values - The values for each channel in the icc profile + * @arg opacity - An extra opacity to mix into the output. + * + * @returns An integer of the sRGB value. + */ +uint32_t CMS::toRGBA(std::vector const &values, double opacity) const +{ + if (!isValid()) { + if (values.size() == _profile_size + 3) + return SP_RGBA32_F_COMPOSE(values[0], values[1], values[2], opacity); + if (values.size() == _profile_size + 4) + return SP_RGBA32_F_COMPOSE(values[0], values[1], values[2], opacity * values.back()); + std::cerr << "Can not convert CMS color to sRGB, no profile available and no fallback color\n"; + return 0x0; + } + + static auto rgb = Manager::get().find(Space::Type::RGB); + std::vector copy = values; + if (convert(copy, rgb)) { + // The opacity will be copied during conversion (if present) so we only + // need to test if the rgb has opacity and add it on if it does. + if (copy.size() == rgb->getComponentCount() + 1) + opacity *= copy.back(); + + // CMS color channels never include opacity, it's not in the specification + return SP_RGBA32_F_COMPOSE(copy[0], copy[1], copy[2], opacity); + } + std::cerr << "Can not convert CMS color to sRGB.\n"; + return 0x0; +} + +/** + * Return true if, using a rough hueruistic, this color could be considered to be using + * too much ink if it was printed using the ink as specified. + * + * NOTE: This is only useful for CMYK profiles. Anything else will return false. + * + * @arg input - Channel values in this space. + */ +bool CMS::overInk(std::vector const &input) const +{ + if (!input.size() || getType() != Type::CMYK) + return false; + + /* Some literature states that when the sum of paint values exceed 320%, it is considered to be a satured color, + which means the paper can get too wet due to an excessive amount of ink. This may lead to several issues + such as misalignment and poor quality of printing in general.*/ + return input[0] + input[1] + input[2] + input[3] > 3.2; +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/cms.h b/src/colors/spaces/cms.h new file mode 100644 index 0000000000000000000000000000000000000000..fb1e5ef1f78979028dc296af1e904373223ce1ad --- /dev/null +++ b/src/colors/spaces/cms.h @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_CMS_H +#define SEEN_COLORS_SPACES_CMS_H + +#include "base.h" + +namespace Inkscape::Colors { +class DocumentCMS; +} + +namespace Inkscape::Colors::Space { + +// A class for containing the icc profile and the machinery for converting colors +class CMS : public AnySpace +{ +public: + CMS(std::shared_ptr profile); + CMS(std::string profile_name, unsigned profile_size, Type profile_type = Type::NONE); + ~CMS() override = default; + + Type getType() const override { return _profile_type; } + std::string const getName() const override { return _profile_name; } + std::string const getIcon() const override { return "color-selector-cms"; } + unsigned int getComponentCount() const override; + + std::shared_ptr const getProfile() const override; + RenderingIntent getIntent() const override { return _intent; } + void setIntent(RenderingIntent intent) { _intent = intent; } + + /** Returns false if this icc profile is not connected to any actual profile */ + bool isValid() const override { return (bool)_profile; } + + void spaceToProfile(std::vector &io) const override; +protected: + friend class Colors::Color; + friend class Colors::DocumentCMS; + + std::string toString(std::vector const &values, bool opacity = true) const override; + uint32_t toRGBA(std::vector const &values, double opacity = 1.0) const override; + + void setName(std::string name) { _profile_name = std::move(name); } + bool overInk(std::vector const &input) const override; + +private: + std::string _profile_name; + unsigned _profile_size; + Type _profile_type; + std::shared_ptr _profile; + RenderingIntent _intent = RenderingIntent::UNKNOWN; + +public: + class CmsParser : public Parser + { + public: + CmsParser() + : Parser("icc-color", Type::CMS) + {} + std::string parseColor(std::istringstream &input, std::vector &output, bool &more) const override; + }; +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_CMS_H diff --git a/src/colors/spaces/cmyk.cpp b/src/colors/spaces/cmyk.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a4ca9ea0e7b1efe4844b0c6b9b2c5aabac2d42e1 --- /dev/null +++ b/src/colors/spaces/cmyk.cpp @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * DeviceCMYK is NOT a color managed color space for ink values, for those + * please see the CMS icc profile based color spaces. + *//* + * Authors: see git history + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "cmyk.h" + +#include "colors/color.h" +#include "colors/printer.h" + +namespace Inkscape::Colors::Space { + +/** + * Convert the DeviceCMYK color into sRGB components used in the sRGB icc profile. + * + * See CSS Color Module Level 5, device-cmyk Uncalibrated conversion. + * + * @arg io - A vector of the input values, where the new values will be stored. + */ +void DeviceCMYK::spaceToProfile(std::vector &io) const +{ + double white = 1.0 - io[3]; + io[0] = 1.0 - std::min(1.0, io[0] * white + io[3]); + io[1] = 1.0 - std::min(1.0, io[1] * white + io[3]); + io[2] = 1.0 - std::min(1.0, io[2] * white + io[3]); + + // Delete black from position 3 + io.erase(io.begin() + 3); +} + +/** + * Convert from sRGB icc values to DeviceCMYK values + * + * See CSS Color Module Level 5, device-cmyk Uncalibrated conversion. + * + * @arg io - A vector of the input values, where the new values will be stored. + */ +void DeviceCMYK::profileToSpace(std::vector &io) const +{ + // Insert black channel at position 3 + io.insert(io.begin() + 3, 1.0 - std::max(std::max(io[0], io[1]), io[2])); + double const white = 1.0 - io[3]; + + // Each channel is it's color chart oposite (cyan->red) with a bit of white removed. + io[0] = white ? (1.0 - io[0] - io[3]) / white : 0.0; + io[1] = white ? (1.0 - io[1] - io[3]) / white : 0.0; + io[2] = white ? (1.0 - io[2] - io[3]) / white : 0.0; +} + +/** + * Print the DeviceCMYK color to a CSS Color Module Level 5 string. + * + * @arg values - A vector of doubles for each channel in the DeviceCMYK space + * @arg opacity - True if the opacity should be included in the output. + */ +std::string DeviceCMYK::toString(std::vector const &values, bool opacity) const +{ + auto os = CssFuncPrinter(4, "device-cmyk"); + os << values; + if (opacity && values.size() == 5) + os << values[4]; + return os; +} + +/** + * Return true if, using a rough hueruistic, this color could be considered to be using + * too much ink if it was printed using the ink as specified. + * + * @arg input - Channel values in this space. + */ +bool DeviceCMYK::overInk(std::vector const &input) const +{ + if (!input.size()) + return false; + // Over 320% is considered over inked, see cms.cpp for details. + return input[0] + input[1] + input[2] + input[3] > 3.2; +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/cmyk.h b/src/colors/spaces/cmyk.h new file mode 100644 index 0000000000000000000000000000000000000000..134f92da06b79a97a05d033d0977d192250307b3 --- /dev/null +++ b/src/colors/spaces/cmyk.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_DEVICECMYK_H +#define SEEN_COLORS_SPACES_DEVICECMYK_H + +#include "rgb.h" + +namespace Inkscape::Colors::Space { + +/** + * This sRGB based DeviceCMYK space is uncalibrated and fixed to the sRGB icc profile. + */ +class DeviceCMYK : public RGB +{ +public: + DeviceCMYK() = default; + ~DeviceCMYK() override = default; + + Type getType() const override { return Type::CMYK; } + std::string const getName() const override { return "DeviceCMYK"; } + std::string const getIcon() const override { return "color-selector-cmyk"; } + unsigned int getComponentCount() const override { return 4; } + + void spaceToProfile(std::vector &output) const override; + void profileToSpace(std::vector &output) const override; + +protected: + friend class Inkscape::Colors::Color; + + std::string toString(std::vector const &values, bool opacity = true) const override; + bool overInk(std::vector const &input) const override; +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_DEVICECMYK_H diff --git a/src/colors/spaces/components.cpp b/src/colors/spaces/components.cpp new file mode 100644 index 0000000000000000000000000000000000000000..463bd3168ca59ab6276d02f27240d7d52b255724 --- /dev/null +++ b/src/colors/spaces/components.cpp @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Meta data about color channels and how they are presented to users. + *//* + * Authors: see git history + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "components.h" + +#include +#include +#include // avoid glib include +#include +#include + +#include "enum.h" + +#define _(String) gettext(String) + +namespace Inkscape::Colors::Space { + +Component::Component(Type type, unsigned int index, std::string id, std::string name, std::string tip, unsigned scale) + : type(type) + , index(index) + , id(std::move(id)) + , name(std::move(name)) + , tip(std::move(tip)) + , scale(scale) +{} + +/** + * Clamp the value to between 0.0 and 1.0, except for hue which is wrapped around. + */ +double Component::normalize(double value) const +{ + if (scale == 360 && (value < 0.0 || value > 1.0)) { + return value - std::floor(value); + } + return std::clamp(value, 0.0, 1.0); +} + +void Components::add(std::string id, std::string name, std::string tip, unsigned scale) +{ + _components.emplace_back(_type, _components.size(), std::move(id), std::move(name), std::move(tip), scale); +} + +std::map _build(bool alpha) +{ + std::map sets; + + sets[Type::RGB].setType(Type::RGB); + sets[Type::RGB].add("r", _("_R:"), _("Red"), 255); + sets[Type::RGB].add("g", _("_G:"), _("Green"), 255); + sets[Type::RGB].add("b", _("_B:"), _("Blue"), 255); + + sets[Type::linearRGB].setType(Type::linearRGB); + sets[Type::linearRGB].add("r", _("l_R:"), _("Linear Red"), 255); + sets[Type::linearRGB].add("g", _("l_G:"), _("Linear Green"), 255); + sets[Type::linearRGB].add("b", _("l_B:"), _("Linear Blue"), 255); + + sets[Type::HSL].setType(Type::HSL); + sets[Type::HSL].add("h", _("_H:"), _("Hue"), 360); + sets[Type::HSL].add("s", _("_S:"), _("Saturation"), 100); + sets[Type::HSL].add("l", _("_L:"), _("Lightness"), 100); + + sets[Type::HSL].setType(Type::HSL); + sets[Type::CMYK].add("c", _("_C:"), _("Cyan"), 100); + sets[Type::CMYK].add("m", _("_M:"), _("Magenta"), 100); + sets[Type::CMYK].add("y", _("_Y:"), _("Yellow"), 100); + sets[Type::CMYK].add("k", _("_K:"), _("Black"), 100); + + sets[Type::CMY].setType(Type::CMY); + sets[Type::CMY].add("c", _("_C:"), _("Cyan"), 100); + sets[Type::CMY].add("m", _("_M:"), _("Magenta"), 100); + sets[Type::CMY].add("y", _("_Y:"), _("Yellow"), 100); + + sets[Type::HSV].setType(Type::HSV); + sets[Type::HSV].add("h", _("_H:"), _("Hue"), 360); + sets[Type::HSV].add("s", _("_S:"), _("Saturation"), 100); + sets[Type::HSV].add("v", _("_V:"), _("Value"), 100); + + sets[Type::HSLUV].setType(Type::HSLUV); + sets[Type::HSLUV].add("h", _("_H*"), _("Hue"), 360); + sets[Type::HSLUV].add("s", _("_S*"), _("Saturation"), 100); + sets[Type::HSLUV].add("l", _("_L*"), _("Lightness"), 100); + + sets[Type::LUV].setType(Type::LUV); + sets[Type::LUV].add("l", _("_L*"), _("Luminance"), 100); + sets[Type::LUV].add("u", _("_u*"), _("Chroma U"), 100); + sets[Type::LUV].add("v", _("_v*"), _("Chroma V"), 100); + + sets[Type::LCH].setType(Type::LCH); + sets[Type::LCH].add("l", _("_L"), _("Luminance"), 255); + sets[Type::LCH].add("c", _("_C"), _("Chroma"), 255); + sets[Type::LCH].add("h", _("_H"), _("Hue"), 360); + + sets[Type::OKHSL].setType(Type::OKHSL); + sets[Type::OKHSL].add("h", _("_Hok"), _("Hue"), 360); + sets[Type::OKHSL].add("s", _("_Sok"), _("Saturation"), 100); + sets[Type::OKHSL].add("l", _("_Lok"), _("Lightness"), 100); + + sets[Type::OKLAB].setType(Type::OKLAB); + sets[Type::OKLAB].add("h", _("_Lok"), _("Lightness"), 100); + sets[Type::OKLAB].add("s", _("_Aok"), _("Component A"), 100); + sets[Type::OKLAB].add("l", _("_Bok"), _("Component B"), 100); + + sets[Type::OKLCH].setType(Type::OKLCH); + sets[Type::OKLCH].add("l", _("_Lok"), _("Lightness"), 100); + sets[Type::OKLCH].add("c", _("_Cok"), _("Chroma"), 100); + sets[Type::OKLCH].add("h", _("_Hok"), _("Hue"), 360); + + sets[Type::XYZ].setType(Type::XYZ); + sets[Type::XYZ].add("x", "_X", "X", 255); + sets[Type::XYZ].add("y", "_Y", "Y", 100); + sets[Type::XYZ].add("z", "_Z", "Z", 255); + + sets[Type::YCbCr].setType(Type::YCbCr); + sets[Type::YCbCr].add("y", "_Y", "Y", 255); + sets[Type::YCbCr].add("cb", "C_b", "Cb", 255); + sets[Type::YCbCr].add("cr", "C_r", "Cr", 255); + + sets[Type::LAB].setType(Type::LAB); + sets[Type::LAB].add("l", "_L", "L", 100); + sets[Type::LAB].add("a", "_a", "a", 256); + sets[Type::LAB].add("b", "_b", "b", 256); + + sets[Type::YXY].setType(Type::YXY); + sets[Type::YXY].add("y1", "_Y", "Y", 255); + sets[Type::YXY].add("x", "_x", "x", 255); + sets[Type::YXY].add("y2", "y", "y", 255); + + sets[Type::Gray].setType(Type::Gray); + sets[Type::Gray].add("gray", _("G:"), _("Gray"), 1024); + + if (alpha) { + for (auto &[key, val] : sets) { + val.add("a", _("_A:"), _("Alpha"), 255); + } + } + return sets; +} + +Components const &Components::get(Type space, bool alpha) +{ + static std::map sets_no_alpha = _build(false); + static std::map sets_with_alpha = _build(true); + + auto &lookup_set = alpha ? sets_with_alpha : sets_no_alpha; + if (auto search = lookup_set.find(space); search != lookup_set.end()) { + return search->second; + } + return lookup_set[Type::NONE]; +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/components.h b/src/colors/spaces/components.h new file mode 100644 index 0000000000000000000000000000000000000000..82c1f1230cacce9e6725feb7e8e94d70c32e6459 --- /dev/null +++ b/src/colors/spaces/components.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Jon A. Cruz + * Martin Owens + * + * Copyright (C) 2013-2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_COMPONENTS_H +#define SEEN_COLORS_COMPONENTS_H + +#include +#include +#include +#include + +namespace Inkscape::Colors::Space { + +enum class Type; + +struct Component +{ + Component(Type type, unsigned int index, std::string id, std::string name, std::string tip, unsigned scale); + + Type type; + unsigned int index; + std::string id; + std::string name; + std::string tip; + unsigned scale; + + double normalize(double value) const; +}; + +class Components +{ +public: + Components() = default; + + static Components const &get(Type type, bool alpha = false); + + std::vector::const_iterator begin() const { return std::begin(_components); } + std::vector::const_iterator end() const { return std::end(_components); } + Component const &operator[](const unsigned int index) const { return _components[index]; } + + Type getType() const { return _type; } + unsigned size() const { return _components.size(); } + + void add(std::string id, std::string name, std::string tip, unsigned scale); + void setType(Type type) { _type = type; } + +private: + Type _type; + std::vector _components; +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_COMPONENTS_H diff --git a/src/colors/spaces/enum.h b/src/colors/spaces/enum.h new file mode 100644 index 0000000000000000000000000000000000000000..b5834a577d3de60383d73e701a1d35d06478c35b --- /dev/null +++ b/src/colors/spaces/enum.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_ENUM_H +#define SEEN_COLORS_SPACES_ENUM_H + +#include +#include + +namespace Inkscape::Colors { +enum class RenderingIntent +{ + UNKNOWN = 0, + AUTO = 1, + PERCEPTUAL = 2, + RELATIVE_COLORIMETRIC = 3, + SATURATION = 4, + ABSOLUTE_COLORIMETRIC = 5, + // This isn't an SVG standard value, this is an Inkscape additional + // value that means RENDERING_INTENT_RELATIVE_COLORIMETRIC minus + // the black point compensation. This BPC doesn't apply to any other + // rendering intent so is safely folded in here. + RELATIVE_COLORIMETRIC_NOBPC = 6 +}; + +// Used in caching keys and in svg rendering-intent attributes +static std::map intentIds = { + {RenderingIntent::UNKNOWN, ""}, + {RenderingIntent::AUTO, "auto"}, + {RenderingIntent::PERCEPTUAL, "perceptual"}, + {RenderingIntent::SATURATION, "saturation"}, + {RenderingIntent::ABSOLUTE_COLORIMETRIC, "absolute-colorimetric"}, + {RenderingIntent::RELATIVE_COLORIMETRIC, "relative-colorimetric"}, + {RenderingIntent::RELATIVE_COLORIMETRIC_NOBPC, "relative-colorimetric-nobpc"}, +}; + +namespace Space { +// The spaces we support are a mixture of ICC profile spaces +// and internal spaces converted to and from RGB +enum class Type +{ + NONE, // An error of some kind, or destroyed object + Gray, // Grayscale, typical of some print icc profiles + RGB, // sRGB color space typical with SVG + linearRGB, // linear RGB used in SVG and other transforms + HSL, // Hue, Saturation and Lightness, sometimes called HLS + HSV, // Hue, Saturation and Value, similar to HSL and HWB + HWB, // Hue, Whiteness and Blackness, similar to HSL and HSV + CMYK, // Cyan, Magenta, Yellow and Black for print + CMY, // CMYK without the black, used in some icc profiles + XYZ, // Color, Luminance and Blueness, same CIE as RGB + YXY, + LUV, // Lightness and chromaticity, aka CIELUV + LCH, // Lunimance, Chroma and Hue, aka HCL + LAB, // Lightness, Green-Magenta and Blue-Yellow, aka CIELAB + HSLUV, + OKHSL, + OKLCH, + OKLAB, + YCbCr, + CSSNAME, // Special css-named colors + CMS // Special cms type +}; + +} // namespace Space +} // namespace Inkscape::Colors + +#endif // SEEN_COLORS_SPACES_ENUM_H diff --git a/src/colors/spaces/gray.cpp b/src/colors/spaces/gray.cpp new file mode 100644 index 0000000000000000000000000000000000000000..069d9ee5ec10b524bca7025d5bfb58cc836ce0ea --- /dev/null +++ b/src/colors/spaces/gray.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: see git history + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "gray.h" + +namespace Inkscape::Colors::Space { + +/** + * Convert a single gray channel into an RGB + */ +void Gray::spaceToProfile(std::vector &io) const +{ + io.insert(io.begin(), io[0]); + io.insert(io.begin(), io[0]); +} + +/** + * Convert an RGB into a gray channel using the HSL method + */ +void Gray::profileToSpace(std::vector &io) const +{ + double max = std::max(std::max(io[0], io[1]), io[2]); + double min = std::min(std::min(io[0], io[1]), io[2]); + // Retain opacity if it exists by removing channels + io.erase(io.begin()); + io.erase(io.begin()); + io[0] = (max + min) / 2.0; // Based on HSL conversion +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/gray.h b/src/colors/spaces/gray.h new file mode 100644 index 0000000000000000000000000000000000000000..7f66a317ece9f0228a8233700a9acca9abacebce --- /dev/null +++ b/src/colors/spaces/gray.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_GRAY_H +#define SEEN_COLORS_SPACES_GRAY_H + +#include "rgb.h" + +namespace Inkscape::Colors::Space { + +class Gray : public RGB +{ + Type getType() const override { return Type::Gray; } + std::string const getName() const override { return "Gray"; } + std::string const getIcon() const override { return "color-selector-gray"; } + unsigned int getComponentCount() const override { return 1; } + +protected: + friend class Inkscape::Colors::Color; + + void spaceToProfile(std::vector &output) const override; + void profileToSpace(std::vector &output) const override; +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_GRAY_H diff --git a/src/colors/spaces/hsl.cpp b/src/colors/spaces/hsl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c8a2dca1cacfa4437161ae8d0309cdaf4848a023 --- /dev/null +++ b/src/colors/spaces/hsl.cpp @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: see git history + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "hsl.h" + +#include + +#include "colors/color.h" +#include "colors/printer.h" + +namespace Inkscape::Colors::Space { + +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; +} + +/** + * Convert the HSL color into sRGB components used in the sRGB icc profile. + */ +void HSL::spaceToProfile(std::vector &output) const +{ + double h = output[0]; + double s = output[1]; + double l = output[2]; + + if (s == 0) { // Gray + output[0] = l; + output[1] = l; + output[2] = l; + } else { + double v2; + if (l < 0.5) { + v2 = l * (1 + s); + } else { + v2 = l + s - l * s; + } + double v1 = 2 * l - v2; + + output[0] = hue_2_rgb(v1, v2, h * 6 + 2.0); + output[1] = hue_2_rgb(v1, v2, h * 6); + output[2] = hue_2_rgb(v1, v2, h * 6 - 2.0); + } +} + +/** + * Convert from sRGB icc values to HSL values + */ +void HSL::profileToSpace(std::vector &output) const +{ + double r = output[0]; + double g = output[1]; + double b = output[2]; + + double max = std::max(std::max(r, g), b); + double min = std::min(std::min(r, g), b); + double delta = max - min; + + double h = 0; + double s = 0; + double l = (max + min) / 2.0; + + if (delta != 0) { + if (l <= 0.5) + s = delta / (max + min); + else + s = delta / (2 - max - min); + + if (r == max) + h = (g - b) / delta; + else if (g == max) + h = 2.0 + (b - r) / delta; + else if (b == max) + h = 4.0 + (r - g) / delta; + + h = h / 6.0; + + if (h < 0) + h += 1; + if (h > 1) + h -= 1; + } + output[0] = h; + output[1] = s; + output[2] = l; +} + +/** + * Print the HSL color to a CSS string. + * + * @arg values - A vector of doubles for each channel in the HSL space + * @arg opacity - True if the opacity should be included in the output. + */ +std::string HSL::toString(std::vector const &values, bool opacity) const +{ + auto oo = CssLegacyPrinter(3, "hsl", opacity && values.size() == 4); + // First entry is Hue, which is in degrees + return oo << (int)(values[0] * 360) << values[1] << values[2] << values.back(); +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/hsl.h b/src/colors/spaces/hsl.h new file mode 100644 index 0000000000000000000000000000000000000000..5a77008595197d83a4539e01c884ffb2154010e4 --- /dev/null +++ b/src/colors/spaces/hsl.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_HSL_H +#define SEEN_COLORS_SPACES_HSL_H + +#include "rgb.h" + +namespace Inkscape::Colors::Space { + +class HSL : public RGB +{ +public: + HSL() = default; + ~HSL() override = default; + + Type getType() const override { return Type::HSL; } + std::string const getName() const override { return "HSL"; } + std::string const getIcon() const override { return "color-selector-hsx"; } + +protected: + friend class Inkscape::Colors::Color; + + std::string toString(std::vector const &values, bool opacity = true) const override; + + void spaceToProfile(std::vector &output) const override; + void profileToSpace(std::vector &output) const override; + +public: + class Parser : public HueParser + { + public: + Parser(bool alpha) + : HueParser("hsl", Type::HSL, alpha) + {} + }; +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_HSL_H diff --git a/src/colors/spaces/hsluv.cpp b/src/colors/spaces/hsluv.cpp new file mode 100644 index 0000000000000000000000000000000000000000..653bbae16707fe949c42591fd40e01b9e70fe677 --- /dev/null +++ b/src/colors/spaces/hsluv.cpp @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: + * 2015 Alexei Boronine (original idea, JavaScript implementation) + * 2015 Roger Tallada (Obj-C implementation) + * 2017 Martin Mitas (C implementation, based on Obj-C implementation) + * 2021 Massinissa Derriche (C++ implementation for Inkscape, based on C implementation) + * 2023 Martin Owens (New Color classes) + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "hsluv.h" + +#include + +namespace Inkscape::Colors::Space { + +/** + * Calculate the bounds of the Luv colors in RGB gamut. + * + * @param l Lightness. Between 0.0 and 100.0. + * @return Bounds of Luv colors in RGB gamut. + */ +std::array HSLuv::get_bounds(double l) +{ + std::array bounds; + + double tl = l + 16.0; + double sub1 = (tl * tl * tl) / 1560896.0; + double sub2 = (sub1 > EPSILON ? sub1 : (l / KAPPA)); + int channel; + int t; + + for (channel = 0; channel < 3; channel++) { + double m1 = d65[channel][0]; + double m2 = d65[channel][1]; + double m3 = d65[channel][2]; + + for (t = 0; t < 2; t++) { + double top1 = (284517.0 * m1 - 94839.0 * m3) * sub2; + double top2 = (838422.0 * m3 + 769860.0 * m2 + 731718.0 * m1) * l * sub2 - 769860.0 * t * l; + double bottom = (632260.0 * m3 - 126452.0 * m2) * sub2 + 126452.0 * t; + + bounds[channel * 2 + t].setCoefficients(top1, -bottom, top2); + } + } + + return bounds; +} + +/** + * Calculate the maximum in gamut chromaticity for the given luminance and hue. + * + * @param l Luminance. + * @param h Hue. + * @return The maximum chromaticity. + */ +static double max_chroma_for_lh(double l, double h) +{ + double min_len = std::numeric_limits::max(); + auto const ray = Geom::Ray(Geom::Point(0, 0), Geom::rad_from_deg(h)); + + for (auto const &line : HSLuv::get_bounds(l)) { + auto intersections = line.intersect(ray); + if (intersections.empty()) { + continue; + } + double len = intersections[0].point().length(); + + if (len >= 0 && len < min_len) { + min_len = len; + } + } + + return min_len; +} + +/** + * Convert a color from the the HSLuv colorspace to the LCH colorspace. + * + * @param in_out[in,out] The HSLuv color converted to a LCH color. + */ +void HSLuv::toLch(std::vector &in_out) +{ + double h = in_out[0] * 360; + double s = in_out[1] * 100; + double l = in_out[2] * 100; + double c; + + /* White and black: disambiguate chroma */ + if (l > 99.9999999 || l < 0.00000001) { + c = 0.0; + } else { + c = max_chroma_for_lh(l, h) / 100.0 * s; + } + + /* Grays: disambiguate hue */ + if (s < 0.00000001) { + h = 0.0; + } + + in_out[0] = l; + in_out[1] = c; + in_out[2] = h; +} + +/** + * Convert a color from the the LCH colorspace to the HSLuv colorspace. + * + * @param in_out[in,out] The LCH color converted to a HSLuv color. + */ +void HSLuv::fromLch(std::vector &in_out) +{ + double l = in_out[0]; + double c = in_out[1]; + double h = in_out[2]; + double s; + + /* White and black: disambiguate saturation */ + if (l > 99.9999999 || l < 0.00000001) { + s = 0.0; + } else { + s = c / max_chroma_for_lh(l, h) * 100.0; + } + + /* Grays: disambiguate hue */ + if (c < 0.00000001) { + h = 0.0; + } + + in_out[0] = h / 360; + in_out[1] = s / 100; + in_out[2] = l / 100; +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/hsluv.h b/src/colors/spaces/hsluv.h new file mode 100644 index 0000000000000000000000000000000000000000..ceaa48aaa6659fa6111c0364d738099b0f11253a --- /dev/null +++ b/src/colors/spaces/hsluv.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_HSLUV_H +#define SEEN_COLORS_SPACES_HSLUV_H + +#include <2geom/line.h> +#include <2geom/ray.h> + +#include "lch.h" + +#include <2geom/line.h> +#include <2geom/ray.h> + +namespace Inkscape::Colors::Space { + +class HSLuv : public RGB +{ +public: + HSLuv() = default; + ~HSLuv() override = default; + + Type getType() const override { return Type::HSLUV; } + std::string const getName() const override { return "HSLuv"; } + std::string const getIcon() const override { return "color-selector-hsluv"; } + +protected: + friend class Inkscape::Colors::Color; + + void spaceToProfile(std::vector &output) const override + { + HSLuv::toLch(output); + Lch::toLuv(output); + Luv::toXYZ(output); + XYZ::toLinearRGB(output); + LinearRGB::toRGB(output); + } + void profileToSpace(std::vector &output) const override + { + LinearRGB::fromRGB(output); + XYZ::fromLinearRGB(output); + Luv::fromXYZ(output); + Lch::fromLuv(output); + HSLuv::fromLch(output); + } + +public: + static void toLch(std::vector &output); + static void fromLch(std::vector &output); + static std::array get_bounds(double l); +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_HSLUV_H diff --git a/src/colors/spaces/hsv.cpp b/src/colors/spaces/hsv.cpp new file mode 100644 index 0000000000000000000000000000000000000000..88da40534325bc590fbd4b573d6a0672b5f7f446 --- /dev/null +++ b/src/colors/spaces/hsv.cpp @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: see git history + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "hsv.h" + +#include + +#include "colors/color.h" +#include "colors/printer.h" + +namespace Inkscape::Colors::Space { + +/** + * Convert the HSV color into sRGB components used in the sRGB icc profile. + */ +void HSV::spaceToProfile(std::vector &output) const +{ + double v = output[2]; + double d = output[0] * 5.99999999; + double f = d - std::floor(d); + double w = v * (1.0 - output[1]); + double q = v * (1.0 - (output[1] * f)); + double t = v * (1.0 - (output[1] * (1.0 - f))); + + if (d < 1.0) { + output[0] = v; + output[1] = t; + output[2] = w; + } else if (d < 2.0) { + output[0] = q; + output[1] = v; + output[2] = w; + } else if (d < 3.0) { + output[0] = w; + output[1] = v; + output[2] = t; + } else if (d < 4.0) { + output[0] = w; + output[1] = q; + output[2] = v; + } else if (d < 5.0) { + output[0] = t; + output[1] = w; + output[2] = v; + } else { + output[0] = v; + output[1] = w; + output[2] = q; + } +} + +/** + * Convert from sRGB icc values to HSV values + */ +void HSV::profileToSpace(std::vector &output) const +{ + double r = output[0]; + double g = output[1]; + double b = output[2]; + + double max = std::max(std::max(r, g), b); + double min = std::min(std::min(r, g), b); + double delta = max - min; + + output[2] = max; + output[1] = max > 0 ? delta / max : 0.0; + + if (output[1] != 0.0) { + if (r == max) { + output[0] = (g - b) / delta; + } else if (g == max) { + output[0] = 2.0 + (b - r) / delta; + } else { + output[0] = 4.0 + (r - g) / delta; + } + output[0] = output[0] / 6.0; + if (output[0] < 0) + output[0] += 1.0; + } else + output[0] = 0.0; +} + +/** + * Parse the hwb css string and convert to hsv inline, if it exists in the input string stream. + */ +bool HSV::fromHwbParser::parse(std::istringstream &ss, std::vector &output) const +{ + if (HueParser::parse(ss, output)) { + // See https://en.wikipedia.org/wiki/HWB_color_model#Converting_to_and_from_HSV + auto scale = output[1] + output[2]; + if (scale > 1.0) { + output[1] /= scale; + output[2] /= scale; + } + output[1] = output[2] == 1.0 ? 0.0 : (1.0 - (output[1] / (1.0 - output[2]))); + output[2] = 1.0 - output[2]; + return true; + } + return false; +} + +/** + * Print the HSV color to a CSS hwb() string. + * + * @arg values - A vector of doubles for each channel in the HSV space + * @arg opacity - True if the opacity should be included in the output. + */ +std::string HSV::toString(std::vector const &values, bool opacity) const +{ + auto oo = CssLegacyPrinter(3, "hwb", opacity && values.size() == 4); + // First entry is Hue, which is in degrees, white and black are dirived + return oo << (int)(values[0] * 360) // Hue, degrees, 0..360 + << (1.0 - values[1]) * values[2] // White + << 1.0 - values[2] // Black + << values.back(); // Opacity +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/hsv.h b/src/colors/spaces/hsv.h new file mode 100644 index 0000000000000000000000000000000000000000..a591775bb3f84590f8148352639de112cefd445b --- /dev/null +++ b/src/colors/spaces/hsv.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_HSV_H +#define SEEN_COLORS_SPACES_HSV_H + +#include "rgb.h" + +namespace Inkscape::Colors::Space { + +class HSV : public RGB +{ +public: + HSV() = default; + ~HSV() override = default; + + Type getType() const override { return Type::HSV; } + std::string const getName() const override { return "HSV"; } + std::string const getIcon() const override { return "color-selector-hsx"; } + + void spaceToProfile(std::vector &output) const override; + void profileToSpace(std::vector &output) const override; + +protected: + friend class Inkscape::Colors::Color; + + std::string toString(std::vector const &values, bool opacity) const override; + +public: + class fromHwbParser : public HueParser + { + public: + fromHwbParser(bool alpha) + : HueParser("hwb", Space::Type::HSV, alpha) + {} + bool parse(std::istringstream &input, std::vector &output) const override; + }; +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_HSV_H diff --git a/src/colors/spaces/lab.cpp b/src/colors/spaces/lab.cpp new file mode 100644 index 0000000000000000000000000000000000000000..96a5ea5a3f47700b401b08b523e8f652b3d81d12 --- /dev/null +++ b/src/colors/spaces/lab.cpp @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: + * 2023 Martin Owens + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "lab.h" + +#include + +#include "colors/color.h" +#include "colors/printer.h" + +namespace Inkscape::Colors::Space { + +constexpr double LUMA_SCALE = 100; + +// NOTE! Inkscape's calculations use a range of 256, while CSS uses 250 +constexpr double MIN_SCALE = -128; +constexpr double MAX_SCALE = 128; +constexpr double MIN_CSS_SCALE = -125; +constexpr double MAX_CSS_SCALE = 125; + +/** + * Changes the values from 0..1, to typical lab scaling used in calculations. + */ +void Lab::scaleUp(std::vector &in_out) +{ + in_out[0] = SCALE_UP(in_out[0], 0, LUMA_SCALE); + in_out[1] = SCALE_UP(in_out[1], MIN_SCALE, MAX_SCALE); + in_out[2] = SCALE_UP(in_out[2], MIN_SCALE, MAX_SCALE); +} + +/** + * Changes the values from typical lab scaling (see above) to values 0..1. + */ +void Lab::scaleDown(std::vector &in_out) +{ + in_out[0] = SCALE_DOWN(in_out[0], 0, LUMA_SCALE); + in_out[1] = SCALE_DOWN(in_out[1], MIN_SCALE, MAX_SCALE); + in_out[2] = SCALE_DOWN(in_out[2], MIN_SCALE, MAX_SCALE); +} + +/** + * Convert a color from the the Lab colorspace to the XYZ colorspace. + * + * @param in_out[in,out] The Lab color converted to a XYZ color. + */ +void Lab::toXYZ(std::vector &in_out) +{ + scaleUp(in_out); + + double y = (in_out[0] + 16.0) / 116.0; + in_out[0] = in_out[1] / 500.0 + y; + in_out[1] = y; + in_out[2] = y - in_out[2] / 200.0; + + for (unsigned i = 0; i < 3; i++) { + double x3 = std::pow(in_out[i], 3); + if (x3 > 0.008856) { + in_out[i] = x3; + } else { + in_out[i] = (in_out[i] - 16.0 / 116.0) / 7.787; + } + in_out[i] *= illuminant_d65[i]; + } +} + +/** + * Convert a color from the the XYZ colorspace to the Lab colorspace. + * + * @param in_out[in,out] The XYZ color converted to a Lab color. + */ +void Lab::fromXYZ(std::vector &in_out) +{ + for (unsigned i = 0; i < 3; i++) { + in_out[i] /= illuminant_d65[i]; + } + + double l; + if (in_out[1] > 0.008856) { + l = 116 * std::pow(in_out[1], 0.33333) - 16; + } else { + l = 903.3 * in_out[1]; + } + + for (unsigned i = 0; i < 3; i++) { + if (in_out[i] > 0.008856) { + in_out[i] = std::pow(in_out[i], 0.33333); + } else { + in_out[i] = 7.787 * in_out[i] + 16.0 / 116.0; + } + }; + in_out[2] = 200 * (in_out[1] - in_out[2]); + in_out[1] = 500 * (in_out[0] - in_out[1]); + in_out[0] = l; + + scaleDown(in_out); +} + +/** + * Print the Lab color to a CSS string. + * + * @arg values - A vector of doubles for each channel in the Lab space + * @arg opacity - True if the opacity should be included in the output. + */ +std::string Lab::toString(std::vector const &values, bool opacity) const +{ + auto os = CssFuncPrinter(3, "lab"); + + os << values[0] * LUMA_SCALE // Luminance + << SCALE_UP(values[1], MIN_CSS_SCALE, MAX_CSS_SCALE) // Chroma A + << SCALE_UP(values[2], MIN_CSS_SCALE, MAX_CSS_SCALE); // Chroma B + + if (opacity && values.size() == 4) + os << values[3]; // Optional opacity + + return os; +} + +bool Lab::Parser::parse(std::istringstream &ss, std::vector &output) const +{ + bool end = false; + if (append_css_value(ss, output, end, ',', LUMA_SCALE) // Lightness + && append_css_value(ss, output, end, ',', MAX_CSS_SCALE) // Chroma-A + && append_css_value(ss, output, end, '/', MAX_CSS_SCALE) // Chroma-B + && (append_css_value(ss, output, end) || true) // Optional opacity + && end) { + // The A and B portions are between -100% and 100% leading to this post unit aditional conversion. + output[1] = (output[1] + 1) / 2; + output[2] = (output[2] + 1) / 2; + return true; + } + return false; +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/lab.h b/src/colors/spaces/lab.h new file mode 100644 index 0000000000000000000000000000000000000000..f39e0f33f96d19aac999133f97409cd6cf8b8704 --- /dev/null +++ b/src/colors/spaces/lab.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_LAB_H +#define SEEN_COLORS_SPACES_LAB_H + +#include "xyz.h" + +namespace Inkscape::Colors::Space { + +class Lab : public RGB +{ +public: + Lab() = default; + ~Lab() override = default; + + Type getType() const override { return Type::LAB; } + std::string const getName() const override { return "Lab"; } + std::string const getIcon() const override { return "color-selector-lab"; } + +protected: + friend class Inkscape::Colors::Color; + + void spaceToProfile(std::vector &output) const override + { + Lab::toXYZ(output); + XYZ::toLinearRGB(output); + LinearRGB::toRGB(output); + } + void profileToSpace(std::vector &output) const override + { + LinearRGB::fromRGB(output); + XYZ::fromLinearRGB(output); + Lab::fromXYZ(output); + } + + std::string toString(std::vector const &values, bool opacity) const override; + +public: + class Parser : public Colors::Parser + { + public: + Parser() + : Colors::Parser("lab", Type::LAB) + {} + bool parse(std::istringstream &input, std::vector &output) const override; + }; + + static void toXYZ(std::vector &output); + static void fromXYZ(std::vector &output); + + static void scaleDown(std::vector &in_out); + static void scaleUp(std::vector &in_out); +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_LAB_H diff --git a/src/colors/spaces/lch.cpp b/src/colors/spaces/lch.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b9915e983540ec49213df88f3709b4f89e10e709 --- /dev/null +++ b/src/colors/spaces/lch.cpp @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: + * 2015 Alexei Boronine (original idea, JavaScript implementation) + * 2015 Roger Tallada (Obj-C implementation) + * 2017 Martin Mitas (C implementation, based on Obj-C implementation) + * 2021 Massinissa Derriche (C++ implementation for Inkscape, based on C implementation) + * 2023 Martin Owens (New Color classes) + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "lch.h" + +#include <2geom/ray.h> + +#include "colors/printer.h" + +namespace Inkscape::Colors::Space { + +constexpr double LUMA_SCALE = 100; +constexpr double CHROMA_SCALE = 150; +constexpr double HUE_SCALE = 360; + +/** + * Changes the values from 0..1, to typical lch scaling used + * in calculations. L:0..100, C:0..150 H:0..360 + */ +void Lch::scaleUp(std::vector &in_out) +{ + in_out[0] = SCALE_UP(in_out[0], 0, LUMA_SCALE); + in_out[1] = SCALE_UP(in_out[1], 0, CHROMA_SCALE); + in_out[2] = SCALE_UP(in_out[2], 0, HUE_SCALE); +} + +/** + * Changes the values from typical lch scaling (see above) to + * values 0..1 used in the color module. + */ +void Lch::scaleDown(std::vector &in_out) +{ + in_out[0] = SCALE_DOWN(in_out[0], 0, LUMA_SCALE); + in_out[1] = SCALE_DOWN(in_out[1], 0, CHROMA_SCALE); + in_out[2] = SCALE_DOWN(in_out[2], 0, HUE_SCALE); +} + +/** + * Convert a color from the the LCH colorspace to the Luv colorspace. + * + * @param in_out[in,out] The LCH color converted to a Luv color. + */ +void Lch::toLuv(std::vector &in_out) +{ + double sinhrad, coshrad; + Geom::sincos(Geom::rad_from_deg(in_out[2]), sinhrad, coshrad); + double u = coshrad * in_out[1]; + double v = sinhrad * in_out[1]; + + in_out[1] = u; + in_out[2] = v; +} + +/** + * Convert a color from the the Luv colorspace to the LCH colorspace. + * + * @param in_out[in,out] The Luv color converted to a LCH color. + */ +void Lch::fromLuv(std::vector &in_out) +{ + double l = in_out[0]; + auto uv = Geom::Point(in_out[1], in_out[2]); + double h; + double const c = uv.length(); + + /* Grays: disambiguate hue */ + if (c < 0.00000001) { + h = 0; + } else { + h = Geom::deg_from_rad(Geom::atan2(uv)); + if (h < 0.0) { + h += 360.0; + } + } + in_out[0] = l; + in_out[1] = c; + in_out[2] = h; +} + +/** + * Print the Lch color to a CSS string. + * + * @arg values - A vector of doubles for each channel in the Lch space + * @arg opacity - True if the opacity should be included in the output. + */ +std::string Lch::toString(std::vector const &values, bool opacity) const +{ + auto os = CssFuncPrinter(3, "lch"); + + os << values[0] * LUMA_SCALE // Luminance + << values[1] * CHROMA_SCALE // Chroma + << values[2] * HUE_SCALE; // Hue + + if (opacity && values.size() == 4) + os << values[3]; // Optional opacity + return os; +} + +bool Lch::Parser::parse(std::istringstream &ss, std::vector &output) const +{ + bool end = false; + return append_css_value(ss, output, end, ',', LUMA_SCALE) // Luminance + && append_css_value(ss, output, end, ',', CHROMA_SCALE) // Chroma + && append_css_value(ss, output, end, '/', HUE_SCALE) // Hue + && (append_css_value(ss, output, end) || true) // Optional opacity + && end; +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/lch.h b/src/colors/spaces/lch.h new file mode 100644 index 0000000000000000000000000000000000000000..b423356ff8fcad8f7d82d7cf6659a3e5ae7e6a88 --- /dev/null +++ b/src/colors/spaces/lch.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_LCH_H +#define SEEN_COLORS_SPACES_LCH_H + +#include "luv.h" + +namespace Inkscape::Colors::Space { + +class Lch : public RGB +{ +public: + Lch() = default; + ~Lch() override = default; + + Type getType() const override { return Type::LCH; } + std::string const getName() const override { return "Lch"; } + std::string const getIcon() const override { return "color-selector-lch"; } + +protected: + friend class Inkscape::Colors::Color; + + void spaceToProfile(std::vector &output) const override + { + Lch::scaleUp(output); + Lch::toLuv(output); + Luv::toXYZ(output); + XYZ::toLinearRGB(output); + LinearRGB::toRGB(output); + } + void profileToSpace(std::vector &output) const override + { + LinearRGB::fromRGB(output); + XYZ::fromLinearRGB(output); + Luv::fromXYZ(output); + Lch::fromLuv(output); + Lch::scaleDown(output); + } + + std::string toString(std::vector const &values, bool opacity) const override; + +public: + class Parser : public Colors::Parser + { + public: + Parser() + : Colors::Parser("lch", Type::LCH) + {} + bool parse(std::istringstream &input, std::vector &output) const override; + }; + + static void toLuv(std::vector &output); + static void fromLuv(std::vector &output); + + static void scaleDown(std::vector &in_out); + static void scaleUp(std::vector &in_out); +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_LCH_H diff --git a/src/colors/spaces/linear-rgb.cpp b/src/colors/spaces/linear-rgb.cpp new file mode 100644 index 0000000000000000000000000000000000000000..34be7589768661d0c2b22e403185f8b5da653c98 --- /dev/null +++ b/src/colors/spaces/linear-rgb.cpp @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: + * Rafał Siejakowski + * Martin Owens + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "linear-rgb.h" + +#include + +#include "colors/printer.h" + +namespace Inkscape::Colors::Space { + +/** + * Convenience function used for RGB conversions. + * + * @param c Value. + * @return RGB color component. + */ +double from_linear(double c) +{ + if (c <= 0.0031308) { + return 12.92 * c; + } else { + return 1.055 * std::pow(c, 1.0 / 2.4) - 0.055; + } +} + +/** + * Convenience function used for RGB conversions. + * + * @param c Value. + * @return XYZ color component. + */ +double to_linear(double c) +{ + if (c > 0.04045) { + return std::pow((c + 0.055) / 1.055, 2.4); + } else { + return c / 12.92; + } +} + +/** + * Convert a color from the a linear RGB colorspace to the sRGB colorspace. + * + * @param in_out[in,out] The linear RGB color converted to a RGB color. + */ +void LinearRGB::toRGB(std::vector &in_out) +{ + in_out[0] = from_linear(in_out[0]); + in_out[1] = from_linear(in_out[1]); + in_out[2] = from_linear(in_out[2]); +} + +/** + * Convert from sRGB icc values to linear RGB values + * + * @param in_out[in,out] The RGB color converted to a linear RGB color. + */ +void LinearRGB::fromRGB(std::vector &in_out) +{ + in_out[0] = to_linear(in_out[0]); + in_out[1] = to_linear(in_out[1]); + in_out[2] = to_linear(in_out[2]); +} + +/** + * Print the RGB color to a CSS Color module 4 srgb-linear color. + * + * @arg values - A vector of doubles for each channel in the RGB space + * @arg opacity - True if the opacity should be included in the output. + */ +std::string LinearRGB::toString(std::vector const &values, bool opacity) const +{ + auto os = CssColorPrinter(3, "srgb-linear"); + os << values; + if (opacity && values.size() == 4) + os << values.back(); + return os; +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/linear-rgb.h b/src/colors/spaces/linear-rgb.h new file mode 100644 index 0000000000000000000000000000000000000000..031fa10ce194531b84270bea329157cc49cd5800 --- /dev/null +++ b/src/colors/spaces/linear-rgb.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_LINEARRGB_H +#define SEEN_COLORS_SPACES_LINEARRGB_H + +#include "rgb.h" + +namespace Inkscape::Colors::Space { + +class LinearRGB : public RGB +{ +public: + LinearRGB() = default; + ~LinearRGB() override = default; + + Type getType() const override { return Type::linearRGB; } + std::string const getName() const override { return "linearRGB"; } + std::string const getIcon() const override { return "color-selector-linear-rgb"; } + +protected: + friend class Inkscape::Colors::Color; + + void spaceToProfile(std::vector &output) const override { LinearRGB::toRGB(output); } + void profileToSpace(std::vector &output) const override { LinearRGB::fromRGB(output); } + + std::string toString(std::vector const &values, bool opacity = true) const override; + +public: + static void toRGB(std::vector &output); + static void fromRGB(std::vector &output); +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_LINEARRGB_H diff --git a/src/colors/spaces/luv.cpp b/src/colors/spaces/luv.cpp new file mode 100644 index 0000000000000000000000000000000000000000..da67311f98c9701f74445d07e0717f95d7313775 --- /dev/null +++ b/src/colors/spaces/luv.cpp @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: + * 2015 Alexei Boronine (original idea, JavaScript implementation) + * 2015 Roger Tallada (Obj-C implementation) + * 2017 Martin Mitas (C implementation, based on Obj-C implementation) + * 2021 Massinissa Derriche (C++ implementation for Inkscape, based on C implementation) + * 2023 Martin Owens (New Color classes) + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "luv.h" + +#include + +namespace Inkscape::Colors::Space { + +constexpr double REF_U = 0.19783000664283680764; +constexpr double REF_V = 0.46831999493879100370; + +// There's no CSS for LUV yet, so we don't know what scales +// the w3c might choose to use. Our own calculations use these. +constexpr double LUMA_SCALE = 100; +constexpr double MIN_U = -100; +constexpr double MAX_U = 200; +constexpr double MIN_V = -200; +constexpr double MAX_V = 120; + +/** + * Changes the values from 0..1, to typical luv scaling used in calculations. + */ +void Luv::scaleUp(std::vector &in_out) +{ + in_out[0] = SCALE_UP(in_out[0], 0, LUMA_SCALE); + in_out[1] = SCALE_UP(in_out[1], MIN_U, MAX_U); + in_out[2] = SCALE_UP(in_out[2], MIN_V, MAX_V); +} + +/** + * Changes the values from typical luv scaling (see above) to values 0..1. + */ +void Luv::scaleDown(std::vector &in_out) +{ + in_out[0] = SCALE_DOWN(in_out[0], 0, LUMA_SCALE); + in_out[1] = SCALE_DOWN(in_out[1], MIN_U, MAX_U); + in_out[2] = SCALE_DOWN(in_out[2], MIN_V, MAX_V); +} + +std::vector Luv::fromCoordinates(std::vector const &in) +{ + auto out = in; + scaleDown(out); + return out; +} + +std::vector Luv::toCoordinates(std::vector const &in) +{ + auto out = in; + scaleUp(out); + return out; +} + +/** + * Utility function used to convert from the XYZ colorspace to CIELuv. + * https://en.wikipedia.org/wiki/CIELUV + * + * @param y Y component of the XYZ color. + * @return Luminance component of Luv color. + */ +static double y2l(double y) +{ + if (y <= EPSILON) + return y * KAPPA; + else + return 116.0 * std::cbrt(y) - 16.0; +} + +/** + * Utility function used to convert from CIELuv colorspace to XYZ. + * + * @param l Luminance component of Luv color. + * @return Y component of the XYZ color. + */ +static double l2y(double l) +{ + if (l <= 8.0) { + return l / KAPPA; + } else { + double x = (l + 16.0) / 116.0; + return (x * x * x); + } +} + +/** + * Convert a color from the the Luv colorspace to the XYZ colorspace. + * + * @param in_out[in,out] The Luv color converted to a XYZ color. + */ +void Luv::toXYZ(std::vector &in_out) +{ + if (in_out[0] <= 0.00000001) { + /* Black would create a divide-by-zero error. */ + in_out[0] = 0.0; + in_out[1] = 0.0; + in_out[2] = 0.0; + return; + } + + double var_u = in_out[1] / (13.0 * in_out[0]) + REF_U; + double var_v = in_out[2] / (13.0 * in_out[0]) + REF_V; + double y = l2y(in_out[0]); + double x = -(9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v); + double z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v); + + in_out[0] = x; + in_out[1] = y; + in_out[2] = z; +} + +/** + * Convert a color from the the XYZ colorspace to the Luv colorspace. + * + * @param in_out[in,out] The XYZ color converted to a Luv color. + */ +void Luv::fromXYZ(std::vector &in_out) +{ + double const denominator = in_out[0] + (15.0 * in_out[1]) + (3.0 * in_out[2]); + double var_u = 4.0 * in_out[0] / denominator; + double var_v = 9.0 * in_out[1] / denominator; + double l = y2l(in_out[1]); + double u = 13.0 * l * (var_u - REF_U); + double v = 13.0 * l * (var_v - REF_V); + + in_out[0] = l; + if (l < 0.00000001) { + in_out[1] = 0.0; + in_out[2] = 0.0; + } else { + in_out[1] = u; + in_out[2] = v; + } +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/luv.h b/src/colors/spaces/luv.h new file mode 100644 index 0000000000000000000000000000000000000000..2acc48e414e39ed82f5747bda77dd5b517595cdf --- /dev/null +++ b/src/colors/spaces/luv.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_LUV_H +#define SEEN_COLORS_SPACES_LUV_H + +#include "xyz.h" + +namespace Inkscape::Colors::Space { + +// CIE LUV constants +constexpr double KAPPA = 903.29629629629629629630; +constexpr double EPSILON = 0.00885645167903563082; + +class Luv : public RGB +{ +public: + Luv() = default; + ~Luv() override = default; + + Type getType() const override { return Type::LUV; } + std::string const getName() const override { return "Luv"; } + std::string const getIcon() const override { return "color-selector-luv"; } + +protected: + friend class Inkscape::Colors::Color; + + void spaceToProfile(std::vector &output) const override + { + Luv::scaleUp(output); + Luv::toXYZ(output); + XYZ::toLinearRGB(output); + LinearRGB::toRGB(output); + } + void profileToSpace(std::vector &output) const override + { + LinearRGB::fromRGB(output); + XYZ::fromLinearRGB(output); + Luv::fromXYZ(output); + Luv::scaleDown(output); + } + +public: + static void toXYZ(std::vector &output); + static void fromXYZ(std::vector &output); + + static void scaleDown(std::vector &in_out); + static void scaleUp(std::vector &in_out); + + static std::vector fromCoordinates(std::vector const &in); + static std::vector toCoordinates(std::vector const &in); +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_LUV_H diff --git a/src/colors/spaces/named.cpp b/src/colors/spaces/named.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d4985924551b9b04cbb0f7f2e69794cd71f8376a --- /dev/null +++ b/src/colors/spaces/named.cpp @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: see git history + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "named.h" + +#include +#include + +#include "colors/color.h" // SP_RGBA32_*_F + +namespace Inkscape::Colors::Space { + +class CssColor +{ +public: + typedef std::map ColorMap; + static const ColorMap color_map; +}; + +// These are all the colors defined in the CSS standard +CssColor::ColorMap const CssColor::color_map = { + // clang-format off + {"aliceblue", 0xF0F8FF}, + {"antiquewhite", 0xFAEBD7}, + {"aqua", 0x00FFFF}, + {"aquamarine", 0x7FFFD4}, + {"azure", 0xF0FFFF}, + {"beige", 0xF5F5DC}, + {"bisque", 0xFFE4C4}, + {"black", 0x000000}, + {"blanchedalmond", 0xFFEBCD}, + {"blue", 0x0000FF}, + {"blueviolet", 0x8A2BE2}, + {"brown", 0xA52A2A}, + {"burlywood", 0xDEB887}, + {"cadetblue", 0x5F9EA0}, + {"chartreuse", 0x7FFF00}, + {"chocolate", 0xD2691E}, + {"coral", 0xFF7F50}, + {"cornflowerblue", 0x6495ED}, + {"cornsilk", 0xFFF8DC}, + {"crimson", 0xDC143C}, + {"cyan", 0x00FFFF}, + {"darkblue", 0x00008B}, + {"darkcyan", 0x008B8B}, + {"darkgoldenrod", 0xB8860B}, + {"darkgray", 0xA9A9A9}, + {"darkgreen", 0x006400}, + {"darkgrey", 0xA9A9A9}, + {"darkkhaki", 0xBDB76B}, + {"darkmagenta", 0x8B008B}, + {"darkolivegreen", 0x556B2F}, + {"darkorange", 0xFF8C00}, + {"darkorchid", 0x9932CC}, + {"darkred", 0x8B0000}, + {"darksalmon", 0xE9967A}, + {"darkseagreen", 0x8FBC8F}, + {"darkslateblue", 0x483D8B}, + {"darkslategray", 0x2F4F4F}, + {"darkslategrey", 0x2F4F4F}, + {"darkturquoise", 0x00CED1}, + {"darkviolet", 0x9400D3}, + {"deeppink", 0xFF1493}, + {"deepskyblue", 0x00BFFF}, + {"dimgray", 0x696969}, + {"dimgrey", 0x696969}, + {"dodgerblue", 0x1E90FF}, + {"firebrick", 0xB22222}, + {"floralwhite", 0xFFFAF0}, + {"forestgreen", 0x228B22}, + {"fuchsia", 0xFF00FF}, + {"gainsboro", 0xDCDCDC}, + {"ghostwhite", 0xF8F8FF}, + {"gold", 0xFFD700}, + {"goldenrod", 0xDAA520}, + {"gray", 0x808080}, + {"grey", 0x808080}, + {"green", 0x008000}, + {"greenyellow", 0xADFF2F}, + {"honeydew", 0xF0FFF0}, + {"hotpink", 0xFF69B4}, + {"indianred", 0xCD5C5C}, + {"indigo", 0x4B0082}, + {"ivory", 0xFFFFF0}, + {"khaki", 0xF0E68C}, + {"lavender", 0xE6E6FA}, + {"lavenderblush", 0xFFF0F5}, + {"lawngreen", 0x7CFC00}, + {"lemonchiffon", 0xFFFACD}, + {"lightblue", 0xADD8E6}, + {"lightcoral", 0xF08080}, + {"lightcyan", 0xE0FFFF}, + {"lightgoldenrodyellow", 0xFAFAD2}, + {"lightgray", 0xD3D3D3}, + {"lightgreen", 0x90EE90}, + {"lightgrey", 0xD3D3D3}, + {"lightpink", 0xFFB6C1}, + {"lightsalmon", 0xFFA07A}, + {"lightseagreen", 0x20B2AA}, + {"lightskyblue", 0x87CEFA}, + {"lightslategray", 0x778899}, + {"lightslategrey", 0x778899}, + {"lightsteelblue", 0xB0C4DE}, + {"lightyellow", 0xFFFFE0}, + {"lime", 0x00FF00}, + {"limegreen", 0x32CD32}, + {"linen", 0xFAF0E6}, + {"magenta", 0xFF00FF}, + {"maroon", 0x800000}, + {"mediumaquamarine", 0x66CDAA}, + {"mediumblue", 0x0000CD}, + {"mediumorchid", 0xBA55D3}, + {"mediumpurple", 0x9370DB}, + {"mediumseagreen", 0x3CB371}, + {"mediumslateblue", 0x7B68EE}, + {"mediumspringgreen", 0x00FA9A}, + {"mediumturquoise", 0x48D1CC}, + {"mediumvioletred", 0xC71585}, + {"midnightblue", 0x191970}, + {"mintcream", 0xF5FFFA}, + {"mistyrose", 0xFFE4E1}, + {"moccasin", 0xFFE4B5}, + {"navajowhite", 0xFFDEAD}, + {"navy", 0x000080}, + {"oldlace", 0xFDF5E6}, + {"olive", 0x808000}, + {"olivedrab", 0x6B8E23}, + {"orange", 0xFFA500}, + {"orangered", 0xFF4500}, + {"orchid", 0xDA70D6}, + {"palegoldenrod", 0xEEE8AA}, + {"palegreen", 0x98FB98}, + {"paleturquoise", 0xAFEEEE}, + {"palevioletred", 0xDB7093}, + {"papayawhip", 0xFFEFD5}, + {"peachpuff", 0xFFDAB9}, + {"peru", 0xCD853F}, + {"pink", 0xFFC0CB}, + {"plum", 0xDDA0DD}, + {"powderblue", 0xB0E0E6}, + {"purple", 0x800080}, + {"rebeccapurple", 0x663399}, + {"red", 0xFF0000}, + {"rosybrown", 0xBC8F8F}, + {"royalblue", 0x4169E1}, + {"saddlebrown", 0x8B4513}, + {"salmon", 0xFA8072}, + {"sandybrown", 0xF4A460}, + {"seagreen", 0x2E8B57}, + {"seashell", 0xFFF5EE}, + {"sienna", 0xA0522D}, + {"silver", 0xC0C0C0}, + {"skyblue", 0x87CEEB}, + {"slateblue", 0x6A5ACD}, + {"slategray", 0x708090}, + {"slategrey", 0x708090}, + {"snow", 0xFFFAFA}, + {"springgreen", 0x00FF7F}, + {"steelblue", 0x4682B4}, + {"tan", 0xD2B48C}, + {"teal", 0x008080}, + {"thistle", 0xD8BFD8}, + {"tomato", 0xFF6347}, + {"turquoise", 0x40E0D0}, + {"violet", 0xEE82EE}, + {"wheat", 0xF5DEB3}, + {"white", 0xFFFFFF}, + {"whitesmoke", 0xF5F5F5}, + {"yellow", 0xFFFF00}, + {"yellowgreen", 0x9ACD32}, + // clang-format on +}; + +/** + * Parse a name into RGB values. + */ +bool NamedColor::NameParser::parse(std::istringstream &ss, std::vector &output) const +{ + std::string name; + ss >> name; // ignore whitespace then string + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + auto it = CssColor::color_map.find(name); + if (it != CssColor::color_map.end()) { + auto rgb32 = it->second << 8; + output.emplace_back(SP_RGBA32_R_F(rgb32)); + output.emplace_back(SP_RGBA32_G_F(rgb32)); + output.emplace_back(SP_RGBA32_B_F(rgb32)); + // There is never any opacity set for named colors + return true; + } + return false; +} + +/** + * Print the RGB color to an SVG Tiny compatible color name, or fall back to RGB hex. + * + * @arg values - A vector of doubles for each channel in the RGB space + * @arg opacity - True if the opacity should be included in the output. + */ +std::string NamedColor::toString(std::vector const &values, bool opacity) const +{ + // If opacity is set, pass down + if (values.size() == 4 && opacity) + return RGB::toString(values, true); + + auto name = getNameFor(toRGBA(values)); + if (!name.empty()) + return name; + + return RGB::toString(values, opacity); +} + +/** + * Look up the css color name, if not found returns empty string. + */ +std::string NamedColor::getNameFor(unsigned int rgba) +{ + rgba >>= 8; + // We removed the SVG Tiny support for named colors because it disrupted our ability + // to support SVG named colors properly. If support is needed, add it to a new space. + for (auto &pair : CssColor::color_map) { + if (pair.second == rgba) { + return pair.first; + } + } + return ""; +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/named.h b/src/colors/spaces/named.h new file mode 100644 index 0000000000000000000000000000000000000000..b3dbb46fa7505c58cf922993d2d856eaa6bf4db2 --- /dev/null +++ b/src/colors/spaces/named.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_NAMED_H +#define SEEN_COLORS_SPACES_NAMED_H + +#include "rgb.h" + +namespace Inkscape::Colors::Space { + +/** + * A named color is still a purely RGB color, it's just formatted so it + * can be written back out as a named color faithfully. + */ +class NamedColor : public RGB +{ +public: + NamedColor() = default; + ~NamedColor() override = default; + + Type getType() const override { return Type::CSSNAME; } + Type getComponentType() const override { return Type::RGB; } + std::string const getName() const override { return "CSSNAME"; } + std::string const getIcon() const override { return "color-selector-named"; } + + static std::string getNameFor(unsigned int rgba); + +protected: + friend class Inkscape::Colors::Color; + + std::string toString(std::vector const &values, bool opacity = true) const override; + +public: + class NameParser : public Colors::Parser + { + public: + NameParser() + : Colors::Parser("", Type::CSSNAME) + {} + bool parse(std::istringstream &input, std::vector &output) const override; + }; +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_NAMED_H diff --git a/src/colors/spaces/okhsl.cpp b/src/colors/spaces/okhsl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..54ad62eaea1842a585b793afcbb2f1dcdb672e30 --- /dev/null +++ b/src/colors/spaces/okhsl.cpp @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: + * Rafał Siejakowski + * Martin Owens + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "okhsl.h" + +#include <2geom/angle.h> +#include + +#include "oklch.h" // max_chroma + +namespace Inkscape::Colors::Space { + +/** + * Convert a color from the the OkHsl colorspace to the OkLab colorspace. + * + * @param in_out[in,out] The OkHsl color converted to an OkLab color. + */ +void OkHsl::toOkLab(std::vector &in_out) +{ + double l = std::clamp(in_out[2], 0.0, 1.0); + + // Get max chroma for this hue and lightness and compute the absolute chroma. + double const chromax = OkLch::max_chroma(l, in_out[0] * 360.0); + double const absolute_chroma = in_out[1] * chromax; + + // Convert hue and chroma to the Cartesian a, b coordinates. + Geom::sincos(in_out[0] * 2.0 * M_PI, in_out[2], in_out[1]); + in_out[0] = l; + in_out[1] *= absolute_chroma; + in_out[2] *= absolute_chroma; +} + +/** + * Convert a color from the the OkLab colorspace to the OkHsl colorspace. + * + * @param in_out[in,out] The OkLab color converted to an OkHsl color. + */ +void OkHsl::fromOkLab(std::vector &in_out) +{ + // Compute the chroma. + double const absolute_chroma = std::hypot(in_out[1], in_out[2]); + if (absolute_chroma < 1e-7) { + // It would be numerically unstable to calculate the hue for this + // color, so we set the hue and saturation to zero (grayscale color). + in_out[2] = in_out[0]; + in_out[1] = 0.0; + in_out[0] = 0.0; + } + + // Compute the hue (in the unit interval). + Geom::Angle const hue_angle = std::atan2(in_out[2], in_out[1]); + in_out[2] = std::clamp(in_out[0], 0.0, 1.0); + in_out[0] = hue_angle.radians0() / (2.0 * M_PI); + + // Compute the linear saturation. + double const hue_degrees = Geom::deg_from_rad(hue_angle.radians0()); + double const chromax = OkLch::max_chroma(in_out[2], hue_degrees); + in_out[1] = (chromax == 0.0) ? 0.0 : std::clamp(absolute_chroma / chromax, 0.0, 1.0); +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/okhsl.h b/src/colors/spaces/okhsl.h new file mode 100644 index 0000000000000000000000000000000000000000..1a9384796b996cb00abe67c4b330ca6c82750dac --- /dev/null +++ b/src/colors/spaces/okhsl.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_OKHSL_H +#define SEEN_COLORS_SPACES_OKHSL_H + +#include "oklab.h" + +namespace Inkscape::Colors::Space { + +class OkHsl : public RGB +{ +public: + OkHsl() = default; + ~OkHsl() override = default; + + Type getType() const override { return Type::OKHSL; } + std::string const getName() const override { return "OkHsl"; } + std::string const getIcon() const override { return "color-selector-okhsl"; } + +protected: + friend class Inkscape::Colors::Color; + + void spaceToProfile(std::vector &output) const override + { + OkHsl::toOkLab(output); + OkLab::toLinearRGB(output); + LinearRGB::toRGB(output); + } + void profileToSpace(std::vector &output) const override + { + LinearRGB::fromRGB(output); + OkLab::fromLinearRGB(output); + OkHsl::fromOkLab(output); + } + +public: + static void toOkLab(std::vector &output); + static void fromOkLab(std::vector &output); +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_OKHSL_H diff --git a/src/colors/spaces/oklab.cpp b/src/colors/spaces/oklab.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a666cdf27769daacba6fbdc581a2f726a0769797 --- /dev/null +++ b/src/colors/spaces/oklab.cpp @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: + * Rafał Siejakowski + * Martin Owens + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "oklab.h" + +#include <2geom/math-utils.h> +#include + +#include "colors/printer.h" + +namespace Inkscape::Colors::Space { + +/* These values are technically unbounded in the actual calculations but are + * defined between -0.4 and 0.4 by the CSS Color Module specification + * as the reasonable upper and lower limits for display. Our internal model + * always scales from 0 to 1 of this expected range of values. + */ +constexpr double MIN_SCALE = -0.4; +constexpr double MAX_SCALE = 0.4; + +void OkLab::scaleUp(std::vector &in_out) +{ + in_out[1] = SCALE_UP(in_out[1], MIN_SCALE, MAX_SCALE); + in_out[2] = SCALE_UP(in_out[2], MIN_SCALE, MAX_SCALE); +} + +void OkLab::scaleDown(std::vector &in_out) +{ + in_out[1] = SCALE_DOWN(in_out[1], MIN_SCALE, MAX_SCALE); + in_out[2] = SCALE_DOWN(in_out[2], MIN_SCALE, MAX_SCALE); +} + +/** Two-dimensional array to store a constant 3x3 matrix. */ +using Matrix = const double[3][3]; + +// clang-format off +/** Matrix of the linear transformation from linear RGB space to linear + * cone responses, used in the first step of RGB to OKLab conversion. + */ +Matrix LRGB2CONE = {{0.4122214708, 0.5363325363, 0.0514459929}, + {0.2119034982, 0.6806995451, 0.1073969566}, + {0.0883024619, 0.2817188376, 0.6299787005}}; + +/** The inverse of the matrix LRGB2CONE. */ +Matrix CONE2LRGB = { + { 4.0767416613479942676681908333711298900607278264432, -3.30771159040819331315866078424893188865618253342, 0.230969928729427886449650619561935920170561518112 }, + { -1.2684380040921760691815055595117506020901414005992, 2.60975740066337143024050095284233623056192338553, -0.341319396310219620992658250306535533187548361872 }, + { -0.0041960865418371092973767821251846315637521173374, -0.70341861445944960601310996913659932654899822384, 1.707614700930944853864541790660472961199090408527 } +}; + +/** The matrix M2 used in the second step of RGB to OKLab conversion. + * Taken from https://bottosson.github.io/posts/oklab/ (retrieved 2022). + */ +Matrix M2 = {{0.2104542553, 0.793617785, -0.0040720468}, + {1.9779984951, -2.428592205, 0.4505937099}, + {0.0259040371, 0.7827717662, -0.808675766}}; + +/** The inverse of the matrix M2. The first column looks like it wants to be 1 but + * this form is closer to the actual inverse (due to numerics). */ +Matrix M2_INVERSE = { + { 0.99999999845051981426207542502031373637162589278552, 0.39633779217376785682345989261573192476766903603, 0.215803758060758803423141461830037892590617787467 }, + { 1.00000000888176077671607524567047071276183677410134, -0.10556134232365634941095687705472233997368274024, -0.063854174771705903405254198817795633810975771082 }, + { 1.00000005467241091770129286515344610721841028698942, -0.08948418209496575968905274586339134130669669716, -1.291485537864091739948928752914772401878545675371 } +}; +// clang-format on + +/** Compute the dot-product between two 3D-vectors. */ +template +inline constexpr double dot3(const A1 &a1, const A2 &a2) +{ + return a1[0] * a2[0] + a1[1] * a2[1] + a1[2] * a2[2]; +} + +/** + * Convert a color from the the OKLab colorspace to the Linear RGB colorspace. + * + * @param in_out[in,out] The OKLab color converted to a Linear RGB color. + */ +void OkLab::toLinearRGB(std::vector &in_out) +{ + std::array cones; + for (unsigned i = 0; i < 3; i++) { + cones[i] = Geom::cube(dot3(M2_INVERSE[i], in_out)); + } + for (unsigned i = 0; i < 3; i++) { + in_out[i] = std::clamp(dot3(CONE2LRGB[i], cones), 0.0, 1.0); + } +} + +/** + * Convert a color from the the Linear RGB colorspace to the OKLab colorspace. + * + * @param in_out[in,out] The Linear RGB color converted to a OKLab color. + */ +void OkLab::fromLinearRGB(std::vector &in_out) +{ + std::vector cones(3); + for (unsigned i = 0; i < 3; i++) { + cones[i] = std::cbrt(dot3(LRGB2CONE[i], in_out)); + } + for (unsigned i = 0; i < 3; i++) { + in_out[i] = dot3(M2[i], cones); + } +} + +bool OkLab::Parser::parse(std::istringstream &ss, std::vector &output) const +{ + bool end = false; + if (append_css_value(ss, output, end, ',') // Luminance + && append_css_value(ss, output, end, ',', MAX_SCALE) // Chroma A + && append_css_value(ss, output, end, '/', MAX_SCALE) // Chroma B + && (append_css_value(ss, output, end) || true) // Optional opacity + && end) { + // Values are between -100% and 100% so post processed into the range 0 to 1 + output[1] = (output[1] + 1) / 2; + output[2] = (output[2] + 1) / 2; + return true; + } + return false; +} + +/** + * Print the Lab color to a CSS string. + * + * @arg values - A vector of doubles for each channel in the Lch space + * @arg opacity - True if the opacity should be included in the output. + */ +std::string OkLab::toString(std::vector const &values, bool opacity) const +{ + auto os = CssFuncPrinter(3, "oklab"); + + os << values[0] // Luminance + << SCALE_UP(values[1], MIN_SCALE, MAX_SCALE) // Chroma A + << SCALE_UP(values[2], MIN_SCALE, MAX_SCALE); // Chroma B + + if (opacity && values.size() == 4) + os << values[3]; // Optional opacity + + return os; +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/oklab.h b/src/colors/spaces/oklab.h new file mode 100644 index 0000000000000000000000000000000000000000..5a8c9908c599b0f4e302e51ac4c74ab150649951 --- /dev/null +++ b/src/colors/spaces/oklab.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_OKLAB_H +#define SEEN_COLORS_SPACES_OKLAB_H + +#include "linear-rgb.h" + +namespace Inkscape::Colors::Space { + +class OkLab : public RGB +{ +public: + OkLab() = default; + ~OkLab() override = default; + + Type getType() const override { return Type::OKLAB; } + std::string const getName() const override { return "OkLab"; } + std::string const getIcon() const override { return "color-selector-oklab"; } + +protected: + friend class Inkscape::Colors::Color; + + void spaceToProfile(std::vector &output) const override + { + scaleUp(output); + OkLab::toLinearRGB(output); + LinearRGB::toRGB(output); + } + void profileToSpace(std::vector &output) const override + { + LinearRGB::fromRGB(output); + OkLab::fromLinearRGB(output); + scaleDown(output); + } + + std::string toString(std::vector const &values, bool opacity) const override; + +public: + class Parser : public Colors::Parser + { + public: + Parser() + : Colors::Parser("oklab", Type::OKLAB) + {} + bool parse(std::istringstream &input, std::vector &output) const override; + }; + + static void toLinearRGB(std::vector &output); + static void fromLinearRGB(std::vector &output); + + static void scaleUp(std::vector &in_out); + static void scaleDown(std::vector &in_out); +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_OKLAB_H diff --git a/src/oklab.cpp b/src/colors/spaces/oklch.cpp similarity index 59% rename from src/oklab.cpp rename to src/colors/spaces/oklch.cpp index 80f994e02b40a69cf4f05833ee17ab2b0fea6e0f..0e0d660f170f7c5ee0652e2e78c366f5abb942ab 100644 --- a/src/oklab.cpp +++ b/src/colors/spaces/oklch.cpp @@ -1,190 +1,90 @@ // SPDX-License-Identifier: GPL-2.0-or-later -/** @file Implementation of the OKLab/OKLch perceptual color space. - */ -/* +/** @file + *//* * Authors: * Rafał Siejakowski + * Martin Owens * - * Copyright (C) 2022 Authors - * + * Copyright (C) 2023 Authors * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ -#include "oklab.h" - -#include +#include "oklch.h" #include <2geom/angle.h> -#include <2geom/math-utils.h> #include <2geom/polynomial.h> -#include "color.h" +#include -namespace Oklab { +#include "colors/color.h" +#include "colors/printer.h" -/** Two-dimensional array to store a constant 3x3 matrix. */ -using Matrix = const double[3][3]; +namespace Inkscape::Colors::Space { -/** Matrix of the linear transformation from linear RGB space to linear - * cone responses, used in the first step of RGB to OKLab conversion. +/* Chroma is technically unbounded in the actual calculations but is + * defined between 0.0 and 0.4 by the CSS Color Module specification + * as the reasonable upper and lower limits for display. Our internal model + * always scales from 0 to 1 of this expected range of values. */ -Matrix LRGB2CONE = { - { 0.4122214708, 0.5363325363, 0.0514459929 }, - { 0.2119034982, 0.6806995451, 0.1073969566 }, - { 0.0883024619, 0.2817188376, 0.6299787005 } -}; +constexpr double CHROMA_SCALE = 0.4; +constexpr double HUE_SCALE = 360; -/** The inverse of the matrix LRGB2CONE. */ -Matrix CONE2LRGB = { - { 4.0767416613479942676681908333711298900607278264432, -3.30771159040819331315866078424893188865618253342, 0.230969928729427886449650619561935920170561518112 }, - { -1.2684380040921760691815055595117506020901414005992, 2.60975740066337143024050095284233623056192338553, -0.341319396310219620992658250306535533187548361872 }, - { -0.0041960865418371092973767821251846315637521173374, -0.70341861445944960601310996913659932654899822384, 1.707614700930944853864541790660472961199090408527 } -}; - -/** The matrix M2 used in the second step of RGB to OKLab conversion. - * Taken from https://bottosson.github.io/posts/oklab/ (retrieved 2022). +/** + * Convert a color from the the OkLch colorspace to the OKLab colorspace. + * + * @param in_out[in,out] The OkLch color converted to an OKLab color. */ -Matrix M2 = { - { 0.2104542553, 0.793617785, -0.0040720468 }, - { 1.9779984951, -2.428592205, 0.4505937099 }, - { 0.0259040371, 0.7827717662, -0.808675766 } -}; - -/** The inverse of the matrix M2. The first column looks like it wants to be 1 but - * this form is closer to the actual inverse (due to numerics). */ -Matrix M2_INVERSE = { - { 0.99999999845051981426207542502031373637162589278552, 0.39633779217376785682345989261573192476766903603, 0.215803758060758803423141461830037892590617787467 }, - { 1.00000000888176077671607524567047071276183677410134, -0.10556134232365634941095687705472233997368274024, -0.063854174771705903405254198817795633810975771082 }, - { 1.00000005467241091770129286515344610721841028698942, -0.08948418209496575968905274586339134130669669716, -1.291485537864091739948928752914772401878545675371 } -}; - -/** Compute the dot-product between two 3D-vectors. */ -template -inline constexpr double dot3(const A1 &a1, const A2 &a2) +void OkLch::toOkLab(std::vector &in_out) { - return a1[0] * a2[0] + a1[1] * a2[1] + a1[2] * a2[2]; + // c and h are polar coordinates; convert to Cartesian a, b coords. + double c = in_out[1]; + Geom::sincos(Geom::Angle::from_degrees(in_out[2] * HUE_SCALE), in_out[2], in_out[1]); + in_out[1] *= c; + in_out[2] *= c; } -Triplet oklab_to_oklch(Triplet const &ok_lab_color) +/** + * Convert a color from the the OKLab colorspace to the OkLch colorspace. + * + * @param in_out[in,out] The OKLab color converted to an OkLch color. + */ +void OkLch::fromOkLab(std::vector &in_out) { - Triplet result; - result[0] = ok_lab_color[0]; // copy the L component // Convert a, b to polar coordinates c, h. - result[1] = std::hypot(ok_lab_color[1], ok_lab_color[2]); - if (result[1] > 0.001) { - Geom::Angle const hue_angle = std::atan2(ok_lab_color[2], ok_lab_color[1]); - result[2] = Geom::deg_from_rad(hue_angle.radians0()); + double c = std::hypot(in_out[1], in_out[2]); + if (c > 0.001) { + Geom::Angle const hue_angle = std::atan2(in_out[2], in_out[1]); + in_out[2] = Geom::deg_from_rad(hue_angle.radians0()) / HUE_SCALE; } else { - result[2] = 0; + in_out[2] = 0; } - return result; -} - -Triplet oklch_to_oklab(Triplet const &ok_lch_color) -{ - return oklch_radians_to_oklab({ ok_lch_color[0], - ok_lch_color[1], - Geom::Angle::from_degrees(ok_lch_color[2]) }); -} - -Triplet oklch_radians_to_oklab(Triplet const &oklch_rad) -{ - Triplet result; - result[0] = oklch_rad[0]; // Copy the L component - // c and h are polar coordinates; convert to Cartesian a, b coords. - Geom::sincos(oklch_rad[2], result[2], result[1]); - result[1] *= oklch_rad[1]; - result[2] *= oklch_rad[1]; - return result; -} - -Triplet oklab_to_linear_rgb(Triplet const &oklab_color) -{ - Triplet cones; - for (unsigned i = 0; i < 3; i++) { - cones[i] = Geom::cube(dot3(M2_INVERSE[i], oklab_color)); - } - Triplet result; - for (unsigned i = 0; i < 3; i++) { - result[i] = std::clamp(dot3(CONE2LRGB[i], cones), 0.0, 1.0); - } - return result; -} - -Triplet linear_rgb_to_oklab(Triplet const &linear_rgb_color) -{ - Triplet cones; - for (unsigned i = 0; i < 3; i++) { - cones[i] = std::cbrt(dot3(LRGB2CONE[i], linear_rgb_color)); - } - Triplet result; - for (unsigned i = 0; i < 3; i++) { - result[i] = dot3(M2[i], cones); - } - return result; -} - -Triplet oklab_to_okhsl(Triplet const &ok_lab_color) -{ - Triplet result; - result[2] = std::clamp(ok_lab_color[0], 0.0, 1.0); // Copy the L component. - - // Compute the chroma. - double const absolute_chroma = std::hypot(ok_lab_color[1], ok_lab_color[2]); - if (absolute_chroma < 1e-7) { - // It would be numerically unstable to calculate the hue for this - // color, so we set the hue and saturation to zero (grayscale color). - result[0] = 0.0; - result[1] = 0.0; - return result; - } - - // Compute the hue (in the unit interval). - Geom::Angle const hue_angle = std::atan2(ok_lab_color[2], ok_lab_color[1]); - result[0] = hue_angle.radians0() / (2.0 * M_PI); - - // Compute the linear saturation. - double const hue_degrees = Geom::deg_from_rad(hue_angle.radians0()); - double const chromax = max_chroma(result[2], hue_degrees); - result[1] = (chromax == 0.0) ? 0.0 : std::clamp(absolute_chroma / chromax, 0.0, 1.0); - return result; -} - -Triplet okhsl_to_oklab(Triplet const &ok_hsl_color) -{ - Triplet result; - result[0] = std::clamp(ok_hsl_color[2], 0.0, 1.0); // Copy the L component. - - // Get max chroma for this hue and lightness and compute the absolute chroma. - double const chromax = max_chroma(result[0], ok_hsl_color[0] * 360.0); - double const absolute_chroma = ok_hsl_color[1] * chromax; - - // Convert hue and chroma to the Cartesian a, b coordinates. - Geom::sincos(ok_hsl_color[0] * 2.0 * M_PI, result[2], result[1]); - result[1] *= absolute_chroma; - result[2] *= absolute_chroma; - return result; + in_out[1] = c; } /** @brief * Data needed to compute coefficients in the cubic polynomials which express the lines * of constant luminosity and hue (but varying chroma) as curves in the linear RGB space. */ -struct ChromaLineCoefficients { +struct ChromaLineCoefficients +{ // Variable naming: `c%d` contains coefficients of c^%d in the polynomial, where c is // the OKLch chroma. l refers to the luminosity, cos and sin to the cosine and sine of // the hue angle. Trailing digits are exponents. For example, // c2.lcos2 is the coefficient of (l * cos(hue_angle)^2) in the overall coefficient of c^2. - struct { + struct + { double l2cos, l2sin; } c1; - struct { + struct + { double lcos2, lcossin, lsin2; } c2; - struct { + struct + { double cos3, cos2sin, cossin2, sin3; } c3; }; +// clang-format off ChromaLineCoefficients const LAB_BOUNDS[] = { // Red polynomial { @@ -241,6 +141,7 @@ ChromaLineCoefficients const LAB_BOUNDS[] = { } } }; +// clang-format on /** Stores powers of luminance, hue cosine and hue sine angles. */ struct ConstraintMonomials @@ -277,8 +178,8 @@ static std::array component_coefficients(unsigned index, ConstraintMo result[0] = m.l3; // The coefficient of l^3 is always 1 result[1] = coeffs.c1.l2cos * m.l2 * m.c + coeffs.c1.l2sin * m.l2 * m.s; result[2] = coeffs.c2.lcos2 * m.l * m.c2 + coeffs.c2.lcossin * m.l * m.c * m.s + coeffs.c2.lsin2 * m.l * m.s2; - result[3] = coeffs.c3.cos3 * m.c3 + coeffs.c3.cos2sin * m.c2 * m.s - + coeffs.c3.cossin2 * m.c * m.s2 + coeffs.c3.sin3 * m.s3; + result[3] = + coeffs.c3.cos3 * m.c3 + coeffs.c3.cos2sin * m.c2 * m.s + coeffs.c3.cossin2 * m.c * m.s2 + coeffs.c3.sin3 * m.s3; return result; } @@ -303,7 +204,7 @@ static std::array component_coefficients(unsigned index, ConstraintMo * * The case of very small or very large luminosity is handled separately. */ -double max_chroma(double l, double h) +double OkLch::max_chroma(double l, double h) { static double const EPS = 1e-7; if (l < EPS || l > 1.0 - EPS) { // Black or white allow no chroma. @@ -368,22 +269,22 @@ uint8_t const *render_hue_scale(double s, double l, std::array(j) / interval_length, initial_chroma, final_chroma); - auto [r, g, b] = oklab_to_rgb(oklch_to_oklab({l, c, h})); - *pos++ = (uint8_t)SP_COLOR_F_TO_U(r); - *pos++ = (uint8_t)SP_COLOR_F_TO_U(g); - *pos++ = (uint8_t)SP_COLOR_F_TO_U(b); + auto rgb = *Color(Space::Type::OKLCH, {l, c, h / 360}).converted(Space::Type::RGB); + *pos++ = (uint8_t)SP_COLOR_F_TO_U(rgb[0]); + *pos++ = (uint8_t)SP_COLOR_F_TO_U(rgb[1]); + *pos++ = (uint8_t)SP_COLOR_F_TO_U(rgb[2]); *pos++ = 0xFF; h += step; } @@ -396,23 +297,23 @@ uint8_t const *render_saturation_scale(double h, double l, std::arraydata(); auto pos = data; - auto chromax = max_chroma(l, h); + auto chromax = OkLch::max_chroma(l, h); if (chromax == 0.0) { // Render black or white strip. uint8_t const bw = (l > 0.9) ? 0xFF : 0x00; for (size_t i = 0; i < 1024; i++) { - *pos++ = bw; // red - *pos++ = bw; // green - *pos++ = bw; // blue + *pos++ = bw; // red + *pos++ = bw; // green + *pos++ = bw; // blue *pos++ = 0xFF; // alpha } } else { // Render strip of varying chroma. double const chroma_step = chromax / 1024.0; double c = 0.0; for (size_t i = 0; i < 1024; i++) { - auto [r, g, b] = oklab_to_rgb(oklch_to_oklab({l, c, h})); - *pos++ = (uint8_t)SP_COLOR_F_TO_U(r); - *pos++ = (uint8_t)SP_COLOR_F_TO_U(g); - *pos++ = (uint8_t)SP_COLOR_F_TO_U(b); + auto rgb = *Color(Space::Type::OKLCH, {l, c, h}).converted(Space::Type::RGB); + *pos++ = (uint8_t)SP_COLOR_F_TO_U(rgb[0]); + *pos++ = (uint8_t)SP_COLOR_F_TO_U(rgb[1]); + *pos++ = (uint8_t)SP_COLOR_F_TO_U(rgb[2]); *pos++ = 0xFF; c += chroma_step; } @@ -428,22 +329,22 @@ uint8_t const *render_lightness_scale(double h, double s, std::array(j) / interval_length, initial_chroma, final_chroma); - auto [r, g, b] = oklab_to_rgb(oklch_to_oklab({l, c, h})); - *pos++ = (uint8_t)SP_COLOR_F_TO_U(r); - *pos++ = (uint8_t)SP_COLOR_F_TO_U(g); - *pos++ = (uint8_t)SP_COLOR_F_TO_U(b); + auto rgb = *Color(Space::Type::OKLCH, {l, c, h}).converted(Space::Type::RGB); + *pos++ = (uint8_t)SP_COLOR_F_TO_U(rgb[0]); + *pos++ = (uint8_t)SP_COLOR_F_TO_U(rgb[1]); + *pos++ = (uint8_t)SP_COLOR_F_TO_U(rgb[2]); *pos++ = 0xFF; l += step; } @@ -452,15 +353,37 @@ uint8_t const *render_lightness_scale(double h, double s, std::array &output) const +{ + bool end = false; + if (append_css_value(ss, output, end, ',') // Luminance + && append_css_value(ss, output, end, ',', CHROMA_SCALE) // Chroma + && append_css_value(ss, output, end, '/', HUE_SCALE) // Hue + && (append_css_value(ss, output, end) || true) // Optional opacity + && end) { + return true; + } + return false; +} + +/** + * Print the Lab color to a CSS string. + * + * @arg values - A vector of doubles for each channel in the Lch space + * @arg opacity - True if the opacity should be included in the output. + */ +std::string OkLch::toString(std::vector const &values, bool opacity) const +{ + auto os = CssFuncPrinter(3, "oklch"); + + os << values[0] // Luminance + << values[1] * CHROMA_SCALE // Chroma + << values[2] * HUE_SCALE; // Hue + + if (opacity && values.size() == 4) + os << values[3]; // Optional opacity + + return os; +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/oklch.h b/src/colors/spaces/oklch.h new file mode 100644 index 0000000000000000000000000000000000000000..a2529c93407019de36c6d4ec4e92cb7881af8341 --- /dev/null +++ b/src/colors/spaces/oklch.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_OKLCH_H +#define SEEN_COLORS_SPACES_OKLCH_H + +#include "oklab.h" + +namespace Inkscape::Colors::Space { + +class OkLch : public RGB +{ +public: + OkLch() = default; + ~OkLch() override = default; + + Type getType() const override { return Type::OKLCH; } + std::string const getName() const override { return "OkLch"; } + std::string const getIcon() const override { return "color-selector-oklch"; } + +protected: + friend class Inkscape::Colors::Color; + + void spaceToProfile(std::vector &output) const override + { + OkLch::toOkLab(output); + OkLab::toLinearRGB(output); + LinearRGB::toRGB(output); + } + void profileToSpace(std::vector &output) const override + { + LinearRGB::fromRGB(output); + OkLab::fromLinearRGB(output); + OkLch::fromOkLab(output); + } + + std::string toString(std::vector const &values, bool opacity) const override; + +public: + class Parser : public Colors::Parser + { + public: + Parser() + : Colors::Parser("oklch", Type::OKLCH) + {} + bool parse(std::istringstream &input, std::vector &output) const override; + }; + + static void toOkLab(std::vector &output); + static void fromOkLab(std::vector &output); + static double max_chroma(double l, double h); +}; + +uint8_t const *render_hue_scale(double s, double l, std::array *map); +uint8_t const *render_saturation_scale(double h, double l, std::array *map); +uint8_t const *render_lightness_scale(double h, double s, std::array *map); + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_OKLCH_H diff --git a/src/colors/spaces/rgb.cpp b/src/colors/spaces/rgb.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4ae73b29707b43e524ba1054e614698eb957a42f --- /dev/null +++ b/src/colors/spaces/rgb.cpp @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: see git history + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "rgb.h" + +#include + +#include "colors/cms/profile.h" +#include "colors/color.h" +#include "colors/utils.h" + +namespace Inkscape::Colors::Space { + +/** + * Return the RGB color profile, this is static for all RGB sub-types + */ +std::shared_ptr const RGB::getProfile() const +{ + static std::shared_ptr srgb_profile; + if (!srgb_profile) { + srgb_profile = Colors::CMS::Profile::create_srgb(); + } + return srgb_profile; +} + +/** + * Print the RGB color to a CSS Hex code of 6 or 8 digits. + * + * @arg values - A vector of doubles for each channel in the RGB space + * @arg opacity - True if the opacity should be included in the output. + */ +std::string RGB::toString(std::vector const &values, bool opacity) const +{ + return rgba_to_hex(toRGBA(values), values.size() == 4 && opacity); +} + +/** + * Convert the color into an RGBA32 for use within Gdk rendering. + */ +uint32_t RGB::toRGBA(std::vector const &values, double opacity) const +{ + if (getType() != Type::RGB) { + std::vector copy = values; + spaceToProfile(copy); + return _to_rgba(copy, opacity); + } + return _to_rgba(values, opacity); +} + +uint32_t RGB::_to_rgba(std::vector const &values, double opacity) const +{ + switch (values.size()) { + case 3: + return SP_RGBA32_F_COMPOSE(values[0], values[1], values[2], opacity); + case 4: + return SP_RGBA32_F_COMPOSE(values[0], values[1], values[2], opacity * values[3]); + default: + throw ColorError("Color values should be size 3 for RGB or 4 for RGBA."); + } + return 0x0; // transparent black +} + +bool RGB::Parser::parse(std::istringstream &ss, std::vector &output) const +{ + bool end = false; + return append_css_value(ss, output, end, ',', 255) // Red + && append_css_value(ss, output, end, ',', 255) // Green + && append_css_value(ss, output, end, !_alpha ? '/' : ',', 255) // Blue + && (append_css_value(ss, output, end) || !_alpha) // Opacity + && end; +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/rgb.h b/src/colors/spaces/rgb.h new file mode 100644 index 0000000000000000000000000000000000000000..3754bea015582ceac05ee2a9631b69d0d4e743c1 --- /dev/null +++ b/src/colors/spaces/rgb.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_RGB_H +#define SEEN_COLORS_SPACES_RGB_H + +#include "base.h" + +namespace Inkscape::Colors::Space { + +class RGB : public AnySpace +{ +public: + RGB() = default; + ~RGB() override = default; + + Type getType() const override { return Type::RGB; } + std::string const getName() const override { return "RGB"; } + std::string const getIcon() const override { return "color-selector-rgb"; } + unsigned int getComponentCount() const override { return 3; } + std::shared_ptr const getProfile() const override; + +protected: + friend class Inkscape::Colors::Color; + + std::string toString(std::vector const &values, bool opacity = true) const override; + uint32_t toRGBA(std::vector const &values, double opacity = 1.0) const override; + +public: + class Parser : public LegacyParser + { + public: + Parser(bool alpha) + : LegacyParser("rgb", Type::RGB, alpha) + {} + bool parse(std::istringstream &input, std::vector &output) const override; + }; + +private: + uint32_t _to_rgba(std::vector const &values, double opacity) const; +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_RGB_H diff --git a/src/colors/spaces/xyz.cpp b/src/colors/spaces/xyz.cpp new file mode 100644 index 0000000000000000000000000000000000000000..19150c8e8ceb88cf760d5a0743be24659a558775 --- /dev/null +++ b/src/colors/spaces/xyz.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + *//* + * Authors: + * 2015 Alexei Boronine (original idea, JavaScript implementation) + * 2015 Roger Tallada (Obj-C implementation) + * 2017 Martin Mitas (C implementation, based on Obj-C implementation) + * 2021 Massinissa Derriche (C++ implementation for Inkscape, based on C implementation) + * 2023 Martin Owens (New Color classes) + * + * Copyright (C) 2023 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "xyz.h" + +#include + +#include "colors/printer.h" + +namespace Inkscape::Colors::Space { + +/** + * Calculate the dot product of the given arrays. + * + * @param t1 The first array. + * @param t2 The second array. + * @return The resulting dot product. + */ +static double dot_product(std::vector const &t1, std::vector const &t2) +{ + return (t1[0] * t2[0] + t1[1] * t2[1] + t1[2] * t2[2]); +} + +/** + * Convert a color from the the XYZ colorspace to the RGB colorspace. + * + * @param in_out[in,out] The XYZ color converted to a RGB color. + */ +void XYZ::toLinearRGB(std::vector &in_out) +{ + std::vector result = in_out; // copy + for (size_t i : {0, 1, 2}) { + result[i] = dot_product(d65[i], in_out); + } + in_out = result; +} + +/** + * Convert from sRGB icc values to XYZ values + * + * @param in_out[in,out] The RGB color converted to a XYZ color. + */ +void XYZ::fromLinearRGB(std::vector &in_out) +{ + std::vector result = in_out; // copy + for (size_t i : {0, 1, 2}) { + result[i] = dot_product(in_out, d65_inv[i]); + } + in_out = result; +} + +/** + * Print the RGB color to a CSS Color module 4 xyz-d65 color. + * + * @arg values - A vector of doubles for each channel in the RGB space + * @arg opacity - True if the opacity should be included in the output. + */ +std::string XYZ::toString(std::vector const &values, bool opacity) const +{ + auto os = CssColorPrinter(3, "xyz"); + os << values; + if (opacity && values.size() == 4) + os << values[3]; + return os; +} + +}; // namespace Inkscape::Colors::Space diff --git a/src/colors/spaces/xyz.h b/src/colors/spaces/xyz.h new file mode 100644 index 0000000000000000000000000000000000000000..3b097b790660382283f6937a1828e6baaf62e4f1 --- /dev/null +++ b/src/colors/spaces/xyz.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLORS_SPACES_XYZ_H +#define SEEN_COLORS_SPACES_XYZ_H + +#include "linear-rgb.h" + +namespace Inkscape::Colors::Space { + +// CIE standard illuminant D65, Observer= 2° [0.9504, 1.0000, 1.0888]. +// Simulates noon daylight with correlated color temperature of 6504 K. +inline const std::vector illuminant_d65 = {0.9504, 1.0000, 1.0888}; + +/* for sRGB, reference white D65 */ +inline const std::vector d65[3] = {{3.24096994190452134377, -1.53738317757009345794, -0.49861076029300328366}, + {-0.96924363628087982613, 1.87596750150772066772, 0.04155505740717561247}, + {0.05563007969699360846, -0.20397695888897656435, 1.05697151424287856072}}; + +inline const std::vector d65_inv[3] = {{0.41239079926595949381, 0.35758433938387799725, 0.18048078840183429261}, + {0.21263900587151036595, 0.71516867876775596569, 0.07219231536073371975}, + {0.019330818715591851469, 0.1191947797946259924, 0.9505321522496605464}}; + +class XYZ : public RGB +{ +public: + XYZ() = default; + ~XYZ() override = default; + + Type getType() const override { return Type::XYZ; } + std::string const getName() const override { return "XYZ"; } + std::string const getIcon() const override { return "color-selector-xyz"; } + +protected: + friend class Inkscape::Colors::Color; + + void spaceToProfile(std::vector &output) const override + { + XYZ::toLinearRGB(output); + LinearRGB::toRGB(output); + } + void profileToSpace(std::vector &output) const override + { + LinearRGB::fromRGB(output); + XYZ::fromLinearRGB(output); + } + + std::string toString(std::vector const &values, bool opacity = true) const override; + +public: + static void toLinearRGB(std::vector &output); + static void fromLinearRGB(std::vector &output); +}; + +} // namespace Inkscape::Colors::Space + +#endif // SEEN_COLORS_SPACES_XYZ_H diff --git a/src/colors/utils.cpp b/src/colors/utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..936ee829e02febe7370d39ed5c27a3414dee35a3 --- /dev/null +++ b/src/colors/utils.cpp @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 AUTHORS + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include +#include +#include +#include +#include + +#include "colors/color.h" +#include "colors/spaces/base.h" +#include "colors/spaces/named.h" +#include "spaces/enum.h" + +namespace Inkscape::Colors { + +/** + * Parse a color directly without any CSS or CMS support. This function is ONLY + * intended to parse values stored in inkscape specific screen-attributes and + * preferences. + * + * DO NOT use this as a common color parser, it does not support any other format + * other than RRGGBBAA and anything else will cause an error. + * + * @arg value - Must be in format #RRGGBBAA only or an empty string. + */ +uint32_t hex_to_rgba(std::string const &value) +{ + if (value.empty()) + return 0x0; + + std::istringstream ss(value); + if (value.size() != 9 || ss.get() != '#') { + throw ColorError("Baddly formatted color, it must be in #RRGGBBAA format"); + } + unsigned int hex; + ss >> std::hex >> hex; + return hex; +} + +/** + * Convert a 32bit unsigned int into a set of 3 or 4 double values for rgba. + * + * @arg rgba - The integer in the format 0xRRGGBBAA + * @arg opacity - Include the opacity channel, if false throws opacity away. + * + * @returns the values of the rgba channels between 0.0 and 1.0 + */ +std::vector rgba_to_values(uint32_t rgba, bool opacity) +{ + std::vector values(3 + opacity); + values[0] = SP_RGBA32_R_F(rgba); + values[1] = SP_RGBA32_G_F(rgba); + values[2] = SP_RGBA32_B_F(rgba); + if (opacity) { + values[3] = SP_RGBA32_A_F(rgba); + } + return values; +} + +/** + * Output the RGBA value as a #RRGGBB hex color, if alpha is true + * then the output will be #RRGGBBAA instead. + */ +std::string rgba_to_hex(uint32_t value, bool alpha) +{ + std::ostringstream oo; + oo.imbue(std::locale("C")); + oo << "#" << std::setfill('0') << std::setw(alpha ? 8 : 6) << std::hex << (alpha ? value : value >> 8); + return oo.str(); +} + +/** + * Create a somewhat unique id for the given color used for palette identification. + */ +std::string color_to_id(std::optional const &color) +{ + if (!color) + return "none"; + + auto name = color->getName(); + if (!name.empty() && name[0] != '#') + return desc_to_id(name); + + std::ostringstream oo; + + // Special case cssname + if (auto cns = std::dynamic_pointer_cast(color->getSpace())) { + auto name = cns->getNameFor(color->toRGBA()); + if (!name.empty()) { + oo << "css-" << color->toString(); + return oo.str(); + } + } + + oo << color->getSpace()->getName() << "-" << std::hex << std::setfill('0'); + for (double const &value : color->getValues()) { + unsigned int diget = value * 0xff; + oo << std::setw(2) << diget; + } + + auto ret = oo.str(); + std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower); + return ret; +} + +/** + * Transform a color name or description into an id used for palette identification. + */ +std::string desc_to_id(std::string const &desc) +{ + auto name = Glib::ustring(desc); + // Convert description to ascii, strip out symbols, remove duplicate dashes and prefixes + static auto const reg1 = Glib::Regex::create("[^[:alnum:]]"); + name = reg1->replace(name, 0, "-", static_cast(0)); + static auto const reg2 = Glib::Regex::create("-{2,}"); + name = reg2->replace(name, 0, "-", static_cast(0)); + static auto const reg3 = Glib::Regex::create("(^-|-$)"); + name = reg3->replace(name, 0, "", static_cast(0)); + // Move important numbers from the start where they are invalid xml, to the end. + static auto const reg4 = Glib::Regex::create("^(\\d+)(-?)([^\\d]*)"); + name = reg4->replace(name, 0, "\\3\\2\\1", static_cast(0)); + return name.lowercase(); +} + +/** + * Make a darker or lighter version of the color, useful for making checkerboards. + */ +Color make_contrasted_color(Color const &orig, double amount) +{ + if (auto color = orig.converted(Space::Type::HSL)) { + auto lightness = (*color)[2]; + color->set(2, lightness + ((lightness < 0.08 ? 0.08 : -0.08) * amount)); + color->convert(orig.getSpace()); + return *color; + } + return orig; +} + +/** + * Make a themed dark or light color based on a previous shade, returns RGB color. + */ +Color make_theme_color(Color const &orig, bool dark) +{ + // color of the image strip to HSL, so we can manipulate its lightness + auto color = *orig.converted(Colors::Space::Type::HSLUV); + + if (dark) { + // limit saturation to improve contrast with some artwork + color.set(1, std::min(color[1], 0.008)); + // make a darker shade and limit to remove extremes + color.set(2, std::min(color[2] * 0.7, 0.003)); + } else { + // make a lighter shade and limit to remove extemes + color.set(2, std::max(color[2] + (0.01 - color[2]) * 0.5, 0.008)); + } + + return *color.converted(Colors::Space::Type::RGB); +} + +double perceptual_lightness(double l) +{ + return l <= 0.885645168 ? l * 0.09032962963 : std::cbrt(l) * 0.249914424 - 0.16; +} + +/** + * Return a value for how the light the color appears to be using HSLUV + */ +double get_perceptual_lightness(Color const &color) +{ + return perceptual_lightness((*color.converted(Space::Type::HSLUV))[2] * 100); +} + +std::pair get_contrasting_color(double l) +{ + double constexpr l_threshold = 0.85; + if (l > l_threshold) { // Draw dark over light. + auto t = (l - l_threshold) / (1.0 - l_threshold); + return {0.0, 0.4 - 0.1 * t}; + } else { // Draw light over dark. + auto t = (l_threshold - l) / l_threshold; + return {1.0, 0.6 + 0.1 * t}; + } +} + +}; // namespace Inkscape::Colors + +/* + 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 : diff --git a/src/colors/utils.h b/src/colors/utils.h new file mode 100644 index 0000000000000000000000000000000000000000..712dc48891f048d202788a984ce6c41a5144e280 --- /dev/null +++ b/src/colors/utils.h @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 AUTHORS + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_COLORS_UTILS_H +#define SEEN_COLORS_UTILS_H + +#include +#include +#include +#include + +/* Useful composition macros */ + +constexpr double SP_COLOR_U_TO_F(uint32_t v) +{ + return v / 255.0; +} +constexpr uint32_t SP_COLOR_F_TO_U(double v) +{ + return (unsigned int)(v * 255. + .5); +} +constexpr uint32_t SP_RGBA32_R_U(uint32_t v) +{ + return (v >> 24) & 0xff; +} +constexpr uint32_t SP_RGBA32_G_U(uint32_t v) +{ + return (v >> 16) & 0xff; +} +constexpr uint32_t SP_RGBA32_B_U(uint32_t v) +{ + return (v >> 8) & 0xff; +} +constexpr uint32_t SP_RGBA32_A_U(uint32_t v) +{ + return v & 0xff; +} +constexpr double SP_RGBA32_R_F(uint32_t v) +{ + return SP_COLOR_U_TO_F(SP_RGBA32_R_U(v)); +} +constexpr double SP_RGBA32_G_F(uint32_t v) +{ + return SP_COLOR_U_TO_F(SP_RGBA32_G_U(v)); +} +constexpr double SP_RGBA32_B_F(uint32_t v) +{ + return SP_COLOR_U_TO_F(SP_RGBA32_B_U(v)); +} +constexpr double SP_RGBA32_A_F(uint32_t v) +{ + return SP_COLOR_U_TO_F(SP_RGBA32_A_U(v)); +} + +constexpr uint32_t SP_RGBA32_U_COMPOSE(uint32_t r, uint32_t g, uint32_t b, uint32_t a) +{ + return ((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | (a & 0xff); +} +constexpr uint32_t SP_RGBA32_F_COMPOSE(double r, double g, double b, double a) +{ + return SP_RGBA32_U_COMPOSE(SP_COLOR_F_TO_U(r), SP_COLOR_F_TO_U(g), SP_COLOR_F_TO_U(b), SP_COLOR_F_TO_U(a)); +} +constexpr uint32_t SP_RGBA32_C_COMPOSE(uint32_t c, double o) +{ + return SP_RGBA32_U_COMPOSE(SP_RGBA32_R_U(c), SP_RGBA32_G_U(c), SP_RGBA32_B_U(c), SP_COLOR_F_TO_U(o)); +} + +/** + * A set of useful color modifying functions which do not fit as generic + * methods on the color class itself but which are used in various places. + */ +namespace Inkscape::Colors { + +class Color; + +uint32_t hex_to_rgba(std::string const &value); +std::vector rgba_to_values(uint32_t rgba, bool opacity); +std::string rgba_to_hex(uint32_t value, bool alpha = false); +std::string color_to_id(std::optional const &color); +std::string desc_to_id(std::string const &desc); + +Color make_contrasted_color(Color const &orig, double amount); +Color make_theme_color(Color const &orig, bool dark); + +double lightness(Color color); +double perceptual_lightness(double l); +double get_perceptual_lightness(Color const &color); +std::pair get_contrasting_color(double l); + +} // namespace Inkscape::Colors + +#endif // SEEN_COLORS_UTILS_H diff --git a/src/colors/xml-color.cpp b/src/colors/xml-color.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c900f02ced35d5effd60f066bd4c327399daa3be --- /dev/null +++ b/src/colors/xml-color.cpp @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Martin Owens + * + * Copyright (C) 2023 author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "xml-color.h" + +#include "cms/profile.h" +#include "cms/system.h" +#include "colors/color.h" +#include "document-cms.h" +#include "document.h" +#include "manager.h" +#include "spaces/base.h" +#include "spaces/cms.h" +#include "spaces/components.h" +#include "xml/node-iterators.h" +#include "xml/node.h" +#include "xml/repr.h" +#include "xml/simple-document.h" + +namespace Inkscape::Colors { + +/** + * Turn a color into a color xml document, used for drag and drop. + * + * @arg paint - The color or none to convert into xml + */ +std::string paint_to_xml_string(Paint const &paint) +{ + auto doc = paint_to_xml(paint); + auto ret = sp_repr_save_buf(doc); + GC::release(doc); + return ret; +} + +/** + * Parse an xml document into a color. Usually from a drag and drop. + * + * @arg xmls - A string of a color xml document + * @arg doc - An optional document to match icc profiles + */ +Paint xml_string_to_paint(std::string const &xmls, SPDocument *doc) +{ + auto color_doc = sp_repr_read_buf(xmls, nullptr); + auto ret = xml_to_paint(color_doc, doc); + GC::release(color_doc); + return ret; +} + +XML::Document *paint_to_xml(Paint const &paint) +{ + auto *document = new XML::SimpleDocument(); + auto root = document->createElement("paint"); + document->appendChild(root); + + if (std::holds_alternative(paint)) { + auto node = document->createElement("nocolor"); + root->appendChild(node); + GC::release(node); + GC::release(root); + return document; + } + + auto &color = std::get(paint); + auto space = color.getSpace(); + + // This format is entirely inkscape's creation and doesn't work with anything + // outside of inkscape. It's completely safe to change at any time since the + // data is never saved to a file. + auto node = document->createElement("color"); + node->setAttribute("space", space->getName()); + node->setAttributeOrRemoveIfEmpty("name", color.getName()); + root->appendChild(node); + + if (auto cms = std::dynamic_pointer_cast(space)) { + if (auto profile = cms->getProfile()) { + // Store the unique icc profile id, so we have a chance of matching it + node->setAttribute("icc", profile->getId()); + } + } + + if (color.hasOpacity()) { + node->setAttributeSvgDouble("opacity", color.getOpacity()); + } + + auto components = space->getComponents(); + for (unsigned int i = 0; i < components.size() && i < color.size(); i++) { + node->setAttributeCssDouble(components[i].id, color[i]); + } + + GC::release(node); + GC::release(root); + return document; +} + +Paint xml_to_paint(XML::Document const *xml, SPDocument *doc) +{ + auto get_node = [](XML::Node const *node, std::string const &name) { + XML::NodeConstSiblingIterator iter{node->firstChild()}; + for (; iter; ++iter) { + if (iter->name() && name == iter->name()) { + return &*iter; + } + } + return (const Inkscape::XML::Node *)(nullptr); + }; + + if (auto const paint = get_node(xml, "paint")) { + if (get_node(paint, "nocolor")) { + return NoColor(); + } + if (auto color_xml = get_node(paint, "color")) { + auto space_name = color_xml->attribute("space"); + + if (!space_name) { + throw ColorError("Invalid color data, no space specified."); + } + + auto space = Manager::get().find(space_name); + + if (!space && doc) + space = doc->getDocumentCMS().getSpace(space_name); + + if (auto icc_id = color_xml->attribute("icc")) { + // Make a temporary space for the icc information, if possible + if (!space) + if (auto profile = CMS::System::get().getProfile(icc_id)) { + auto cms = std::make_shared(profile); + cms->setIntent(RenderingIntent::AUTO); + space = cms; + } + + if (auto cms = std::dynamic_pointer_cast(space)) { + // Check named space has a cms profile that is actually the same Id + if (cms->getProfile()->getId() != icc_id) { + g_warning("Mismatched icc profiles in color data: '%s'", space_name); + // Not returning, will still return something + } + } + } + if (!space) { + throw ColorError("No color space available."); + } + + XML::NodeConstSiblingIterator color_iter{color_xml->firstChild()}; + std::vector values; + for (auto &comp : space->getComponents()) { + values.emplace_back(color_xml->getAttributeDouble(comp.id)); + } + auto color = Color(space, values); + + if (color_xml->attribute("opacity")) { + color.setOpacity(color_xml->getAttributeDouble("opacity")); + } + if (auto name = color_xml->attribute("name")) { + color.setName(name); + } + return color; + } + } + throw ColorError("No color data found"); +} + +} // namespace Inkscape::Colors + +/* + 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 : diff --git a/src/colors/xml-color.h b/src/colors/xml-color.h new file mode 100644 index 0000000000000000000000000000000000000000..a3dbb346ae23d638f79da7f6656f82c61b05a5db --- /dev/null +++ b/src/colors/xml-color.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Martin Owens + * + * Copyright (C) 2023 author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_COLORS_XML_COLOR +#define INKSCAPE_COLORS_XML_COLOR + +#include +#include +#include + +class SPDocument; + +namespace Inkscape::XML { +struct Document; +} + +namespace Inkscape::Colors { + +class Color; +struct NoColor +{ +}; + +// NoColor must go first for default constructor +using Paint = std::variant; + +XML::Document *paint_to_xml(Paint const &paint); +std::string paint_to_xml_string(Paint const &paint); + +Paint xml_string_to_paint(std::string const &xmls, SPDocument *doc); +Paint xml_to_paint(XML::Document const *xml, SPDocument *doc); + +} // namespace Inkscape::Colors + +#endif // INKSCAPE_COLORS_XML_COLOR + +/* + 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 : diff --git a/src/colorspace.h b/src/colorspace.h deleted file mode 100644 index 1903fe026920db5c9d4b9929106e81c72f9965c6..0000000000000000000000000000000000000000 --- a/src/colorspace.h +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#ifndef SEEN_COLOR_SPACE_H -#define SEEN_COLOR_SPACE_H - -/* - * Authors: - * Jon A. Cruz - * - * Copyright (C) 2013 Authors - * - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" // only include where actually required! -#endif - -# include - -#include -#include - - -namespace Inkscape -{ - -class ColorProfile; - -} // namespace Inkscape - -namespace colorspace -{ - -class Component -{ -public: - Component(); - Component(std::string name, std::string tip, guint scale); - - std::string name; - std::string tip; - guint scale; -}; - -std::vector getColorSpaceInfo( uint32_t space ); - -std::vector getColorSpaceInfo( Inkscape::ColorProfile *prof ); - -} // namespace colorspace - -#endif // SEEN_COLOR_SPACE_H diff --git a/src/desktop-style.cpp b/src/desktop-style.cpp index 1dbfb863cbda9b168253c21f3c823a790bdae61b..74008e627f889fef75e26a5a4ef0c1fc126070cf 100644 --- a/src/desktop-style.cpp +++ b/src/desktop-style.cpp @@ -20,7 +20,8 @@ #include "desktop-style.h" -#include "color-rgba.h" +#include "colors/color.h" +#include "colors/color-set.h" #include "desktop.h" #include "filter-chemistry.h" #include "inkscape.h" @@ -47,7 +48,6 @@ #include "style.h" #include "svg/css-ostringstream.h" -#include "svg/svg-color.h" #include "svg/svg.h" #include "ui/tools/tool-base.h" @@ -72,7 +72,7 @@ static bool isTextualItem(SPObject const *obj) * Set color on selection on desktop. */ void -sp_desktop_set_color(SPDesktop *desktop, ColorRGBA const &color, bool is_relative, bool fill) +sp_desktop_set_color(SPDesktop *desktop, Color const &color, bool is_relative, bool fill) { /// \todo relative color setting if (is_relative) { @@ -80,22 +80,9 @@ sp_desktop_set_color(SPDesktop *desktop, ColorRGBA const &color, bool is_relativ return; } - guint32 rgba = SP_RGBA32_F_COMPOSE(color[0], color[1], color[2], color[3]); - gchar b[64]; - sp_svg_write_color(b, sizeof(b), rgba); SPCSSAttr *css = sp_repr_css_attr_new(); - if (fill) { - sp_repr_css_set_property(css, "fill", b); - Inkscape::CSSOStringStream osalpha; - osalpha << color[3]; - sp_repr_css_set_property(css, "fill-opacity", osalpha.str().c_str()); - } else { - sp_repr_css_set_property(css, "stroke", b); - Inkscape::CSSOStringStream osalpha; - osalpha << color[3]; - sp_repr_css_set_property(css, "stroke-opacity", osalpha.str().c_str()); - } - + sp_repr_css_set_property_string(css, fill ? "fill" : "stroke", color.toString(false)); + sp_repr_css_set_property_double(css, fill ? "fill-opacity" : "stroke-opacity", color.getOpacity()); sp_desktop_set_style(desktop, css); sp_repr_css_attr_unref(css); @@ -286,22 +273,15 @@ sp_desktop_get_style(SPDesktop *desktop, bool with_text) /** * Return the desktop's current color. */ -guint32 +std::optional sp_desktop_get_color(SPDesktop *desktop, bool is_fill) { - guint32 r = 0; // if there's no color, return black gchar const *property = sp_repr_css_property(desktop->current, is_fill ? "fill" : "stroke", "#000"); - if (desktop->current && property) { // if there is style and the property in it, - if (strncmp(property, "url", 3)) { // and if it's not url, - // read it - r = sp_svg_read_color(property, r); - } - } - - return r; + // if there is style and the property in it, + return Color::parse(desktop->current ? property : nullptr); } double @@ -362,14 +342,11 @@ sp_desktop_get_opacity_tool(SPDesktop *desktop, Glib::ustring const &tool, bool return value; } -guint32 -sp_desktop_get_color_tool(SPDesktop *desktop, Glib::ustring const &tool, bool is_fill, bool *has_color) +std::optional +sp_desktop_get_color_tool(SPDesktop *desktop, Glib::ustring const &tool, bool is_fill) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); SPCSSAttr *css = nullptr; - guint32 r = 0; // if there's no color, return black - if (has_color) - *has_color = false; bool styleFromCurrent = prefs->getBool(tool + "/usecurrent"); if (styleFromCurrent) { css = sp_desktop_get_style(desktop, true); @@ -378,22 +355,14 @@ sp_desktop_get_color_tool(SPDesktop *desktop, Glib::ustring const &tool, bool is Inkscape::GC::anchor(css); } + std::optional ret; if (css) { gchar const *property = sp_repr_css_property(css, is_fill ? "fill" : "stroke", "#000"); - - if (desktop->current && property) { // if there is style and the property in it, - if (strncmp(property, "url", 3) && strncmp(property, "none", 4)) { // and if it's not url or none, - // read it - r = sp_svg_read_color(property, r); - if (has_color) - *has_color = true; - } - } - + // if there is style and the property in it, + ret = Color::parse(desktop->current ? property : nullptr); sp_repr_css_attr_unref(css); } - - return r | 0xff; + return ret; } /** @@ -496,19 +465,10 @@ objects_query_fillstroke (const std::vector &objects, SPStyle *style_re } SPIPaint *paint_res = style_res->getFillOrStroke(isfill); - bool paintImpossible = true; paint_res->set = true; - std::optional iccColor; - - bool iccSeen = false; - gfloat c[4]; - c[0] = c[1] = c[2] = c[3] = 0.0; - gint num = 0; - - gfloat prev[3]; - prev[0] = prev[1] = prev[2] = 0.0; - bool same_color = true; + bool paintImpossible = true; + Colors::ColorSet colors; for (auto obj : objects) { if (!obj) { @@ -598,44 +558,18 @@ objects_query_fillstroke (const std::vector &objects, SPStyle *style_re } // 2. Sum color, copy server from paint to paint_res + if (paint_res->set && paint->isColor()) { + auto copy = paint->getColor(); + copy.addOpacity(isfill ? style->fill_opacity : style->stroke_opacity); - if (paint_res->set && paint_effectively_set && paint->isColor()) { - gfloat d[3]; - paint->value.color.get_rgb_floatv(d); - - // Check if this color is the same as previous - if (paintImpossible) { - prev[0] = d[0]; - prev[1] = d[1]; - prev[2] = d[2]; - paint_res->setColor(d[0], d[1], d[2]); - if (paint->value.color.hasColors()) { - iccColor.emplace(paint->value.color); - iccSeen = true; - } - } else { - if (same_color && (prev[0] != d[0] || prev[1] != d[1] || prev[2] != d[2])) { - same_color = false; - iccColor.reset(); - } - if (iccSeen && iccColor && *iccColor != paint->value.color) { - // We can't yet blend together two icc based colors. - same_color = false; - iccColor.reset(); - } + if (colors.isEmpty()) { + paint_res->setColor(copy); } - - // average color - c[0] += d[0]; - c[1] += d[1]; - c[2] += d[2]; - c[3] += SP_SCALE24_TO_FLOAT (isfill? style->fill_opacity.value : style->stroke_opacity.value); - - num ++; + // Remove colors from this list somehow? + colors.set(obj->getId(), copy); } paintImpossible = false; - paint_res->colorSet = paint->colorSet; paint_res->paintOrigin = paint->paintOrigin; if (paint_res->set && paint_effectively_set && paint->isPaintserver()) { // copy the server if (isfill) { @@ -648,27 +582,17 @@ objects_query_fillstroke (const std::vector &objects, SPStyle *style_re style_res->fill_rule.computed = style->fill_rule.computed; // no averaging on this, just use the last one } - // After all objects processed, divide the color if necessary and return - if (paint_res->set && paint_res->isColor()) { // set the color - g_assert (num >= 1); - - c[0] /= num; - c[1] /= num; - c[2] /= num; - c[3] /= num; - paint_res->setColor(c[0], c[1], c[2]); + if (paint_res->set && paint_res->isColor() && !colors.isEmpty()) { + auto color = colors.getAverage(); if (isfill) { - style_res->fill_opacity.value = SP_SCALE24_FROM_FLOAT (c[3]); + style_res->fill_opacity.set_double(color.stealOpacity()); } else { - style_res->stroke_opacity.value = SP_SCALE24_FROM_FLOAT (c[3]); - } - - if (iccSeen && iccColor) { - paint_res->value.color.copyColors(*iccColor); + style_res->stroke_opacity.set_double(color.stealOpacity()); } + paint_res->setColor(color); - if (num > 1) { - if (same_color) + if (colors.size() > 1) { + if (colors.isSame()) return QUERY_STYLE_MULTIPLE_SAME; else return QUERY_STYLE_MULTIPLE_AVERAGED; diff --git a/src/desktop-style.h b/src/desktop-style.h index a9d357942d3440297cc5ee4806d2a845bd87cc80..fa1f4dff159adf7fc6c3986183e6eb10ebacdab3 100644 --- a/src/desktop-style.h +++ b/src/desktop-style.h @@ -14,11 +14,12 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ +#include #include +#include #include -class ColorRGBA; class SPCSSAttr; class SPDesktop; class SPObject; @@ -29,9 +30,15 @@ class ObjectSet; namespace XML { class Node; } +namespace Colors { +class Color; +class ColorSet; +} } namespace Glib { class ustring; } +using Inkscape::Colors::Color; + enum { // what kind of a style the query is returning QUERY_STYLE_NOTHING, // nothing was queried - e.g. no selection QUERY_STYLE_SINGLE, // single object was queried @@ -65,14 +72,14 @@ enum { // which property was queried (add when you need more) }; void sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines); -void sp_desktop_set_color(SPDesktop *desktop, ColorRGBA const &color, bool is_relative, bool fill); +void sp_desktop_set_color(SPDesktop *desktop, Color const &color, bool is_relative, bool fill); void sp_desktop_set_style(Inkscape::ObjectSet *set, SPDesktop *desktop, SPCSSAttr *css, bool change = true, bool write_current = true, bool switch_style = false); void sp_desktop_set_style(SPDesktop *desktop, SPCSSAttr *css, bool change = true, bool write_current = true, bool switch_style = false); SPCSSAttr *sp_desktop_get_style(SPDesktop *desktop, bool with_text); -guint32 sp_desktop_get_color (SPDesktop *desktop, bool is_fill); double sp_desktop_get_master_opacity_tool(SPDesktop *desktop, Glib::ustring const &tool, bool* has_opacity = nullptr); double sp_desktop_get_opacity_tool(SPDesktop *desktop, Glib::ustring const &tool, bool is_fill); -guint32 sp_desktop_get_color_tool(SPDesktop *desktop, Glib::ustring const &tool, bool is_fill, bool* has_color = nullptr); +std::optional sp_desktop_get_color (SPDesktop *desktop, bool is_fill); +std::optional sp_desktop_get_color_tool(SPDesktop *desktop, Glib::ustring const &tool, bool is_fill); double sp_desktop_get_font_size_tool (SPDesktop *desktop); void sp_desktop_apply_style_tool(SPDesktop *desktop, Inkscape::XML::Node *repr, Glib::ustring const &tool, bool with_text); diff --git a/src/desktop.cpp b/src/desktop.cpp index a51218a275cdd70bf776b5826c67f1378bb54feb..e05e111f52992d387aae75f85853f10b4da4f11d 100644 --- a/src/desktop.cpp +++ b/src/desktop.cpp @@ -30,7 +30,6 @@ #include #include "desktop.h" -#include "color.h" #include "desktop-events.h" #include "desktop-style.h" #include "document-undo.h" diff --git a/src/display/cairo-utils.cpp b/src/display/cairo-utils.cpp index 807fb80fc9d4aa3f0f04580f373204f4731aba33..89c26ee60660c9e7fd6af8bfd7496763a3530dcc 100644 --- a/src/display/cairo-utils.cpp +++ b/src/display/cairo-utils.cpp @@ -29,7 +29,8 @@ #include #include "cairo-templates.h" -#include "color.h" +#include "colors/manager.h" +#include "colors/utils.h" #include "document.h" #include "helper/pixbuf-ops.h" #include "preferences.h" @@ -1015,13 +1016,39 @@ ink_cairo_set_source_rgba32(cairo_t *ct, guint32 rgba) { cairo_set_source_rgba(ct, SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba), SP_RGBA32_A_F(rgba)); } +void ink_cairo_set_source_rgba32(Cairo::RefPtr ctx, guint32 rgba) +{ + ctx->set_source_rgba(SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba), SP_RGBA32_A_F(rgba)); +} +void ink_cairo_set_source_rgba32(Cairo::Context &ctx, guint32 rgba) +{ + ctx.set_source_rgba(SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba), SP_RGBA32_A_F(rgba)); +} -void -ink_cairo_set_source_color(cairo_t *ct, SPColor const &c, double opacity) +/** + * The following functions interact between Inkscape color model, and cairo surface rendering. + * for compatability we only convert to RGB and use that directly. + */ +void ink_cairo_set_source_color(Cairo::RefPtr ctx, Inkscape::Colors::Color const &color, double opacity) +{ + ink_cairo_set_source_rgba32(ctx, color.toRGBA(opacity)); +} +void ink_cairo_set_source_color(cairo_t *ctx, Inkscape::Colors::Color const &color, double opacity) +{ + ink_cairo_set_source_rgba32(ctx, color.toRGBA(opacity)); +} +void ink_cairo_pattern_add_color_stop(cairo_pattern_t *ptn, double offset, Inkscape::Colors::Color const &color, double opacity) +{ + auto c = color.toRGBA(opacity); + cairo_pattern_add_color_stop_rgba(ptn, offset, SP_RGBA32_R_F(c), SP_RGBA32_G_F(c), SP_RGBA32_B_F(c), SP_RGBA32_A_F(c)); +} +cairo_pattern_t *ink_cairo_pattern_create(Inkscape::Colors::Color const &color, double opacity) { - cairo_set_source_rgba(ct, c.v.c[0], c.v.c[1], c.v.c[2], opacity); + auto c = color.toRGBA(opacity); + return cairo_pattern_create_rgba(SP_RGBA32_R_F(c), SP_RGBA32_G_F(c), SP_RGBA32_B_F(c), SP_RGBA32_A_F(c)); } + void ink_matrix_to_2geom(Geom::Affine &m, cairo_matrix_t const &cm) { m[0] = cm.xx; @@ -1270,17 +1297,6 @@ static int ink_cairo_surface_average_color_internal(cairo_surface_t *surface, do return width * height; } -guint32 ink_cairo_surface_average_color(cairo_surface_t *surface) -{ - double rf,gf,bf,af; - ink_cairo_surface_average_color_premul(surface, rf,gf,bf,af); - guint32 r = round(rf * 255); - guint32 g = round(gf * 255); - guint32 b = round(bf * 255); - guint32 a = round(af * 255); - ASSEMBLE_ARGB32(px, a,r,g,b); - return px; -} // We extract colors from pattern background, if we need to extract sometimes from a gradient we can add // a extra parameter with the spot number and use cairo_pattern_get_color_stop_rgba // also if the pattern is a image we can pass a boolean like solid = false to get the color by image average ink_cairo_surface_average_color @@ -1306,34 +1322,22 @@ guint32 ink_cairo_pattern_get_argb32(cairo_pattern_t *pattern) return 0; } -void ink_cairo_surface_average_color(cairo_surface_t *surface, double &r, double &g, double &b, double &a) +Colors::Color ink_cairo_surface_average_color(cairo_surface_t *surface) { - int count = ink_cairo_surface_average_color_internal(surface, r,g,b,a); - - r /= a; - g /= a; - b /= a; - a /= count; - - r = CLAMP(r, 0.0, 1.0); - g = CLAMP(g, 0.0, 1.0); - b = CLAMP(b, 0.0, 1.0); - a = CLAMP(a, 0.0, 1.0); + double r, g, b, a = 0.0; + int count = ink_cairo_surface_average_color_internal(surface, r, g, b, a); + auto color = Colors::Color(Colors::Space::Type::RGB, {r / a, g / a, b / a, a / count}); + color.normalize(); + return color; } -void ink_cairo_surface_average_color_premul(cairo_surface_t *surface, double &r, double &g, double &b, double &a) +Colors::Color ink_cairo_surface_average_color_premul(cairo_surface_t *surface) { - int count = ink_cairo_surface_average_color_internal(surface, r,g,b,a); - - r /= count; - g /= count; - b /= count; - a /= count; - - r = CLAMP(r, 0.0, 1.0); - g = CLAMP(g, 0.0, 1.0); - b = CLAMP(b, 0.0, 1.0); - a = CLAMP(a, 0.0, 1.0); + double r, g, b, a = 0.0; + int count = ink_cairo_surface_average_color_internal(surface, r, g, b, a); + auto color = Colors::Color(Colors::Space::Type::RGB, {r / count, g / count, b / count, a / count}); + color.normalize(); + return color; } static guint32 srgb_to_linear( const guint32 c, const guint32 a ) { @@ -1513,37 +1517,21 @@ ink_cairo_pattern_create_checkerboard(guint32 rgba, bool use_alpha) int const w = 6; int const h = 6; - double r = SP_RGBA32_R_F(rgba); - double g = SP_RGBA32_G_F(rgba); - double b = SP_RGBA32_B_F(rgba); - - float hsl[3]; - SPColor::rgb_to_hsl_floatv(hsl, r, g, b); - hsl[2] += hsl[2] < 0.08 ? 0.08 : -0.08; // 0.08 = 0.77-0.69, the original checkerboard colors. - - float rgb2[3]; - SPColor::hsl_to_rgb_floatv(rgb2, hsl[0], hsl[1], hsl[2]); + auto color_a = Colors::Color(rgba, use_alpha); + auto color_b = Inkscape::Colors::make_contrasted_color(color_a, 1.0); + // Once the second color is generated, the original doesn't need alpha + color_a.enableOpacity(false); cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 2*w, 2*h); cairo_t *ct = cairo_create(s); cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE); - cairo_set_source_rgb(ct, r, g, b); + ink_cairo_set_source_color(ct, color_a); cairo_paint(ct); - cairo_set_source_rgb(ct, rgb2[0], rgb2[1], rgb2[2]); + ink_cairo_set_source_color(ct, color_b); cairo_rectangle(ct, 0, 0, w, h); cairo_rectangle(ct, w, h, w, h); cairo_fill(ct); - if (use_alpha) { - // use alpha to show opacity cover checkerboard - double a = SP_RGBA32_A_F(rgba); - if (a > 0.0) { - cairo_set_operator(ct, CAIRO_OPERATOR_OVER); - cairo_rectangle(ct, 0, 0, 2 * w, 2 * h); - cairo_set_source_rgba(ct, r, g, b, a); - cairo_fill(ct); - } - } cairo_destroy(ct); cairo_pattern_t *p = cairo_pattern_create_for_surface(s); diff --git a/src/display/cairo-utils.h b/src/display/cairo-utils.h index 2a3eb4dffed63aa6e4f721580c1bb03ea51cf300..c44a76674c7cd980d40078886c7e5631b52ea610 100644 --- a/src/display/cairo-utils.h +++ b/src/display/cairo-utils.h @@ -17,12 +17,14 @@ #include #include "style.h" -struct SPColor; typedef struct _GdkPixbuf GdkPixbuf; void ink_cairo_pixbuf_cleanup(unsigned char *, void *); namespace Inkscape { +namespace Colors { +class Color; +} /** Class to hold image data for raster images. * Allows easy interoperation with GdkPixbuf and Cairo. */ @@ -98,8 +100,13 @@ void set_cairo_surface_ci(cairo_surface_t *surface, SPColorInterpolation cif); void copy_cairo_surface_ci(cairo_surface_t *in, cairo_surface_t *out); void convert_cairo_surface_ci(cairo_surface_t *surface, SPColorInterpolation cif); -void ink_cairo_set_source_color(cairo_t *ct, SPColor const &color, double opacity); -void ink_cairo_set_source_rgba32(cairo_t *ct, guint32 rgba); +cairo_pattern_t *ink_cairo_pattern_create(Inkscape::Colors::Color const &color, double opacity = 1.0); +void ink_cairo_pattern_add_color_stop(cairo_pattern_t *ptn, double offset, Inkscape::Colors::Color const &color, double opacity = 1.0); +void ink_cairo_set_source_color(Cairo::RefPtr ctx, Inkscape::Colors::Color const &color, double opacity = 1.0); +void ink_cairo_set_source_color(cairo_t *ctx, Inkscape::Colors::Color const &c, double opacity = 1.0); +void ink_cairo_set_source_rgba32(Cairo::RefPtr ctx, guint32 rgba); +void ink_cairo_set_source_rgba32(Cairo::Context &ctx, guint32 rgba); +void ink_cairo_set_source_rgba32(cairo_t *ctx, guint32 rgba); void ink_cairo_transform(cairo_t *ct, Geom::Affine const &m); void ink_cairo_pattern_set_matrix(cairo_pattern_t *cp, Geom::Affine const &m); void ink_cairo_set_hairline(cairo_t *ct); @@ -119,10 +126,9 @@ cairo_surface_t *ink_cairo_surface_create_output(cairo_surface_t *image, cairo_s void ink_cairo_surface_blit(cairo_surface_t *src, cairo_surface_t *dest); int ink_cairo_surface_get_width(cairo_surface_t *surface); int ink_cairo_surface_get_height(cairo_surface_t *surface); -guint32 ink_cairo_surface_average_color(cairo_surface_t *surface); guint32 ink_cairo_pattern_get_argb32(cairo_pattern_t *pattern); -void ink_cairo_surface_average_color(cairo_surface_t *surface, double &r, double &g, double &b, double &a); -void ink_cairo_surface_average_color_premul(cairo_surface_t *surface, double &r, double &g, double &b, double &a); +Colors::Color ink_cairo_surface_average_color(cairo_surface_t *surface); +Colors::Color ink_cairo_surface_average_color_premul(cairo_surface_t *surface); double srgb_to_linear( const double c ); int ink_cairo_surface_srgb_to_linear(cairo_surface_t *surface); diff --git a/src/display/control/canvas-item-bpath.cpp b/src/display/control/canvas-item-bpath.cpp index 7c7ddf99fdf5bc5d9e9b795174163875db48e0ad..757e8e2d8199cc52373870b1d5411565ca0c868d 100644 --- a/src/display/control/canvas-item-bpath.cpp +++ b/src/display/control/canvas-item-bpath.cpp @@ -16,7 +16,6 @@ #include "canvas-item-bpath.h" -#include "color.h" // SP_RGBA_x_F #include "display/curve.h" #include "display/cairo-utils.h" #include "helper/geom.h" // bounds_exact_transformed() diff --git a/src/display/control/canvas-item-curve.cpp b/src/display/control/canvas-item-curve.cpp index 7369fdb775b74456539e265891bc26dd86d2339f..9ee52425ad4ebeab79a3fcbb221932b34f81a5af 100644 --- a/src/display/control/canvas-item-curve.cpp +++ b/src/display/control/canvas-item-curve.cpp @@ -18,8 +18,7 @@ #include "canvas-item-curve.h" -#include "color.h" // SP_RGBA_x_F - +#include "display/cairo-utils.h" #include "helper/geom.h" #include "ui/widget/canvas.h" @@ -178,7 +177,7 @@ void CanvasItemCurve::_render(Inkscape::CanvasItemBuffer &buf) const buf.cr->set_line_width(background_width); buf.cr->stroke_preserve(); - buf.cr->set_source_rgba(SP_RGBA32_R_F(_stroke), SP_RGBA32_G_F(_stroke), SP_RGBA32_B_F(_stroke), SP_RGBA32_A_F(_stroke)); + ink_cairo_set_source_rgba32(buf.cr, _stroke); buf.cr->set_line_width(_width); buf.cr->stroke(); diff --git a/src/display/control/canvas-item-grid.cpp b/src/display/control/canvas-item-grid.cpp index 9b9696d675e2b7a6d49d6ba6c09ae4041822eea2..c36920b51bd66478abcc69bb3e84d61e2c4fc8e2 100644 --- a/src/display/control/canvas-item-grid.cpp +++ b/src/display/control/canvas-item-grid.cpp @@ -14,8 +14,8 @@ #include #include +#include "colors/color.h" #include "canvas-item-grid.h" -#include "color.h" #include "helper/geom.h" enum Dim3 { X, Y, Z }; @@ -45,8 +45,8 @@ CanvasItemGrid::CanvasItemGrid(CanvasItemGroup *group) : CanvasItem(group) , _origin(0, 0) , _spacing(1, 1) - , _minor_color(GRID_DEFAULT_MINOR_COLOR) - , _major_color(GRID_DEFAULT_MAJOR_COLOR) + , _minor_color(0x0) + , _major_color(0x0) , _major_line_interval(5) , _dotted(false) { diff --git a/src/display/control/canvas-item-grid.h b/src/display/control/canvas-item-grid.h index 9acacca0cfd4c1fcc1ca32e1d612633d5908020e..7670e33b750856fe8ad9fd5d9bf95ed290f34225 100644 --- a/src/display/control/canvas-item-grid.h +++ b/src/display/control/canvas-item-grid.h @@ -18,12 +18,6 @@ #include "canvas-item.h" #include "preferences.h" -// default colors in RGBA -uint32_t constexpr GRID_DEFAULT_MAJOR_COLOR = 0x0099e5'4d; -uint32_t constexpr GRID_DEFAULT_MINOR_COLOR = 0x0099e5'26; -uint32_t constexpr GRID_DEFAULT_BLOCK_COLOR = 0x0047cb'4d; -uint32_t constexpr GRID_DEFAULT_MINOR_BLOCK_COLOR = 0x0047cb'26; - namespace Inkscape { class CanvasItemGrid : public CanvasItem diff --git a/src/display/control/canvas-item-quad.cpp b/src/display/control/canvas-item-quad.cpp index 3b4946cfb645e469c8e022d1717abdea75927bd8..b6427a6dd59178e5fe91e5274f6e276e28c48694 100644 --- a/src/display/control/canvas-item-quad.cpp +++ b/src/display/control/canvas-item-quad.cpp @@ -18,7 +18,7 @@ #include "canvas-item-quad.h" -#include "color.h" // SP_RGBA_x_F +#include "display/cairo-utils.h" #include "helper/geom.h" namespace Inkscape { @@ -134,13 +134,11 @@ void CanvasItemQuad::_render(Inkscape::CanvasItemBuffer &buf) const cairo_set_operator(buf.cr->cobj(), CAIRO_OPERATOR_DIFFERENCE); } - buf.cr->set_source_rgba(SP_RGBA32_R_F(_fill), SP_RGBA32_G_F(_fill), - SP_RGBA32_B_F(_fill), SP_RGBA32_A_F(_fill)); + ink_cairo_set_source_rgba32(buf.cr, _fill); buf.cr->fill_preserve(); buf.cr->set_line_width(1); - buf.cr->set_source_rgba(SP_RGBA32_R_F(_stroke), SP_RGBA32_G_F(_stroke), - SP_RGBA32_B_F(_stroke), SP_RGBA32_A_F(_stroke)); + ink_cairo_set_source_rgba32(buf.cr, _stroke); buf.cr->stroke_preserve(); buf.cr->begin_new_path(); diff --git a/src/display/control/canvas-item-rect.cpp b/src/display/control/canvas-item-rect.cpp index ac502d487ef72a8353943a1f6fb20b9aa1143ad7..7c3cbdeeaec0a7477eb88907a15689d8f90f86b6 100644 --- a/src/display/control/canvas-item-rect.cpp +++ b/src/display/control/canvas-item-rect.cpp @@ -18,7 +18,6 @@ #include -#include "color.h" // SP_RGBA_x_F #include "desktop.h" #include "display/cairo-utils.h" #include "helper/geom.h" diff --git a/src/display/control/canvas-item-text.cpp b/src/display/control/canvas-item-text.cpp index e026fc1efe56a576ffa536d8bfb638467275a17c..694f064647306f8729b76beaad3dfbdb5bd70666 100644 --- a/src/display/control/canvas-item-text.cpp +++ b/src/display/control/canvas-item-text.cpp @@ -20,8 +20,7 @@ #include // std::move #include -#include "color.h" // SP_RGBA_x_F - +#include "display/cairo-utils.h" #include "ui/util.h" namespace Inkscape { @@ -150,8 +149,7 @@ void CanvasItemText::_render(Inkscape::CanvasItemBuffer &buf) const buf.cr->arc(x + radius, y + radius, radius, M_PI, 3*M_PI_2); } buf.cr->set_line_width(2); - buf.cr->set_source_rgba(SP_RGBA32_R_F(_background), SP_RGBA32_G_F(_background), - SP_RGBA32_B_F(_background), SP_RGBA32_A_F(_background)); + ink_cairo_set_source_rgba32(buf.cr, _background); buf.cr->fill(); } @@ -164,8 +162,8 @@ void CanvasItemText::_render(Inkscape::CanvasItemBuffer &buf) const buf.cr->select_font_face(_fontname, Cairo::ToyFontFace::Slant::NORMAL, Cairo::ToyFontFace::Weight::NORMAL); buf.cr->set_font_size(_fontsize); buf.cr->text_path(_text); - buf.cr->set_source_rgba(SP_RGBA32_R_F(_fill), SP_RGBA32_G_F(_fill), - SP_RGBA32_B_F(_fill), SP_RGBA32_A_F(_fill)); + + ink_cairo_set_source_rgba32(buf.cr, _fill); buf.cr->fill(); buf.cr->restore(); } diff --git a/src/display/control/canvas-page.cpp b/src/display/control/canvas-page.cpp index 81f6569884fcf6eda214999fbfae8eb24906d742..5426e9ed77204cfcafd22eaace493693841fe7aa 100644 --- a/src/display/control/canvas-page.cpp +++ b/src/display/control/canvas-page.cpp @@ -13,12 +13,11 @@ #include "canvas-page.h" #include "canvas-item-rect.h" #include "canvas-item-text.h" -#include "color.h" +#include "colors/utils.h" namespace Inkscape { CanvasPage::CanvasPage() = default; - CanvasPage::~CanvasPage() = default; /** @@ -47,7 +46,7 @@ void CanvasPage::add(Geom::Rect size, CanvasItemGroup *background_group, CanvasI item->set_name("margin"); item->set_dashed(false); item->set_inverted(false); - item->set_stroke(_margin_color); + item->set_stroke(_margin_color.toRGBA()); canvas_items.emplace_back(item); } @@ -55,7 +54,7 @@ void CanvasPage::add(Geom::Rect size, CanvasItemGroup *background_group, CanvasI item->set_name("bleed"); item->set_dashed(false); item->set_inverted(false); - item->set_stroke(_bleed_color); + item->set_stroke(_bleed_color.toRGBA()); canvas_items.emplace_back(item); } @@ -112,11 +111,11 @@ void CanvasPage::update(Geom::Rect size, Geom::OptRect margin, Geom::OptRect ble { // Put these in the preferences? bool border_on_top = _border_on_top; - guint32 shadow_color = _border_color; // there's no separate shadow color in the UI, border color is used + guint32 shadow_color = _border_color.toRGBA(); // there's no separate shadow color in the UI, border color is used guint32 select_color = 0x000000cc; - guint32 border_color = _border_color; - guint32 margin_color = _margin_color; - guint32 bleed_color = _bleed_color; + guint32 border_color = _border_color.toRGBA(); + guint32 margin_color = _margin_color.toRGBA(); + guint32 bleed_color = _bleed_color.toRGBA(); // This is used when showing the viewport as *not a page* it's mostly // never used as the first page is normally the viewport too. @@ -171,7 +170,7 @@ void CanvasPage::update(Geom::Rect size, Geom::OptRect margin, Geom::OptRect ble rect->set_background(_background_color | 0xff); } */ - rect->set_fill(_background_color); + rect->set_fill(_background_color.toRGBA()); rect->set_shadow(shadow_color, _shadow_size); } else { rect->set_fill(0x0); @@ -198,8 +197,7 @@ void CanvasPage::_updateTextItem(CanvasItemText *label, Geom::Rect page, std::st double radius = 0.2; // Change the colors for whiter/lighter backgrounds - unsigned char luminance = SP_RGBA32_LUMINANCE(_canvas_color); - if (luminance < 0x88) { + if (Colors::get_perceptual_lightness(_canvas_color) < 0.5) { foreground = 0x000000ff; background = 0xffffff99; selected = 0x50afe7ff; @@ -246,9 +244,9 @@ bool CanvasPage::setShadow(int shadow) return false; } -bool CanvasPage::setPageColor(uint32_t border, uint32_t bg, uint32_t canvas, uint32_t margin, uint32_t bleed) +bool CanvasPage::setPageColor(Color const &border, Color const &bg, Color const &canvas, Color const &margin, Color const &bleed) { - if (border != _border_color || bg != _background_color || canvas != _canvas_color) { + if (std::tie(border, bg, canvas, margin, bleed) != std::tie(_border_color, _background_color, _canvas_color, _margin_color, _bleed_color)) { _border_color = border; _background_color = bg; _canvas_color = canvas; diff --git a/src/display/control/canvas-page.h b/src/display/control/canvas-page.h index e3227bfc2a697e1c5d7d6a042c8a0d26b3b96cd5..6d7bac308a9ca1c7f129c1264feebb7024ac40bc 100644 --- a/src/display/control/canvas-page.h +++ b/src/display/control/canvas-page.h @@ -17,10 +17,13 @@ #include #include "canvas-item-ptr.h" +#include "colors/color.h" namespace Inkscape { namespace UI::Widget { class Canvas; } +using Colors::Color; + class CanvasItemGroup; class CanvasItemText; @@ -39,7 +42,7 @@ public: bool setOnTop(bool on_top); bool setShadow(int shadow); - bool setPageColor(uint32_t border, uint32_t bg, uint32_t canvas, uint32_t margin, uint32_t bleed); + bool setPageColor(Color const &border, Color const &bg, Color const &canvas, Color const &margin, Color const &bleed); bool setLabelStyle(const std::string &style); bool is_selected = false; @@ -53,11 +56,11 @@ private: int _shadow_size = 0; bool _border_on_top = true; - uint32_t _background_color = 0xffffffff; - uint32_t _border_color = 0x00000040; - uint32_t _canvas_color = 0xffffffff; - uint32_t _margin_color = 0x1699d771; // Blue'ish - uint32_t _bleed_color = 0xbe310e62; // Red'ish + Color _background_color {0xffffffff}; + Color _border_color {0x00000040}; + Color _canvas_color {0xffffffff}; + Color _margin_color {0x1699d771}; + Color _bleed_color {0xbe310e62}; std::string _label_style = "default"; }; diff --git a/src/display/control/ctrl-handle-rendering.cpp b/src/display/control/ctrl-handle-rendering.cpp index c2deb8fc7f13097f67f39f65e0f73817257015df..7d42d95946e9ead98d7e998da0416c225ca562f3 100644 --- a/src/display/control/ctrl-handle-rendering.cpp +++ b/src/display/control/ctrl-handle-rendering.cpp @@ -10,8 +10,8 @@ #include #include <2geom/point.h> -#include "color.h" #include "display/control/canvas-item-enums.h" +#include "display/cairo-utils.h" namespace Inkscape::Handles { namespace { @@ -424,13 +424,6 @@ void draw_cairo_path(CanvasItemCtrlShape shape, Cairo::Context &cr, double size, std::unordered_map> cache; std::mutex mutex; -void set_source_rgba32(Cairo::Context &cr, uint32_t rgba) -{ - cr.set_source_rgba(SP_RGBA32_R_F(rgba), - SP_RGBA32_G_F(rgba), - SP_RGBA32_B_F(rgba), - SP_RGBA32_A_F(rgba)); -} std::shared_ptr draw_uncached(RenderParams const &p) { @@ -470,16 +463,16 @@ std::shared_ptr draw_uncached(RenderParams const &p) draw_cairo_path(p.shape, cr, p.size * scale, grid_fit); // Outline. - set_source_rgba32(cr, p.outline); + ink_cairo_set_source_rgba32(cr, p.outline); cr.set_line_width(effective_outline * scale); cr.stroke_preserve(); // Fill. - set_source_rgba32(cr, p.fill);; + ink_cairo_set_source_rgba32(cr, p.fill);; cr.fill_preserve(); // Stroke. - set_source_rgba32(cr, p.stroke); + ink_cairo_set_source_rgba32(cr, p.stroke); cr.set_line_width(p.stroke_width * scale); cr.stroke(); diff --git a/src/display/drawing-paintserver.cpp b/src/display/drawing-paintserver.cpp index e018d8b9402f557268b9ef253a8cb6353cd4c0ce..9f3b8f8edf603cca032bc12de280a1ec28424696 100644 --- a/src/display/drawing-paintserver.cpp +++ b/src/display/drawing-paintserver.cpp @@ -1,14 +1,21 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "drawing-paintserver.h" + +#include + #include "cairo-utils.h" +#include "colors/color.h" namespace Inkscape { DrawingPaintServer::~DrawingPaintServer() = default; +DrawingSolidColor::DrawingSolidColor(Colors::Color color) + : color(std::move(color)) {} + cairo_pattern_t *DrawingSolidColor::create_pattern(cairo_t *, Geom::OptRect const &, double opacity) const { - return cairo_pattern_create_rgba(c[0], c[1], c[2], alpha * opacity); + return ink_cairo_pattern_create(color, opacity); } void DrawingGradient::common_setup(cairo_pattern_t *pat, Geom::OptRect const &bbox, double opacity) const @@ -45,7 +52,7 @@ cairo_pattern_t *DrawingLinearGradient::create_pattern(cairo_t *, Geom::OptRect // add stops for (auto &stop : stops) { // multiply stop opacity by paint opacity - cairo_pattern_add_color_stop_rgba(pat, stop.offset, stop.color.v.c[0], stop.color.v.c[1], stop.color.v.c[2], stop.opacity * opacity); + ink_cairo_pattern_add_color_stop(pat, stop.offset, *stop.color, opacity); } return pat; @@ -103,7 +110,7 @@ cairo_pattern_t *DrawingRadialGradient::create_pattern(cairo_t *ct, Geom::OptRec // add stops for (auto &stop : stops) { // multiply stop opacity by paint opacity - cairo_pattern_add_color_stop_rgba(pat, stop.offset, stop.color.v.c[0], stop.color.v.c[1], stop.color.v.c[2], stop.opacity * opacity); + ink_cairo_pattern_add_color_stop(pat, stop.offset, *stop.color, opacity); } return pat; diff --git a/src/display/drawing-paintserver.h b/src/display/drawing-paintserver.h index eb68f6b67267cf662acbb2d40bd3027ad6a12095..7a9a22edafa356c733de12377d5c5e95a29aba89 100644 --- a/src/display/drawing-paintserver.h +++ b/src/display/drawing-paintserver.h @@ -23,6 +23,9 @@ class SPGradient; namespace Inkscape { +namespace Colors { +class Color; +} /** * A DrawingPaintServer is a lightweight copy of the resources needed to paint using a paint server. @@ -56,15 +59,11 @@ class DrawingSolidColor final : public DrawingPaintServer { public: - DrawingSolidColor(float *c, double alpha) - : c({c[0], c[1], c[2]}) - , alpha(alpha) {} - + DrawingSolidColor(Colors::Color color); cairo_pattern_t *create_pattern(cairo_t *, Geom::OptRect const &, double opacity) const override; private: - std::array c; ///< RGB color components. - double alpha; + Colors::Color color; }; /** diff --git a/src/display/drawing.cpp b/src/display/drawing.cpp index 3070adf04c44a8646cf5651652ceed3f049658c0..5dc7c24801d2ba36a3a29d27eb9bbb1eb24acb40 100644 --- a/src/display/drawing.cpp +++ b/src/display/drawing.cpp @@ -373,7 +373,11 @@ void Drawing::averageColor(Geom::IntRect const &area, double &R, double &G, doub auto dc = Inkscape::DrawingContext(surface->cobj(), area.min()); render(dc, area); - ink_cairo_surface_average_color_premul(surface->cobj(), R, G, B, A); + auto color = ink_cairo_surface_average_color_premul(surface->cobj()); + R = color[0]; + G = color[1]; + B = color[2]; + A = color.getOpacity(); } /* diff --git a/src/display/nr-filter-diffuselighting.cpp b/src/display/nr-filter-diffuselighting.cpp index 8f80998ba96085aada46fd1ea6ecc523da3461e1..6905023ed9a57b7244f55b19f10e1267229fca58 100644 --- a/src/display/nr-filter-diffuselighting.cpp +++ b/src/display/nr-filter-diffuselighting.cpp @@ -26,7 +26,6 @@ #include "display/nr-filter-units.h" #include "display/nr-filter-utils.h" #include "display/nr-light.h" -#include "svg/svg-color.h" namespace Inkscape { namespace Filters { @@ -141,14 +140,6 @@ void FilterDiffuseLighting::render_cairo(FilterSlot &slot) const double g = SP_RGBA32_G_F(lighting_color); double b = SP_RGBA32_B_F(lighting_color); - if (icc) { - unsigned char ru, gu, bu; - icc_color_to_sRGB(&*icc, &ru, &gu, &bu); - r = SP_COLOR_U_TO_F(ru); - g = SP_COLOR_U_TO_F(gu); - b = SP_COLOR_U_TO_F(bu); - } - // Only alpha channel of input is used, no need to check input color_interpolation_filter value. // Lighting color is always defined in terms of sRGB, preconvert to linearRGB // if color_interpolation_filters set to linearRGB (for efficiency assuming diff --git a/src/display/nr-filter-diffuselighting.h b/src/display/nr-filter-diffuselighting.h index 69577b5d14deb918187fa93767d6b5691c10dc80..a50d90939ae67578f1c88aac98699eced40138b5 100644 --- a/src/display/nr-filter-diffuselighting.h +++ b/src/display/nr-filter-diffuselighting.h @@ -19,12 +19,10 @@ #include "display/nr-filter-primitive.h" #include "display/nr-filter-slot.h" #include "display/nr-filter-units.h" -#include "svg/svg-icc-color.h" class SPFeDistantLight; class SPFePointLight; class SPFeSpotLight; -struct SVGICCColor; namespace Inkscape { namespace Filters { @@ -36,7 +34,6 @@ public: ~FilterDiffuseLighting() override; void render_cairo(FilterSlot &slot) const override; - void set_icc(SVGICCColor const &icc_) { icc = icc_; } void area_enlarge(Geom::IntRect &area, Geom::Affine const &trans) const override; double complexity(Geom::Affine const &ctm) const override; @@ -51,9 +48,6 @@ public: guint32 lighting_color; Glib::ustring name() const override { return "Diffuse Lighting"; } - -private: - std::optional icc; }; } // namespace Filters diff --git a/src/display/nr-filter-flood.cpp b/src/display/nr-filter-flood.cpp index cc7fcb2ce6fe12aa8e57b7a6163931a872ae966e..b39f284c65f29e9a5d5154f4efaaf1382e04ff42 100644 --- a/src/display/nr-filter-flood.cpp +++ b/src/display/nr-filter-flood.cpp @@ -18,9 +18,6 @@ #include "display/cairo-utils.h" #include "display/nr-filter-flood.h" #include "display/nr-filter-slot.h" -#include "svg/svg-icc-color.h" -#include "svg/svg-color.h" -#include "color.h" namespace Inkscape { namespace Filters { @@ -36,15 +33,7 @@ void FilterFlood::render_cairo(FilterSlot &slot) const double r = SP_RGBA32_R_F(color); double g = SP_RGBA32_G_F(color); double b = SP_RGBA32_B_F(color); - double a = opacity; - - if (icc) { - unsigned char ru, gu, bu; - icc_color_to_sRGB(&*icc, &ru, &gu, &bu); - r = SP_COLOR_U_TO_F(ru); - g = SP_COLOR_U_TO_F(gu); - b = SP_COLOR_U_TO_F(bu); - } + double a = SP_RGBA32_A_F(color); cairo_surface_t *out = ink_cairo_surface_create_same_size(input, CAIRO_CONTENT_COLOR_ALPHA); @@ -99,11 +88,6 @@ void FilterFlood::set_color(guint32 c) color = c; } -void FilterFlood::set_opacity(double o) -{ - opacity = o; -} - double FilterFlood::complexity(Geom::Affine const &) const { // flood is actually less expensive than normal rendering, diff --git a/src/display/nr-filter-flood.h b/src/display/nr-filter-flood.h index 2228e007873da2d8e5a48afe4a79f5e654482a97..31504bbf91f12640dce45802f5a9d0089c4ce803 100644 --- a/src/display/nr-filter-flood.h +++ b/src/display/nr-filter-flood.h @@ -32,16 +32,12 @@ public: double complexity(Geom::Affine const &ctm) const override; bool uses_background() const override { return false; } - void set_opacity(double o); void set_color(guint32 c); - void set_icc(SVGICCColor const &icc_) { icc = icc_; } Glib::ustring name() const override { return Glib::ustring("Flood"); } private: - double opacity; guint32 color; - std::optional icc; }; } // namespace Filters diff --git a/src/display/nr-filter-specularlighting.cpp b/src/display/nr-filter-specularlighting.cpp index 64402bb15176ffa6c4b9d008c759bd54dc4460ce..7b8399d455d73009a52bc36d168f6820e315ee0c 100644 --- a/src/display/nr-filter-specularlighting.cpp +++ b/src/display/nr-filter-specularlighting.cpp @@ -26,8 +26,6 @@ #include "display/nr-filter-units.h" #include "display/nr-filter-utils.h" #include "display/nr-light.h" -#include "svg/svg-icc-color.h" -#include "svg/svg-color.h" namespace Inkscape { namespace Filters { @@ -154,14 +152,6 @@ void FilterSpecularLighting::render_cairo(FilterSlot &slot) const double g = SP_RGBA32_G_F(lighting_color); double b = SP_RGBA32_B_F(lighting_color); - if (icc) { - unsigned char ru, gu, bu; - icc_color_to_sRGB(&*icc, &ru, &gu, &bu); - r = SP_COLOR_U_TO_F(ru); - g = SP_COLOR_U_TO_F(gu); - b = SP_COLOR_U_TO_F(bu); - } - // Only alpha channel of input is used, no need to check input color_interpolation_filter value. // Lighting color is always defined in terms of sRGB, preconvert to linearRGB // if color_interpolation_filters set to linearRGB (for efficiency assuming diff --git a/src/display/nr-filter-specularlighting.h b/src/display/nr-filter-specularlighting.h index fdc7eb9a976dda07183b8d8e62e945879eb63678..43d7e0167b31a9330187e2980688653af7a24e35 100644 --- a/src/display/nr-filter-specularlighting.h +++ b/src/display/nr-filter-specularlighting.h @@ -34,7 +34,6 @@ public: ~FilterSpecularLighting() override; void render_cairo(FilterSlot &slot) const override; - void set_icc(SVGICCColor const &icc_) { icc = icc_; } void area_enlarge(Geom::IntRect &area, Geom::Affine const &trans) const override; double complexity(Geom::Affine const &ctm) const override; @@ -51,9 +50,6 @@ public: guint32 lighting_color; Glib::ustring name() const override { return Glib::ustring("Specular Lighting"); } - -private: - std::optional icc; }; } // namespace Filters diff --git a/src/display/nr-light.cpp b/src/display/nr-light.cpp index 00404fdc17c95d7a3832743712d6bbb9e040f5d8..9562ea779ddfce199c3edc77d22de570dcacef1b 100644 --- a/src/display/nr-light.cpp +++ b/src/display/nr-light.cpp @@ -17,7 +17,7 @@ #include "object/filters/distantlight.h" #include "object/filters/pointlight.h" #include "object/filters/spotlight.h" -#include "color.h" +#include "colors/color.h" namespace Inkscape { namespace Filters { diff --git a/src/display/nr-style.cpp b/src/display/nr-style.cpp index 8d21061aef33f6fe8bc499a6679b001d6ed287b4..3b1652307a6418f65d1fcc16d82a03f1b4f3d1bf 100644 --- a/src/display/nr-style.cpp +++ b/src/display/nr-style.cpp @@ -13,6 +13,7 @@ #include "display/nr-style.h" #include "style.h" +#include "colors/manager.h" #include "display/cairo-utils.h" #include "display/drawing-context.h" #include "display/drawing-pattern.h" @@ -28,7 +29,7 @@ void NRStyleData::Paint::clear() type = PaintType::NONE; } -void NRStyleData::Paint::set(SPColor const &c) +void NRStyleData::Paint::set(Colors::Color const &c) { clear(); type = PaintType::COLOR; @@ -47,16 +48,16 @@ void NRStyleData::Paint::set(SPPaintServer *ps) void NRStyleData::Paint::set(SPIPaint const *paint) { if (paint->isPaintserver()) { - SPPaintServer* server = paint->value.href->getObject(); + SPPaintServer* server = paint->href->getObject(); if (server && server->isValid()) { set(server); - } else if (paint->colorSet) { - set(paint->value.color); + } else if (paint->isColor()) { + set(paint->getColor()); } else { clear(); } } else if (paint->isColor()) { - set(paint->value.color); + set(paint->getColor()); } else if (paint->isNone()) { clear(); } else if (paint->paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL || @@ -259,7 +260,7 @@ NRStyleData::NRStyleData(SPStyle const *style, SPStyle const *context_style) } else if (style_td->text_decoration_color.set) { if(style->fill.isPaintserver() || style->fill.isColor()) { // SVG sets color specifically - text_decoration_fill.set(style->text_decoration_color.value.color); + text_decoration_fill.set(style->text_decoration_color.getColor()); } else { // No decoration fill because no text fill text_decoration_fill.clear(); @@ -274,7 +275,7 @@ NRStyleData::NRStyleData(SPStyle const *style, SPStyle const *context_style) } else if (style_td->text_decoration_color.set) { if(style->stroke.isPaintserver() || style->stroke.isColor()) { // SVG sets color specifically - text_decoration_stroke.set(style->text_decoration_color.value.color); + text_decoration_stroke.set(style->text_decoration_color.getColor()); } else { // No decoration stroke because no text stroke text_decoration_stroke.clear(); @@ -323,8 +324,7 @@ auto NRStyle::preparePaint(Inkscape::DrawingContext &dc, Inkscape::RenderContext } break; case NRStyleData::PaintType::COLOR: { - auto const &c = paint.color.v.c; - cp.pattern = CairoPatternUniqPtr(cairo_pattern_create_rgba(c[0], c[1], c[2], paint.opacity)); + cp.pattern = CairoPatternUniqPtr(ink_cairo_pattern_create(*paint.color, paint.opacity)); break; } default: diff --git a/src/display/nr-style.h b/src/display/nr-style.h index c3d889e612f29571b8e8532775b01920cfd41ada..ec81f93c902d6e67de5e4c5e91c7a6b70476c56b 100644 --- a/src/display/nr-style.h +++ b/src/display/nr-style.h @@ -18,7 +18,6 @@ #include #include #include <2geom/rect.h> -#include "color.h" #include "drawing-paintserver.h" #include "initlock.h" @@ -55,12 +54,12 @@ struct NRStyleData struct Paint { PaintType type = PaintType::NONE; - SPColor color = 0; + std::optional color; std::unique_ptr server; float opacity = 1.0; void clear(); - void set(SPColor const &c); + void set(Colors::Color const &c); void set(SPPaintServer *ps); void set(SPIPaint const *paint); bool ditherable() const; @@ -147,7 +146,7 @@ public: void applyTextDecorationStroke(DrawingContext &dc, CairoPatternUniqPtr const &cp) const; void invalidate(); - NRStyleData data; + NRStyleData data; private: struct CachedPattern diff --git a/src/document.cpp b/src/document.cpp index 7ff39fa03ce7fb5fc07e3e6129b80cce0dbce544..0f38893754b396b8927e629acc002715ed54afe4 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -54,7 +54,7 @@ #include "inkscape.h" #include "layer-manager.h" #include "page-manager.h" -#include "profile-manager.h" +#include "colors/document-cms.h" #include "rdf.h" #include "selection.h" @@ -146,9 +146,6 @@ SPDocument::SPDocument() : history_size = 0; seeking = false; - // Once things are set, hook in the manager - _profileManager = std::make_unique(this); - // For undo/redo undoStackObservers.add(*_event_log); @@ -164,13 +161,13 @@ SPDocument::SPDocument() : add_document_actions_effect(this); _page_manager = std::make_unique(this); + _cms_manager = std::make_unique(this); } SPDocument::~SPDocument() { destroySignal.emit(); // kill/unhook this first - _profileManager.reset(); _desktop_activated_connection.disconnect(); if (partial) { diff --git a/src/document.h b/src/document.h index b1301132c6aa5b3b085f2fcfd1cbee67c7d34e0d..bf8c2b68ffd4c8320005ef1e22a16ba5b69757e2 100644 --- a/src/document.h +++ b/src/document.h @@ -83,7 +83,9 @@ namespace Inkscape { class Event; class EventLog; class PageManager; - class ProfileManager; + namespace Colors { + class DocumentCMS; + } class Selection; class UndoStackObserver; namespace XML { @@ -168,12 +170,15 @@ public: Inkscape::PageManager& getPageManager() { return *_page_manager; } const Inkscape::PageManager& getPageManager() const { return *_page_manager; } + Inkscape::Colors::DocumentCMS &getDocumentCMS() { return *_cms_manager; } + const Inkscape::Colors::DocumentCMS &getDocumentCMS() const { return *_cms_manager; } private: void _importDefsNode(SPDocument *source, Inkscape::XML::Node *defs, Inkscape::XML::Node *target_defs); SPObject *_activexmltree; std::unique_ptr _page_manager; + std::unique_ptr _cms_manager; std::queue pending_resource_changes; @@ -191,9 +196,7 @@ public: /******** Getters and Setters **********/ // Document structure ----------------- - Inkscape::ProfileManager &getProfileManager() const { return *_profileManager; } Avoid::Router* getRouter() const { return _router.get(); } - /** Returns our SPRoot */ SPRoot *getRoot() { return root; } @@ -367,7 +370,6 @@ public: private: // Document ------------------------------ - std::unique_ptr _profileManager; // Color profile. std::unique_ptr _router; // Instance of the connector router std::unique_ptr _selection; diff --git a/src/doxygen-main.dox b/src/doxygen-main.dox index 9f51b0eae59663cc5fea2d6f5db8dc7fc710c5cb..6da5449cf1b3e0628669f789f39de6f31b66d613 100644 --- a/src/doxygen-main.dox +++ b/src/doxygen-main.dox @@ -320,7 +320,6 @@ namespace XML {} */ /** \page Rendering Rendering Related Classes and Files * - * SPColor [\ref color.cpp, \ref color.h, \ref color-rgba.h] * [\ref geom.cpp] [\ref mod360.cpp] */ /** \page OtherServices Classes and Files From Other Services diff --git a/src/extension/extension.cpp b/src/extension/extension.cpp index daefbc257bcd492112ae72856b7a58c435fa1371..642d39d6ae0cede4016007a81367aeca1dc091e0 100644 --- a/src/extension/extension.cpp +++ b/src/extension/extension.cpp @@ -784,7 +784,7 @@ Extension::get_param_optiongroup_contains(char const *name, char const *value) c Look up in the parameters list, const then execute the function on that found parameter. */ -std::uint32_t +Colors::Color Extension::get_param_color(char const *name) const { const InxParameter *param; @@ -880,12 +880,12 @@ Extension::set_param_optiongroup(char const *name, char const *value) Look up in the parameters list, const then execute the function on that found parameter. */ -std::uint32_t -Extension::set_param_color(char const *name, const std::uint32_t color) +void +Extension::set_param_color(char const *name, Inkscape::Colors::Color const &color) { InxParameter *param; param = get_param(name); - return param->set_color(color); + param->set_color(color); } /** diff --git a/src/extension/extension.h b/src/extension/extension.h index ae11554ecd3fc462da62fc92bf88a0216adfad8e..3de7e2cbb2bd0aff9f4308b8a5c24fb22b81a3ab 100644 --- a/src/extension/extension.h +++ b/src/extension/extension.h @@ -29,6 +29,9 @@ #include "util/hybrid-pointer.h" +// Push color headers to all extensions +#include "colors/color.h" + namespace Glib { class ustring; } // namespace Glib @@ -262,7 +265,8 @@ public: char const *get_param_string (char const *name) const; char const *get_param_optiongroup (char const *name, char const *alt) const; char const *get_param_optiongroup (char const *name) const; - std::uint32_t get_param_color (char const *name) const; + + Colors::Color get_param_color(char const *name) const; bool get_param_optiongroup_contains (char const *name, char const *value) const; @@ -271,7 +275,7 @@ public: double set_param_float (char const *name, double value); char const *set_param_string (char const *name, char const *value); char const *set_param_optiongroup (char const *name, char const *value); - std::uint32_t set_param_color (char const *name, std::uint32_t color); + void set_param_color (char const *name, Colors::Color const &color); void set_param_any(char const *name, std::string const &value); void set_param_hidden(char const *name, bool hidden); diff --git a/src/extension/internal/bitmap/colorize.cpp b/src/extension/internal/bitmap/colorize.cpp index 2e208afbc421cab5b5bb9ccca812ff77911b5510..03c2fa888e8f0afb479e749ef037fc02ac714ad7 100644 --- a/src/extension/internal/bitmap/colorize.cpp +++ b/src/extension/internal/bitmap/colorize.cpp @@ -14,8 +14,6 @@ #include "colorize.h" -#include "color.h" - #include #include @@ -23,22 +21,22 @@ namespace Inkscape { namespace Extension { namespace Internal { namespace Bitmap { - + +Colorize::Colorize() + : _color(0x000000ff) +{} + void Colorize::applyEffect(Magick::Image *image) { - float r = ((_color >> 24) & 0xff) / 255.0F; - float g = ((_color >> 16) & 0xff) / 255.0F; - float b = ((_color >> 8) & 0xff) / 255.0F; - float a = ((_color ) & 0xff) / 255.0F; - - Magick::ColorRGB mc(r,g,b); - - image->colorize(a * 100, mc); + if (auto color = _color.converted(Colors::Space::Type::RGB)) { + Magick::ColorRGB mc((*color)[0], (*color)[1], (*color)[2]); + image->colorize(color->getOpacity() * 100, mc); + } } void Colorize::refreshParameters(Inkscape::Extension::Effect *module) { - _color = module->get_param_color("color"); + _color = module->get_param_color("color"); } #include "../clear-n_.h" diff --git a/src/extension/internal/bitmap/colorize.h b/src/extension/internal/bitmap/colorize.h index e2db5791d857a90cc39f410a1f0d5a449c65c53d..f3e21e16144c849dad927f1e709a7bfb6af7eb97 100644 --- a/src/extension/internal/bitmap/colorize.h +++ b/src/extension/internal/bitmap/colorize.h @@ -11,20 +11,25 @@ #include "imagemagick.h" +#include "colors/color.h" + namespace Inkscape { namespace Extension { namespace Internal { namespace Bitmap { -class Colorize : public ImageMagick { -private: - guint32 _color; +class Colorize : public ImageMagick { public: + Colorize(); + void applyEffect(Magick::Image *image) override; void refreshParameters(Inkscape::Extension::Effect *module) override; static void init (); + +private: + Colors::Color _color; }; }; /* namespace Bitmap */ diff --git a/src/extension/internal/cairo-render-context.cpp b/src/extension/internal/cairo-render-context.cpp index bec401b8c7de99d242a6995d0716f35a03b1e7ff..209b96306a41cd66315398454ccd3746821e8cfe 100644 --- a/src/extension/internal/cairo-render-context.cpp +++ b/src/extension/internal/cairo-render-context.cpp @@ -34,6 +34,7 @@ #include #include +#include "colors/color.h" #include "display/drawing.h" #include "display/cairo-utils.h" #include "display/drawing-paintserver.h" @@ -1262,9 +1263,7 @@ CairoRenderContext::_createPatternForPaintServer(SPPaintServer const *const pain // add stops for (gint i = 0; unsigned(i) < lg->vector.stops.size(); i++) { - float rgb[3]; - lg->vector.stops[i].color.get_rgb_floatv(rgb); - cairo_pattern_add_color_stop_rgba(pattern, lg->vector.stops[i].offset, rgb[0], rgb[1], rgb[2], lg->vector.stops[i].opacity * alpha); + ink_cairo_pattern_add_color_stop(pattern, lg->vector.stops[i].offset, *lg->vector.stops[i].color, alpha); } } else if (auto rg = cast(paintserver_mutable)) { @@ -1282,9 +1281,7 @@ CairoRenderContext::_createPatternForPaintServer(SPPaintServer const *const pain // add stops for (gint i = 0; unsigned(i) < rg->vector.stops.size(); i++) { - float rgb[3]; - rg->vector.stops[i].color.get_rgb_floatv(rgb); - cairo_pattern_add_color_stop_rgba(pattern, rg->vector.stops[i].offset, rgb[0], rgb[1], rgb[2], rg->vector.stops[i].opacity * alpha); + ink_cairo_pattern_add_color_stop(pattern, rg->vector.stops[i].offset, *rg->vector.stops[i].color, alpha); } } else if (auto mg = cast(paintserver_mutable)) { pattern = mg->create_drawing_paintserver()->create_pattern(_cr, pbox, 1.0); @@ -1368,12 +1365,8 @@ void CairoRenderContext::_setFillStyle(SPStyle const *const style, Geom::OptRect cairo_set_source(_cr, pattern); cairo_pattern_destroy(pattern); } - } else if (style->fill.colorSet) { - float rgb[3]; - style->fill.value.color.get_rgb_floatv(rgb); - - cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha); - + } else if (style->fill.isColor()) { + ink_cairo_set_source_color(_cr, style->fill.getColor(), alpha); } else { // unset fill is black g_assert(!style->fill.set || (paint_server && !paint_server->isValid())); @@ -1386,10 +1379,7 @@ void CairoRenderContext::_setStrokeStyle(SPStyle const *style, Geom::OptRect con { float const alpha = _mergedOpacity(SP_SCALE24_TO_FLOAT(style->stroke_opacity.value)); if (style->stroke.isColor() || (style->stroke.isPaintserver() && !style->getStrokePaintServer()->isValid())) { - float rgb[3]; - style->stroke.value.color.get_rgb_floatv(rgb); - - cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha); + ink_cairo_set_source_color(_cr, style->stroke.getColor(), alpha); } else { g_assert( style->stroke.isPaintserver() || is(SP_STYLE_STROKE_SERVER(style)) diff --git a/src/extension/internal/emf-inout.cpp b/src/extension/internal/emf-inout.cpp index 687a5326ad227d3f394d1f9e7c81aae4589baccf..dd3e44357298edcb79b1f86500bc305b3de6a897 100644 --- a/src/extension/internal/emf-inout.cpp +++ b/src/extension/internal/emf-inout.cpp @@ -31,6 +31,7 @@ #include "emf-inout.h" #include "clear-n_.h" +#include "colors/color.h" #include "display/drawing-item.h" #include "display/drawing.h" #include "document.h" @@ -773,31 +774,27 @@ Emf::output_style(PEMF_CALLBACK_DATA d, int iType) SVGOStringStream tmp_style; char tmp[1024] = {0}; - float fill_rgb[3]; - d->dc[d->level].style.fill.value.color.get_rgb_floatv(fill_rgb); - float stroke_rgb[3]; - d->dc[d->level].style.stroke.value.color.get_rgb_floatv(stroke_rgb); + auto fill = d->dc[d->level].style.fill.getColor(); + auto stroke = d->dc[d->level].style.stroke.getColor(); // for U_EMR_BITBLT with no image, try to approximate some of these operations/ // Assume src color is "white" if(d->dwRop3){ switch(d->dwRop3){ case U_PATINVERT: // invert pattern - fill_rgb[0] = 1.0 - fill_rgb[0]; - fill_rgb[1] = 1.0 - fill_rgb[1]; - fill_rgb[2] = 1.0 - fill_rgb[2]; + fill.set("black"); break; case U_SRCINVERT: // treat all of these as black case U_DSTINVERT: case U_BLACKNESS: case U_SRCERASE: case U_NOTSRCCOPY: - fill_rgb[0]=fill_rgb[1]=fill_rgb[2]=0.0; + fill.set("black"); break; case U_SRCCOPY: // treat all of these as white case U_NOTSRCERASE: case U_WHITENESS: - fill_rgb[0]=fill_rgb[1]=fill_rgb[2]=1.0; + fill.set("white"); break; case U_SRCPAINT: // use the existing color case U_SRCAND: @@ -817,19 +814,15 @@ Emf::output_style(PEMF_CALLBACK_DATA d, int iType) // pen color through. switch(d->dwRop2){ case U_R2_BLACK: - fill_rgb[0] = fill_rgb[1] = fill_rgb[2] = 0.0; - stroke_rgb[0]= stroke_rgb[1]= stroke_rgb[2] = 0.0; + fill.set("black"); + stroke.set("black"); break; case U_R2_NOTMERGEPEN: case U_R2_MASKNOTPEN: break; case U_R2_NOTCOPYPEN: - fill_rgb[0] = 1.0 - fill_rgb[0]; - fill_rgb[1] = 1.0 - fill_rgb[1]; - fill_rgb[2] = 1.0 - fill_rgb[2]; - stroke_rgb[0] = 1.0 - stroke_rgb[0]; - stroke_rgb[1] = 1.0 - stroke_rgb[1]; - stroke_rgb[2] = 1.0 - stroke_rgb[2]; + fill.invert(); + stroke.invert(); break; case U_R2_MASKPENNOT: case U_R2_NOT: @@ -844,8 +837,8 @@ Emf::output_style(PEMF_CALLBACK_DATA d, int iType) case U_R2_MERGEPEN: break; case U_R2_WHITE: - fill_rgb[0] = fill_rgb[1] = fill_rgb[2] = 1.0; - stroke_rgb[0]= stroke_rgb[1]= stroke_rgb[2] = 1.0; + fill.set("white"); + stroke.set("white"); break; default: break; @@ -871,14 +864,8 @@ Emf::output_style(PEMF_CALLBACK_DATA d, int iType) case DRAW_LINEAR_GRADIENT: case DRAW_PAINT: default: // <-- this should never happen, but just in case... - snprintf( - tmp, 1023, - "fill:#%02x%02x%02x;", - SP_COLOR_F_TO_U(fill_rgb[0]), - SP_COLOR_F_TO_U(fill_rgb[1]), - SP_COLOR_F_TO_U(fill_rgb[2]) - ); - tmp_style << tmp; + fill.convert(Colors::Space::Type::RGB); + tmp_style << "fill:" << fill.toString(false).c_str() << ";"; break; } snprintf( @@ -897,11 +884,7 @@ Emf::output_style(PEMF_CALLBACK_DATA d, int iType) (d->dc[d->level].fill_mode == d->dc[d->level].stroke_mode) && ( (d->dc[d->level].fill_mode != DRAW_PAINT) || - ( - (fill_rgb[0]==stroke_rgb[0]) && - (fill_rgb[1]==stroke_rgb[1]) && - (fill_rgb[2]==stroke_rgb[2]) - ) + fill == stroke ) ){ d->dc[d->level].stroke_set = false; @@ -924,14 +907,8 @@ Emf::output_style(PEMF_CALLBACK_DATA d, int iType) case DRAW_LINEAR_GRADIENT: case DRAW_PAINT: default: // <-- this should never happen, but just in case... - snprintf( - tmp, 1023, - "stroke:#%02x%02x%02x;", - SP_COLOR_F_TO_U(stroke_rgb[0]), - SP_COLOR_F_TO_U(stroke_rgb[1]), - SP_COLOR_F_TO_U(stroke_rgb[2]) - ); - tmp_style << tmp; + stroke.convert(Colors::Space::Type::RGB); + tmp_style << "stroke:" << stroke.toString(false).c_str() << ";"; break; } tmp_style << "stroke-width:" << @@ -1140,15 +1117,9 @@ Emf::select_pen(PEMF_CALLBACK_DATA d, int index) d->level = cur_level; d->dc[d->level].style.stroke_width.value = pen_width; } - - double r, g, b; - r = SP_COLOR_U_TO_F( U_RGBAGetR(pEmr->lopn.lopnColor) ); - g = SP_COLOR_U_TO_F( U_RGBAGetG(pEmr->lopn.lopnColor) ); - b = SP_COLOR_U_TO_F( U_RGBAGetB(pEmr->lopn.lopnColor) ); - d->dc[d->level].style.stroke.value.color.set( r, g, b ); + d->dc[d->level].style.stroke.setColor(Colors::Color(U_RGB_COMPOSE(pEmr->lopn.lopnColor), false)); } - void Emf::select_extpen(PEMF_CALLBACK_DATA d, int index) { @@ -1267,11 +1238,7 @@ Emf::select_extpen(PEMF_CALLBACK_DATA d, int index) d->dc[d->level].stroke_set = true; if (pEmr->elp.elpPenStyle == U_PS_NULL) { // draw nothing, but fill out all the values with something - double r, g, b; - r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor)); - g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor)); - b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor)); - d->dc[d->level].style.stroke.value.color.set( r, g, b ); + d->dc[d->level].style.stroke.setColor(Colors::Color(U_RGB_COMPOSE(d->dc[d->level].textColor), false)); d->dc[d->level].style.stroke_width.value = 0; d->dc[d->level].stroke_set = false; d->dc[d->level].stroke_mode = DRAW_PAINT; @@ -1293,11 +1260,7 @@ Emf::select_extpen(PEMF_CALLBACK_DATA d, int index) } if( pEmr->elp.elpBrushStyle == U_BS_SOLID){ - double r, g, b; - r = SP_COLOR_U_TO_F( U_RGBAGetR(pEmr->elp.elpColor) ); - g = SP_COLOR_U_TO_F( U_RGBAGetG(pEmr->elp.elpColor) ); - b = SP_COLOR_U_TO_F( U_RGBAGetB(pEmr->elp.elpColor) ); - d->dc[d->level].style.stroke.value.color.set( r, g, b ); + d->dc[d->level].style.stroke.setColor(Colors::Color(U_RGB_COMPOSE(pEmr->elp.elpColor), false)); d->dc[d->level].stroke_mode = DRAW_PAINT; d->dc[d->level].stroke_set = true; } @@ -1313,11 +1276,7 @@ Emf::select_extpen(PEMF_CALLBACK_DATA d, int index) d->dc[d->level].stroke_set = true; } else { // U_BS_PATTERN and anything strange that falls in, stroke is solid textColor - double r, g, b; - r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor)); - g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor)); - b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor)); - d->dc[d->level].style.stroke.value.color.set( r, g, b ); + d->dc[d->level].style.stroke.setColor(Colors::Color(U_RGB_COMPOSE(d->dc[d->level].textColor))); d->dc[d->level].stroke_mode = DRAW_PAINT; d->dc[d->level].stroke_set = true; } @@ -1336,11 +1295,7 @@ Emf::select_brush(PEMF_CALLBACK_DATA d, int index) if(iType == U_EMR_CREATEBRUSHINDIRECT){ PU_EMRCREATEBRUSHINDIRECT pEmr = (PU_EMRCREATEBRUSHINDIRECT) d->emf_obj[index].lpEMFR; if( pEmr->lb.lbStyle == U_BS_SOLID){ - double r, g, b; - r = SP_COLOR_U_TO_F( U_RGBAGetR(pEmr->lb.lbColor) ); - g = SP_COLOR_U_TO_F( U_RGBAGetG(pEmr->lb.lbColor) ); - b = SP_COLOR_U_TO_F( U_RGBAGetB(pEmr->lb.lbColor) ); - d->dc[d->level].style.fill.value.color.set( r, g, b ); + d->dc[d->level].style.fill.setColor(Colors::Color(U_RGB_COMPOSE(pEmr->lb.lbColor), false)); d->dc[d->level].fill_mode = DRAW_PAINT; d->dc[d->level].fill_set = true; } @@ -1355,11 +1310,7 @@ Emf::select_brush(PEMF_CALLBACK_DATA d, int index) PU_EMRCREATEDIBPATTERNBRUSHPT pEmr = (PU_EMRCREATEDIBPATTERNBRUSHPT) d->emf_obj[index].lpEMFR; tidx = add_image(d, (void *) pEmr, pEmr->cbBits, pEmr->cbBmi, pEmr->iUsage, pEmr->offBits, pEmr->offBmi); if(tidx == U_EMR_INVALID){ // This happens if createmonobrush has a DIB that isn't monochrome - double r, g, b; - r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor)); - g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor)); - b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor)); - d->dc[d->level].style.fill.value.color.set( r, g, b ); + d->dc[d->level].style.fill.setColor(Colors::Color(U_RGB_COMPOSE(d->dc[d->level].textColor), false)); d->dc[d->level].fill_mode = DRAW_PAINT; } else { @@ -2509,8 +2460,7 @@ std::cout << "BEFORE DRAW" val = 255.0 / 255.0; break; } - d->dc[d->level].style.fill.value.color.set( val, val, val ); - + d->dc[d->level].style.fill.setColor(Colors::Color(SP_RGBA32_F_COMPOSE(val, val, val, 1.0), false)); d->dc[d->level].fill_mode = DRAW_PAINT; d->dc[d->level].fill_set = true; break; @@ -2525,7 +2475,7 @@ std::cout << "BEFORE DRAW" float val = index == U_BLACK_PEN ? 0 : 1; d->dc[d->level].style.stroke_dasharray.set = false; d->dc[d->level].style.stroke_width.value = 1.0; - d->dc[d->level].style.stroke.value.color.set( val, val, val ); + d->dc[d->level].style.stroke.setColor(Colors::Color(SP_RGBA32_F_COMPOSE(val, val, val, 1.0), false)); d->dc[d->level].stroke_mode = DRAW_PAINT; d->dc[d->level].stroke_set = true; diff --git a/src/extension/internal/emf-print.cpp b/src/extension/internal/emf-print.cpp index 6db324b4b54766c369e982510f34467d9dedf940..2954d45d1c1dcc0138639532f85cd01bb55bc064 100644 --- a/src/extension/internal/emf-print.cpp +++ b/src/extension/internal/emf-print.cpp @@ -65,6 +65,8 @@ #include "util/units.h" +using namespace Inkscape::Colors; + namespace Inkscape { namespace Extension { namespace Internal { @@ -280,7 +282,7 @@ unsigned int PrintEmf::begin(Inkscape::Extension::Print *mod, SPDocument *doc) g_error("Fatal programming error in PrintEmf::begin at U_EMRSETTEXTALIGN_set"); } - htextcolor_rgb[0] = htextcolor_rgb[1] = htextcolor_rgb[2] = 0.0; + htextcolor_rgb = Color(0x000000ff); rec = U_EMRSETTEXTCOLOR_set(U_RGB(0, 0, 0)); if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { g_error("Fatal programming error in PrintEmf::begin at U_EMRSETTEXTCOLOR_set"); @@ -322,7 +324,6 @@ unsigned int PrintEmf::finish(Inkscape::Extension::Print * /*mod*/) // fcolor is defined when gradients are being expanded, it is the color of one stripe or ring. int PrintEmf::create_brush(SPStyle const *style, PU_COLORREF fcolor) { - float rgb[3]; char *rec; U_LOGBRUSH lb; uint32_t brush, fmode; @@ -361,12 +362,12 @@ int PrintEmf::create_brush(SPStyle const *style, PU_COLORREF fcolor) opacity = 0.0; // basically the same as no fill } #endif - style->fill.value.color.get_rgb_floatv(rgb); + auto rgb = *style->fill.getColor().converted(Space::Type::RGB); hatchColor = U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]); fmode = style->fill_rule.computed == 0 ? U_WINDING : (style->fill_rule.computed == 2 ? U_ALTERNATE : U_ALTERNATE); } else if (is(SP_STYLE_FILL_SERVER(style))) { // must be paint-server - SPPaintServer *paintserver = style->fill.value.href->getObject(); + SPPaintServer *paintserver = style->fill.href->getObject(); auto pat = cast(paintserver); double dwidth = pat->width(); double dheight = pat->height(); @@ -392,7 +393,7 @@ int PrintEmf::create_brush(SPStyle const *style, PU_COLORREF fcolor) brushStyle = U_BS_HATCHED; } else if (is(SP_STYLE_FILL_SERVER(style))) { // must be a gradient // currently we do not do anything with gradients, the code below just sets the color to the average of the stops - SPPaintServer *paintserver = style->fill.value.href->getObject(); + SPPaintServer *paintserver = style->fill.href->getObject(); SPLinearGradient *lg = nullptr; SPRadialGradient *rg = nullptr; @@ -548,10 +549,8 @@ int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) bkColor = U_RGB(0, 0, 0); if (style) { - float rgb[3]; - if (is(SP_STYLE_STROKE_SERVER(style))) { // must be paint-server - SPPaintServer *paintserver = style->stroke.value.href->getObject(); + SPPaintServer *paintserver = style->stroke.href->getObject(); auto pat = cast(paintserver); double dwidth = pat->width(); double dheight = pat->height(); @@ -594,7 +593,7 @@ int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) } else if (is(SP_STYLE_STROKE_SERVER(style))) { // must be a gradient // currently we do not do anything with gradients, the code below has no net effect. - SPPaintServer *paintserver = style->stroke.value.href->getObject(); + SPPaintServer *paintserver = style->stroke.href->getObject(); if (is(paintserver)) { auto lg = cast(paintserver); @@ -629,7 +628,7 @@ int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) // default fill } } else if (style->stroke.isColor()) { // test last, always seems to be set, even for other types above - style->stroke.value.color.get_rgb_floatv(rgb); + auto rgb = *style->stroke.getColor().converted(Space::Type::RGB); brushStyle = U_BS_SOLID; hatchColor = U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]); hatchType = U_HS_SOLIDCLR; @@ -1151,23 +1150,17 @@ unsigned int PrintEmf::fill( */ destroy_pen(); //this sets the NULL_PEN, otherwise gradient slices may display with boundaries, see longer explanation below Geom::Path cutter; - float rgb[3]; U_COLORREF wc, c1, c2; FillRule frb = SPWR_to_LVFR((SPWindRule) style->fill_rule.computed); double doff, doff_base, doff_range; double divisions = 128.0; int nstops; int istop = 1; - float opa; // opacity at stop SPRadialGradient *tg = (SPRadialGradient *)(gv.grad); // linear/radial are the same here nstops = tg->vector.stops.size(); - tg->vector.stops[0].color.get_rgb_floatv(rgb); - opa = tg->vector.stops[0].opacity; // first stop - c1 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); - tg->vector.stops[nstops - 1].color.get_rgb_floatv(rgb); - opa = tg->vector.stops[nstops - 1].opacity; // last stop - c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + c1 = toColorRef(tg->vector.stops[0].color); + c2 = toColorRef(tg->vector.stops[nstops - 1].color); doff = 0.0; doff_base = 0.0; @@ -1195,9 +1188,7 @@ unsigned int PrintEmf::fill( (void) create_brush(style, &wc); print_pathv(pathvr, fill_transform); - tg->vector.stops[istop].color.get_rgb_floatv(rgb); - opa = tg->vector.stops[istop].opacity; - c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + c2 = toColorRef(tg->vector.stops[istop].color); for (start = 0.0; start < range; start += step, doff += 1. / divisions) { stop = start + step + overlap; @@ -1221,9 +1212,7 @@ unsigned int PrintEmf::fill( doff_base = doff_range; doff_range = tg->vector.stops[istop].offset; // next or last stop c1 = c2; - tg->vector.stops[istop].color.get_rgb_floatv(rgb); - opa = tg->vector.stops[istop].opacity; - c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + c2 = toColorRef(tg->vector.stops[istop].color); } } } else if (gv.mode == DRAW_LINEAR_GRADIENT) { @@ -1287,9 +1276,7 @@ unsigned int PrintEmf::fill( rcb.top = round(outUL[Y]); rcb.right = round(outLR[X]); rcb.bottom = round(outLR[Y]); - tg->vector.stops[istop].color.get_rgb_floatv(rgb); - opa = tg->vector.stops[istop].opacity; - c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + c2 = toColorRef(tg->vector.stops[istop].color); if(rectDir == 2 || rectDir == 4){ // gradient is reversed, so swap colors ut[0] = make_trivertex(outUL, c2); @@ -1337,9 +1324,7 @@ unsigned int PrintEmf::fill( pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb); print_pathv(pathvr, fill_transform); - tg->vector.stops[istop].color.get_rgb_floatv(rgb); - opa = tg->vector.stops[istop].opacity; - c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + c2 = toColorRef(tg->vector.stops[istop].color); for (start = 0.0; start < range; start += step, doff += 1. / divisions) { stop = start + step + overlap; @@ -1362,9 +1347,7 @@ unsigned int PrintEmf::fill( doff_base = doff_range; doff_range = tg->vector.stops[istop].offset; // next or last stop c1 = c2; - tg->vector.stops[istop].color.get_rgb_floatv(rgb); - opa = tg->vector.stops[istop].opacity; - c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + c2 = toColorRef(tg->vector.stops[istop].color); } } } @@ -2082,12 +2065,11 @@ unsigned int PrintEmf::text(Inkscape::Extension::Print * /*mod*/, char const *te g_error("Fatal programming error in PrintEmf::text at selectobject_set"); } - float rgb[3]; - style->fill.value.color.get_rgb_floatv(rgb); + auto rgb = style->fill.getColor(); // only change the text color when it needs to be changed - if (memcmp(htextcolor_rgb, rgb, 3 * sizeof(float))) { - memcpy(htextcolor_rgb, rgb, 3 * sizeof(float)); - rec = U_EMRSETTEXTCOLOR_set(U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2])); + if (htextcolor_rgb != rgb) { + htextcolor_rgb = rgb; + rec = U_EMRSETTEXTCOLOR_set(toColorRef(rgb)); if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { g_error("Fatal programming error in PrintEmf::text at U_EMRSETTEXTCOLOR_set"); } diff --git a/src/extension/internal/filter/bevels.h b/src/extension/internal/filter/bevels.h index 6eaa5dcedbf8448ad5cb13ad19992606b14ef033..80cf5ab9cb13627293c84dd3cd9aac11bdd2661f 100644 --- a/src/extension/internal/filter/bevels.h +++ b/src/extension/internal/filter/bevels.h @@ -81,30 +81,21 @@ DiffuseLight::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream smooth; std::ostringstream elevation; std::ostringstream azimuth; - std::ostringstream r; - std::ostringstream g; - std::ostringstream b; - std::ostringstream a; smooth << ext->get_param_float("smooth"); elevation << ext->get_param_int("elevation"); azimuth << ext->get_param_int("azimuth"); - guint32 color = ext->get_param_color("color"); - - r << ((color >> 24) & 0xff); - g << ((color >> 16) & 0xff); - b << ((color >> 8) & 0xff); - a << (color & 0xff) / 255.0F; + auto color = ext->get_param_color("color"); _filter = g_strdup_printf( "\n" "\n" - "\n" + "\n" "\n" "\n" "\n" - "\n" - "\n", smooth.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), elevation.str().c_str(), azimuth.str().c_str(), a.str().c_str()); + "\n" + "\n", smooth.str().c_str(), color.toString(false).c_str(), elevation.str().c_str(), azimuth.str().c_str(), color.getOpacity()); return _filter; }; /* DiffuseLight filter */ @@ -165,32 +156,23 @@ MatteJelly::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream bright; std::ostringstream elevation; std::ostringstream azimuth; - std::ostringstream r; - std::ostringstream g; - std::ostringstream b; - std::ostringstream a; smooth << ext->get_param_float("smooth"); bright << ext->get_param_float("bright"); elevation << ext->get_param_int("elevation"); azimuth << ext->get_param_int("azimuth"); - guint32 color = ext->get_param_color("color"); - - r << ((color >> 24) & 0xff); - g << ((color >> 16) & 0xff); - b << ((color >> 8) & 0xff); - a << (color & 0xff) / 255.0F; + auto color = ext->get_param_color("color"); _filter = g_strdup_printf( "\n" "\n" "\n" - "\n" + "\n" "\n" "\n" - "\n" + "\n" "\n" - "\n", smooth.str().c_str(), bright.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), elevation.str().c_str(), azimuth.str().c_str(), a.str().c_str()); + "\n", smooth.str().c_str(), bright.str().c_str(), color.toString().c_str(), elevation.str().c_str(), azimuth.str().c_str(), color.getOpacity()); return _filter; }; /* MatteJelly filter */ @@ -251,31 +233,22 @@ SpecularLight::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream bright; std::ostringstream elevation; std::ostringstream azimuth; - std::ostringstream r; - std::ostringstream g; - std::ostringstream b; - std::ostringstream a; smooth << ext->get_param_float("smooth"); bright << ext->get_param_float("bright"); elevation << ext->get_param_int("elevation"); azimuth << ext->get_param_int("azimuth"); - guint32 color = ext->get_param_color("color"); + auto color = ext->get_param_color("color"); - r << ((color >> 24) & 0xff); - g << ((color >> 16) & 0xff); - b << ((color >> 8) & 0xff); - a << (color & 0xff) / 255.0F; - _filter = g_strdup_printf( "\n" "\n" - "\n" + "\n" "\n" "\n" - "\n" + "\n" "\n" - "\n", smooth.str().c_str(), bright.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), elevation.str().c_str(), azimuth.str().c_str(), a.str().c_str()); + "\n", smooth.str().c_str(), bright.str().c_str(), color.toString(false).c_str(), elevation.str().c_str(), azimuth.str().c_str(), color.getOpacity()); return _filter; }; /* SpecularLight filter */ diff --git a/src/extension/internal/filter/blurs.h b/src/extension/internal/filter/blurs.h index 0d9966b06a30ac74caf5b1afa70f5bdb3abd5824..8d56776d7fc462a6f298713ca197efeee944a40b 100644 --- a/src/extension/internal/filter/blurs.h +++ b/src/extension/internal/filter/blurs.h @@ -387,10 +387,6 @@ ImageBlur::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream dilat; std::ostringstream erosion; std::ostringstream opacity; - std::ostringstream r; - std::ostringstream g; - std::ostringstream b; - std::ostringstream a; std::ostringstream blend; std::ostringstream background; @@ -400,11 +396,7 @@ ImageBlur::get_filter_text (Inkscape::Extension::Extension * ext) erosion << -ext->get_param_float("erosion"); opacity << ext->get_param_float("opacity"); - guint32 color = ext->get_param_color("color"); - r << ((color >> 24) & 0xff); - g << ((color >> 16) & 0xff); - b << ((color >> 8) & 0xff); - a << (color & 0xff) / 255.0F; + auto color = ext->get_param_color("color"); blend << ext->get_param_optiongroup("blend"); if (ext->get_param_bool("background")) { @@ -416,14 +408,14 @@ ImageBlur::get_filter_text (Inkscape::Extension::Extension * ext) // clang-format off _filter = g_strdup_printf( "\n" - "\n" + "\n" "\n" "\n" "\n" "\n" "\n" "\n" - "\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), + "\n", color.getOpacity(), color.toString(false).c_str(), hblur.str().c_str(), vblur.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), background.str().c_str(), blend.str().c_str(), opacity.str().c_str()); // clang-format on diff --git a/src/extension/internal/filter/bumps.h b/src/extension/internal/filter/bumps.h index 85808b0fe8f2f82344098af3cf36fd8e2203e201..3d191993b83ff53f9e884d04cbc234a398942f6d 100644 --- a/src/extension/internal/filter/bumps.h +++ b/src/extension/internal/filter/bumps.h @@ -169,13 +169,7 @@ Bump::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream lightStart; std::ostringstream lightOptions; std::ostringstream lightEnd; - - std::ostringstream floodRed; - std::ostringstream floodGreen; - std::ostringstream floodBlue; - std::ostringstream floodAlpha; std::ostringstream colorize; - simplifyImage << ext->get_param_float("simplifyImage"); simplifyBump << ext->get_param_float("simplifyBump"); @@ -185,8 +179,8 @@ Bump::get_filter_text (Inkscape::Extension::Extension * ext) crop << ext->get_param_float("crop"); blend << ext->get_param_optiongroup("blend"); - guint32 lightingColor = ext->get_param_color("lightingColor"); - guint32 imageColor = ext->get_param_color("imageColor"); + auto lightingColor = ext->get_param_color("lightingColor"); + auto imageColor = ext->get_param_color("imageColor"); if (ext->get_param_bool("background")) { bumpSource << "BackgroundImage" ; @@ -197,15 +191,13 @@ Bump::get_filter_text (Inkscape::Extension::Extension * ext) const gchar *lightType = ext->get_param_optiongroup("lightType"); if ((g_ascii_strcasecmp("specular", lightType) == 0)) { // Specular - lightStart << "> 24) & 0xff) << "," - << ((lightingColor >> 16) & 0xff) << "," << ((lightingColor >> 8) & 0xff) << ")\" surfaceScale=\"" + lightStart << "get_param_float("height") << "\" specularConstant=\"" << ext->get_param_float("lightness") << "\" specularExponent=\"" << ext->get_param_int("precision") << "\" result=\"lighting\">"; lightEnd << ""; } else { // Diffuse - lightStart << "> 24) & 0xff) << "," - << ((lightingColor >> 16) & 0xff) << "," << ((lightingColor >> 8) & 0xff) << ")\" surfaceScale=\"" + lightStart << "get_param_float("height") << "\" diffuseConstant=\"" << ext->get_param_float("lightness") << "\" result=\"lighting\">"; lightEnd << ""; @@ -230,11 +222,6 @@ Bump::get_filter_text (Inkscape::Extension::Extension * ext) << "\" />"; } - floodRed << ((imageColor >> 24) & 0xff); - floodGreen << ((imageColor >> 16) & 0xff); - floodBlue << ((imageColor >> 8) & 0xff); - floodAlpha << (imageColor & 0xff) / 255.0F; - if (ext->get_param_bool("colorize")) { colorize << "flood" ; } else { @@ -252,14 +239,14 @@ Bump::get_filter_text (Inkscape::Extension::Extension * ext) "%s\n" "%s\n" "%s\n" - "\n" + "\n" "\n" "\n" "\n" "\n", simplifyImage.str().c_str(), bumpSource.str().c_str(), red.str().c_str(), green.str().c_str(), blue.str().c_str(), crop.str().c_str(), simplifyBump.str().c_str(), lightStart.str().c_str(), lightOptions.str().c_str(), lightEnd.str().c_str(), - floodRed.str().c_str(), floodGreen.str().c_str(), floodBlue.str().c_str(), floodAlpha.str().c_str(), + imageColor.toString(false).c_str(), imageColor.getOpacity(), colorize.str().c_str(), blend.str().c_str()); // clang-format on @@ -397,16 +384,6 @@ WaxBump::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream precision; std::ostringstream distantAzimuth; std::ostringstream distantElevation; - - std::ostringstream lightRed; - std::ostringstream lightGreen; - std::ostringstream lightBlue; - - std::ostringstream floodRed; - std::ostringstream floodGreen; - std::ostringstream floodBlue; - std::ostringstream floodAlpha; - std::ostringstream revert; std::ostringstream lightingblend; std::ostringstream highlightblend; @@ -429,16 +406,8 @@ WaxBump::get_filter_text (Inkscape::Extension::Extension * ext) distantAzimuth << ext->get_param_int("distantAzimuth"); distantElevation << ext->get_param_int("distantElevation"); - guint32 lightingColor = ext->get_param_color("lightingColor"); - lightRed << ((lightingColor >> 24) & 0xff); - lightGreen << ((lightingColor >> 16) & 0xff); - lightBlue << ((lightingColor >> 8) & 0xff); - - guint32 imageColor = ext->get_param_color("imageColor"); - floodRed << ((imageColor >> 24) & 0xff); - floodGreen << ((imageColor >> 16) & 0xff); - floodBlue << ((imageColor >> 8) & 0xff); - floodAlpha << (imageColor & 0xff) / 255.0F; + auto lightingColor = ext->get_param_color("lightingColor"); + auto imageColor = ext->get_param_color("imageColor"); if (ext->get_param_bool("revert")) { revert << "in" ; @@ -457,10 +426,10 @@ WaxBump::get_filter_text (Inkscape::Extension::Extension * ext) "\n" "\n" "\n" - "\n" + "\n" "\n" "\n" - "\n" + "\n" "\n" "\n" "\n" @@ -473,9 +442,9 @@ WaxBump::get_filter_text (Inkscape::Extension::Extension * ext) "\n" "\n", simplifyImage.str().c_str(), background.str().c_str(), bgopacity.str().c_str(), red.str().c_str(), green.str().c_str(), blue.str().c_str(), crop.str().c_str(), - floodRed.str().c_str(), floodGreen.str().c_str(), floodBlue.str().c_str(), floodAlpha.str().c_str(), + imageColor.toString(false).c_str(), imageColor.getOpacity(), revert.str().c_str(), simplifyBump.str().c_str(), - lightRed.str().c_str(), lightGreen.str().c_str(), lightBlue.str().c_str(), + lightingColor.toString(false).c_str(), lightness.str().c_str(), height.str().c_str(), precision.str().c_str(), distantElevation.str().c_str(), distantAzimuth.str().c_str(), lightingblend.str().c_str(), transparency.str().c_str(), highlightblend.str().c_str() ); diff --git a/src/extension/internal/filter/color.h b/src/extension/internal/filter/color.h index 099ceac758cac18d33b5207d2f9911c1001566ee..44bd0f28d455d1d48cd5e36aad2809eb02e801db 100644 --- a/src/extension/internal/filter/color.h +++ b/src/extension/internal/filter/color.h @@ -197,10 +197,6 @@ ChannelPaint::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream blue; std::ostringstream alpha; std::ostringstream invert; - std::ostringstream floodRed; - std::ostringstream floodGreen; - std::ostringstream floodBlue; - std::ostringstream floodAlpha; saturation << ext->get_param_float("saturation"); red << ext->get_param_float("red"); @@ -208,11 +204,7 @@ ChannelPaint::get_filter_text (Inkscape::Extension::Extension * ext) blue << ext->get_param_float("blue"); alpha << ext->get_param_float("alpha"); - guint32 color = ext->get_param_color("color"); - floodRed << ((color >> 24) & 0xff); - floodGreen << ((color >> 16) & 0xff); - floodBlue << ((color >> 8) & 0xff); - floodAlpha << (color & 0xff) / 255.0F; + auto flood = ext->get_param_color("color"); if (ext->get_param_bool("invert")) { invert << "in"; @@ -225,7 +217,7 @@ ChannelPaint::get_filter_text (Inkscape::Extension::Extension * ext) "\n" "\n" "\n" - "\n" + "\n" "\n" "\n" "\n" @@ -233,8 +225,8 @@ ChannelPaint::get_filter_text (Inkscape::Extension::Extension * ext) "\n" "\n" "\n", saturation.str().c_str(), red.str().c_str(), green.str().c_str(), - blue.str().c_str(), alpha.str().c_str(), floodRed.str().c_str(), - floodGreen.str().c_str(), floodBlue.str().c_str(), floodAlpha.str().c_str(), + blue.str().c_str(), alpha.str().c_str(), + flood.toString(false).c_str(), flood.getOpacity(), invert.str().c_str() ); // clang-format on @@ -454,22 +446,13 @@ Colorize::get_filter_text (Inkscape::Extension::Extension * ext) { if (_filter != nullptr) g_free((void *)_filter); - std::ostringstream a; - std::ostringstream r; - std::ostringstream g; - std::ostringstream b; std::ostringstream hlight; std::ostringstream nlight; std::ostringstream duotone; std::ostringstream blend1; std::ostringstream blend2; - guint32 color = ext->get_param_color("color"); - r << ((color >> 24) & 0xff); - g << ((color >> 16) & 0xff); - b << ((color >> 8) & 0xff); - a << (color & 0xff) / 255.0F; - + auto color = ext->get_param_color("color"); hlight << ext->get_param_float("hlight"); nlight << ext->get_param_float("nlight"); blend1 << ext->get_param_optiongroup("blend1"); @@ -485,13 +468,13 @@ Colorize::get_filter_text (Inkscape::Extension::Extension * ext) "\n" "\n" "\n" - "\n" + "\n" "\n" "\n" "\n" "\n" "\n", hlight.str().c_str(), nlight.str().c_str(), duotone.str().c_str(), - a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), + color.getOpacity(), color.toString(false).c_str(), blend1.str().c_str(), blend2.str().c_str() ); // clang-format on @@ -647,67 +630,51 @@ Duochrome::get_filter_text (Inkscape::Extension::Extension * ext) { if (_filter != nullptr) g_free((void *)_filter); - std::ostringstream a1; - std::ostringstream r1; - std::ostringstream g1; - std::ostringstream b1; - std::ostringstream a2; - std::ostringstream r2; - std::ostringstream g2; - std::ostringstream b2; + bool swapa = false; std::ostringstream fluo; std::ostringstream swap1; std::ostringstream swap2; - guint32 color1 = ext->get_param_color("color1"); - guint32 color2 = ext->get_param_color("color2"); + auto color1 = ext->get_param_color("color1"); + auto color2 = ext->get_param_color("color2"); double fluorescence = ext->get_param_float("fluo"); const gchar *swaptype = ext->get_param_optiongroup("swap"); - r1 << ((color1 >> 24) & 0xff); - g1 << ((color1 >> 16) & 0xff); - b1 << ((color1 >> 8) & 0xff); - r2 << ((color2 >> 24) & 0xff); - g2 << ((color2 >> 16) & 0xff); - b2 << ((color2 >> 8) & 0xff); fluo << fluorescence; if ((g_ascii_strcasecmp("full", swaptype) == 0)) { swap1 << "in"; swap2 << "out"; - a1 << (color1 & 0xff) / 255.0F; - a2 << (color2 & 0xff) / 255.0F; } else if ((g_ascii_strcasecmp("color", swaptype) == 0)) { swap1 << "in"; swap2 << "out"; - a1 << (color2 & 0xff) / 255.0F; - a2 << (color1 & 0xff) / 255.0F; + swapa = true; } else if ((g_ascii_strcasecmp("alpha", swaptype) == 0)) { swap1 << "out"; swap2 << "in"; - a1 << (color2 & 0xff) / 255.0F; - a2 << (color1 & 0xff) / 255.0F; + swapa = true; } else { swap1 << "out"; swap2 << "in"; - a1 << (color1 & 0xff) / 255.0F; - a2 << (color2 & 0xff) / 255.0F; } + double a1 = swapa ? color2.getOpacity() : color1.getOpacity(); + double a2 = swapa ? color1.getOpacity() : color2.getOpacity(); + // clang-format off _filter = g_strdup_printf( "\n" "\n" - "\n" + "\n" "\n" - "\n" + "\n" "\n" "\n" "\n" "\n" "\n" "\n" - "\n", a1.str().c_str(), r1.str().c_str(), g1.str().c_str(), b1.str().c_str(), swap1.str().c_str(), - a2.str().c_str(), r2.str().c_str(), g2.str().c_str(), b2.str().c_str(), swap2.str().c_str(), + "\n", a1, color1.toString(false).c_str(), swap1.str().c_str(), + a2, color2.toString(false).c_str(), swap2.str().c_str(), fluo.str().c_str() ); // clang-format on @@ -1362,11 +1329,6 @@ NudgeRGB::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream bx; std::ostringstream by; - std::ostringstream a; - std::ostringstream r; - std::ostringstream g; - std::ostringstream b; - rx << ext->get_param_float("rx"); ry << ext->get_param_float("ry"); gx << ext->get_param_float("gx"); @@ -1374,16 +1336,12 @@ NudgeRGB::get_filter_text (Inkscape::Extension::Extension * ext) bx << ext->get_param_float("bx"); by << ext->get_param_float("by"); - guint32 color = ext->get_param_color("color"); - r << ((color >> 24) & 0xff); - g << ((color >> 16) & 0xff); - b << ((color >> 8) & 0xff); - a << (color & 0xff) / 255.0F; + auto color = ext->get_param_color("color"); // clang-format off _filter = g_strdup_printf( "\n" - "\n" + "\n" "\n" "\n" "\n" @@ -1393,7 +1351,7 @@ NudgeRGB::get_filter_text (Inkscape::Extension::Extension * ext) "\n" "\n" "\n" - "\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), + "\n", color.getOpacity(), color.toString(false).c_str(), rx.str().c_str(), ry.str().c_str(), gx.str().c_str(), gy.str().c_str(), bx.str().c_str(), by.str().c_str() ); @@ -1478,11 +1436,6 @@ NudgeCMY::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream yx; std::ostringstream yy; - std::ostringstream a; - std::ostringstream r; - std::ostringstream g; - std::ostringstream b; - cx << ext->get_param_float("cx"); cy << ext->get_param_float("cy"); mx << ext->get_param_float("mx"); @@ -1490,16 +1443,12 @@ NudgeCMY::get_filter_text (Inkscape::Extension::Extension * ext) yx << ext->get_param_float("yx"); yy << ext->get_param_float("yy"); - guint32 color = ext->get_param_color("color"); - r << ((color >> 24) & 0xff); - g << ((color >> 16) & 0xff); - b << ((color >> 8) & 0xff); - a << (color & 0xff) / 255.0F; + auto color = ext->get_param_color("color"); // clang-format off _filter = g_strdup_printf( "\n" - "\n" + "\n" "\n" "\n" "\n" @@ -1509,7 +1458,7 @@ NudgeCMY::get_filter_text (Inkscape::Extension::Extension * ext) "\n" "\n" "\n" - "\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), + "\n", color.getOpacity(), color.toString(false).c_str(), cx.str().c_str(), cy.str().c_str(), mx.str().c_str(), my.str().c_str(), yx.str().c_str(), yy.str().c_str() ); @@ -1670,27 +1619,19 @@ SimpleBlend::get_filter_text (Inkscape::Extension::Extension * ext) { if (_filter != nullptr) g_free((void *)_filter); - std::ostringstream a; - std::ostringstream r; - std::ostringstream g; - std::ostringstream b; std::ostringstream blend; - guint32 color = ext->get_param_color("color"); - r << ((color >> 24) & 0xff); - g << ((color >> 16) & 0xff); - b << ((color >> 8) & 0xff); - a << (color & 0xff) / 255.0F; + auto color = ext->get_param_color("color"); blend << ext->get_param_optiongroup("blendmode"); // clang-format off _filter = g_strdup_printf( "\n" - "\n" + "\n" "\n" "\n" - "\n", r.str().c_str(), g.str().c_str(), b.str().c_str(), - a.str().c_str(), blend.str().c_str()); + "\n", color.toString(false).c_str(), + color.getOpacity(), blend.str().c_str()); // clang-format on return _filter; @@ -1864,10 +1805,6 @@ Tritone::get_filter_text (Inkscape::Extension::Extension * ext) if (_filter != nullptr) g_free((void *)_filter); std::ostringstream dist; - std::ostringstream a; - std::ostringstream r; - std::ostringstream g; - std::ostringstream b; std::ostringstream globalblend; std::ostringstream glow; std::ostringstream glowblend; @@ -1878,11 +1815,7 @@ Tritone::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream c2in2; std::ostringstream b6in2; - guint32 color = ext->get_param_color("color"); - r << ((color >> 24) & 0xff); - g << ((color >> 16) & 0xff); - b << ((color >> 8) & 0xff); - a << (color & 0xff) / 255.0F; + auto color = ext->get_param_color("color"); globalblend << ext->get_param_optiongroup("globalblend"); dist << ext->get_param_int("dist"); glow << ext->get_param_float("glow"); @@ -1939,14 +1872,14 @@ Tritone::get_filter_text (Inkscape::Extension::Extension * ext) "\n" "\n" "\n" - "\n" + "\n" "\n" "\n" "\n" "\n" "\n" "\n", dist.str().c_str(), globalblend.str().c_str(), - a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), + color.getOpacity(), color.toString(false).c_str(), c1in2.str().c_str(), glow.str().c_str(), b6in2.str().c_str(), glowblend.str().c_str(), c2in.str().c_str(), c2in2.str().c_str(), llight.str().c_str(), glight.str().c_str() ); // clang-format on diff --git a/src/extension/internal/filter/morphology.h b/src/extension/internal/filter/morphology.h index 5701d2c075af19240badd8c85bab549c32fe91cd..6294ee046def253ce00ad8ad508ee1476be47d18 100644 --- a/src/extension/internal/filter/morphology.h +++ b/src/extension/internal/filter/morphology.h @@ -229,10 +229,6 @@ Outline::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream dilat2; std::ostringstream erosion2; std::ostringstream antialias; - std::ostringstream r; - std::ostringstream g; - std::ostringstream b; - std::ostringstream a; std::ostringstream fopacity; std::ostringstream sopacity; std::ostringstream smooth; @@ -252,11 +248,7 @@ Outline::get_filter_text (Inkscape::Extension::Extension * ext) dilat2 << ext->get_param_float("dilat2"); erosion2 << (- ext->get_param_float("erosion2")); antialias << ext->get_param_float("antialias"); - guint32 color = ext->get_param_color("color"); - r << ((color >> 24) & 0xff); - g << ((color >> 16) & 0xff); - b << ((color >> 8) & 0xff); - a << (color & 0xff) / 255.0F; + auto color = ext->get_param_color("color"); fopacity << ext->get_param_float("fopacity"); sopacity << ext->get_param_float("sopacity"); @@ -310,7 +302,7 @@ Outline::get_filter_text (Inkscape::Extension::Extension * ext) "\n" "\n" "\n" - "\n" + "\n" "\n" "\n" "\n" @@ -318,7 +310,7 @@ Outline::get_filter_text (Inkscape::Extension::Extension * ext) dilat1.str().c_str(), erosion1.str().c_str(), width2.str().c_str(), c2in.str().c_str(), c2op.str().c_str(), dilat2.str().c_str(), erosion2.str().c_str(), antialias.str().c_str(), smooth.str().c_str(), - a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), + color.getOpacity(), color.toString(false).c_str(), c4in.str().c_str(), fopacity.str().c_str(), sopacity.str().c_str() ); // clang-format on diff --git a/src/extension/internal/filter/overlays.h b/src/extension/internal/filter/overlays.h index 74a703c8f5f4c864150db252eb758be0efc3fd14..1f6b49e604da9c638ec07b8069b5883b63baf344 100644 --- a/src/extension/internal/filter/overlays.h +++ b/src/extension/internal/filter/overlays.h @@ -102,10 +102,6 @@ NoiseFill::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream variation; std::ostringstream dilat; std::ostringstream erosion; - std::ostringstream r; - std::ostringstream g; - std::ostringstream b; - std::ostringstream a; std::ostringstream inverted; type << ext->get_param_optiongroup("type"); @@ -115,11 +111,7 @@ NoiseFill::get_filter_text (Inkscape::Extension::Extension * ext) variation << ext->get_param_int("variation"); dilat << ext->get_param_float("dilat"); erosion << (- ext->get_param_float("erosion")); - guint32 color = ext->get_param_color("color"); - r << ((color >> 24) & 0xff); - g << ((color >> 16) & 0xff); - b << ((color >> 8) & 0xff); - a << (color & 0xff) / 255.0F; + auto color = ext->get_param_color("color"); if (ext->get_param_bool("inverted")) inverted << "out"; else @@ -130,13 +122,14 @@ NoiseFill::get_filter_text (Inkscape::Extension::Extension * ext) "\n" "\n" "\n" - "\n" + "\n" "\n" "\n" "\n" "\n" "\n" - "\n", type.str().c_str(), hfreq.str().c_str(), vfreq.str().c_str(), complexity.str().c_str(), variation.str().c_str(), inverted.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str()); + "\n", type.str().c_str(), hfreq.str().c_str(), vfreq.str().c_str(), complexity.str().c_str(), variation.str().c_str(), inverted.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), + color.getOpacity(), color.toString(false).c_str()); return _filter; }; /* NoiseFill filter */ diff --git a/src/extension/internal/filter/paint.h b/src/extension/internal/filter/paint.h index 8f4eda31e5279098f94e53234fbad76ed57e9590..e12d75465b0d77deee75e2f803b0aa385764d7f3 100644 --- a/src/extension/internal/filter/paint.h +++ b/src/extension/internal/filter/paint.h @@ -396,15 +396,7 @@ Drawing::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream blur; std::ostringstream bdilat; std::ostringstream berosion; - std::ostringstream strokea; - std::ostringstream stroker; - std::ostringstream strokeg; - std::ostringstream strokeb; std::ostringstream ios; - std::ostringstream filla; - std::ostringstream fillr; - std::ostringstream fillg; - std::ostringstream fillb; std::ostringstream iof; simply << ext->get_param_float("simply"); @@ -423,21 +415,13 @@ Drawing::get_filter_text (Inkscape::Extension::Extension * ext) bdilat << ext->get_param_float("bdilat"); berosion << (- ext->get_param_float("berosion")); - guint32 fcolor = ext->get_param_color("fcolor"); - fillr << ((fcolor >> 24) & 0xff); - fillg << ((fcolor >> 16) & 0xff); - fillb << ((fcolor >> 8) & 0xff); - filla << (fcolor & 0xff) / 255.0F; + auto fcolor = ext->get_param_color("fcolor"); if (ext->get_param_bool("iof")) iof << "SourceGraphic"; else iof << "flood3"; - guint32 scolor = ext->get_param_color("scolor"); - stroker << ((scolor >> 24) & 0xff); - strokeg << ((scolor >> 16) & 0xff); - strokeb << ((scolor >> 8) & 0xff); - strokea << (scolor & 0xff) / 255.0F; + auto scolor = ext->get_param_color("scolor"); if (ext->get_param_bool("ios")) ios << "SourceGraphic"; else @@ -462,20 +446,20 @@ Drawing::get_filter_text (Inkscape::Extension::Extension * ext) "\n" "\n" "\n" - "\n" + "\n" "\n" - "\n" + "\n" "\n" - "\n" + "\n" "\n" - "\n" + "\n" "\n" "\n" "\n" "\n" "\n" "\n" - "\n", simply.str().c_str(), clean.str().c_str(), erase.str().c_str(), smooth.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), blur.str().c_str(), bdilat.str().c_str(), berosion.str().c_str(), stroker.str().c_str(), strokeg.str().c_str(), strokeb.str().c_str(), ios.str().c_str(), strokea.str().c_str(), offset.str().c_str(), offset.str().c_str(), fillr.str().c_str(), fillg.str().c_str(), fillb.str().c_str(), iof.str().c_str(), filla.str().c_str(), translucent.str().c_str()); + "\n", simply.str().c_str(), clean.str().c_str(), erase.str().c_str(), smooth.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), blur.str().c_str(), bdilat.str().c_str(), berosion.str().c_str(), scolor.toString(false).c_str(), ios.str().c_str(), scolor.getOpacity(), offset.str().c_str(), offset.str().c_str(), fcolor.toString(false).c_str(), iof.str().c_str(), fcolor.getOpacity(), translucent.str().c_str()); // clang-format on return _filter; @@ -771,14 +755,6 @@ PointEngraving::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream grain; std::ostringstream erase; std::ostringstream blur; - std::ostringstream r; - std::ostringstream g; - std::ostringstream b; - std::ostringstream a; - std::ostringstream br; - std::ostringstream bg; - std::ostringstream bb; - std::ostringstream ba; std::ostringstream iof; std::ostringstream iop; @@ -794,17 +770,8 @@ PointEngraving::get_filter_text (Inkscape::Extension::Extension * ext) erase << ext->get_param_float("erase"); blur << ext->get_param_float("blur"); - guint32 fcolor = ext->get_param_color("fcolor"); - r << ((fcolor >> 24) & 0xff); - g << ((fcolor >> 16) & 0xff); - b << ((fcolor >> 8) & 0xff); - a << (fcolor & 0xff) / 255.0F; - - guint32 pcolor = ext->get_param_color("pcolor"); - br << ((pcolor >> 24) & 0xff); - bg << ((pcolor >> 16) & 0xff); - bb << ((pcolor >> 8) & 0xff); - ba << (pcolor & 0xff) / 255.0F; + auto fcolor = ext->get_param_color("fcolor"); + auto pcolor = ext->get_param_color("pcolor"); if (ext->get_param_bool("iof")) iof << "SourceGraphic"; @@ -826,18 +793,18 @@ PointEngraving::get_filter_text (Inkscape::Extension::Extension * ext) "\n" "\n" "\n" - "\n" + "\n" "\n" - "\n" + "\n" "\n" - "\n" + "\n" "\n" "\n", reduction.str().c_str(), blend.str().c_str(), type.str().c_str(), hfreq.str().c_str(), vfreq.str().c_str(), complexity.str().c_str(), variation.str().c_str(), lightness.str().c_str(), grain.str().c_str(), erase.str().c_str(), blur.str().c_str(), - br.str().c_str(), bg.str().c_str(), bb.str().c_str(), ba.str().c_str(), iop.str().c_str(), - r.str().c_str(), g.str().c_str(), b.str().c_str(), a.str().c_str(), iof.str().c_str(), - a.str().c_str(), ba.str().c_str() ); + pcolor.toString(false).c_str(), pcolor.getOpacity(), iop.str().c_str(), + fcolor.toString(false).c_str(), fcolor.getOpacity(), iof.str().c_str(), + fcolor.getOpacity(), pcolor.getOpacity()); // clang-format on return _filter; diff --git a/src/extension/internal/filter/shadows.h b/src/extension/internal/filter/shadows.h index a41654c934e89da9cdcc84c2d8a0215f9faec412..1d5510725198f0a73605e0045c72c0e7fbad6929 100644 --- a/src/extension/internal/filter/shadows.h +++ b/src/extension/internal/filter/shadows.h @@ -104,14 +104,8 @@ gchar const *ColorizableDropShadow::get_filter_text(Inkscape::Extension::Extensi // Style parameters + auto color = ext->get_param_color("color"); float blur_std = ext->get_param_float("blur"); - - guint32 color = ext->get_param_color("color"); - float flood_a = (color & 0xff) / 255.0F; - int flood_r = ((color >> 24) & 0xff); - int flood_g = ((color >> 16) & 0xff); - int flood_b = ((color >> 8) & 0xff); - float offset_x = ext->get_param_float("xoffset"); float offset_y = ext->get_param_float("yoffset"); @@ -168,14 +162,14 @@ gchar const *ColorizableDropShadow::get_filter_text(Inkscape::Extension::Extensi auto old = std::locale::global(std::locale::classic()); _filter = g_strdup_printf( "\n" - "\n" + "\n" "\n" "\n" "\n" "\n" "\n", - flood_a, flood_r, flood_g, flood_b, + color.getOpacity(), color.toString(false).c_str(), blur_std, offset_x, offset_y, diff --git a/src/extension/internal/filter/transparency.h b/src/extension/internal/filter/transparency.h index 25e4d82228fbd7c2d99c9be5ab03839dfcb12392..776cf0fe996c40e07cccec7341e83bea83005359 100644 --- a/src/extension/internal/filter/transparency.h +++ b/src/extension/internal/filter/transparency.h @@ -381,18 +381,10 @@ Silhouette::get_filter_text (Inkscape::Extension::Extension * ext) { if (_filter != nullptr) g_free((void *)_filter); - std::ostringstream a; - std::ostringstream r; - std::ostringstream g; - std::ostringstream b; std::ostringstream cutout; std::ostringstream blur; - guint32 color = ext->get_param_color("color"); - r << ((color >> 24) & 0xff); - g << ((color >> 16) & 0xff); - b << ((color >> 8) & 0xff); - a << (color & 0xff) / 255.0F; + auto color = ext->get_param_color("color"); if (ext->get_param_bool("cutout")) cutout << "out"; else @@ -402,10 +394,10 @@ Silhouette::get_filter_text (Inkscape::Extension::Extension * ext) // clang-format off _filter = g_strdup_printf( "\n" - "\n" + "\n" "\n" "\n" - "\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), cutout.str().c_str(), blur.str().c_str()); + "\n", color.getOpacity(), color.toString(false).c_str(), cutout.str().c_str(), blur.str().c_str()); // clang-format on return _filter; diff --git a/src/extension/internal/gimpgrad.cpp b/src/extension/internal/gimpgrad.cpp index 036b0c7b4d77aacb416eed2ba5574242edc4a7ad..4d42faf1dc18ddb48f86060fac3f04ad8f119e6d 100644 --- a/src/extension/internal/gimpgrad.cpp +++ b/src/extension/internal/gimpgrad.cpp @@ -13,18 +13,20 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ -#include #include "io/sys.h" #include "extension/system.h" #include "svg/css-ostringstream.h" -#include "svg/svg-color.h" +#include "colors/color.h" +#include "colors/manager.h" #include "gimpgrad.h" #include "streq.h" #include "strneq.h" #include "document.h" #include "extension/extension.h" +using namespace Inkscape::Colors; + namespace Inkscape::Extension::Internal { bool GimpGrad::load(Inkscape::Extension::Extension *) @@ -50,24 +52,22 @@ static void append_css_num(Glib::ustring &str, double const num) \param location Where the stop is placed in the gradient \return The text that is the stop. Full SVG containing the element. - This function encapsulates all of the translation of the ColorRGBA + This function encapsulates all of the translation of the Color and the location into the gradient. It is really pretty simple except - that the ColorRGBA is in floats that are 0 to 1 and the SVG wants + that the Color is in floats that are 0 to 1 and the SVG wants hex values from 0 to 255 for color. Otherwise mostly this is just turning the values into strings and returning it. */ -static Glib::ustring stop_svg(ColorRGBA const in_color, double const location) +static Glib::ustring stop_svg(Color const &in_color, double const location) { Glib::ustring ret("toString(false); ret += '"'; if (in_color[3] != 1) { ret += " stop-opacity=\""; - append_css_num(ret, in_color[3]); + append_css_num(ret, in_color.getOpacity()); ret += '"'; } ret += " offset=\""; @@ -161,7 +161,7 @@ std::unique_ptr GimpGrad::open(Inkscape::Extension::Input *, char co goto error; } - ColorRGBA prev_color(-1.0, -1.0, -1.0, -1.0); + Color prev_color(0x0); Glib::ustring outsvg("\n"); long n_segs_found = 0; double prev_right = 0.0; @@ -194,9 +194,9 @@ std::unique_ptr GimpGrad::open(Inkscape::Extension::Input *, char co goto error; } - ColorRGBA const leftcolor(dbls[3], dbls[4], dbls[5], dbls[6]); - ColorRGBA const rightcolor(dbls[7], dbls[8], dbls[9], dbls[10]); g_assert(11 == G_N_ELEMENTS(dbls)); + auto leftcolor = Color(Space::Type::RGB, {dbls[3], dbls[4], dbls[5], dbls[6]}); + auto rightcolor = Color(Space::Type::RGB, {dbls[7], dbls[8], dbls[9], dbls[10]}); /* Interpolation enums: curve shape and colour space. */ { @@ -215,7 +215,7 @@ std::unique_ptr GimpGrad::open(Inkscape::Extension::Input *, char co outsvg += stop_svg(leftcolor, left); } if (fabs(middle - .5 * (left + right)) > 1e-4) { - outsvg += stop_svg(leftcolor.average(rightcolor), middle); + outsvg += stop_svg(leftcolor.averaged(rightcolor), middle); } outsvg += stop_svg(rightcolor, right); diff --git a/src/extension/internal/latex-pstricks.cpp b/src/extension/internal/latex-pstricks.cpp index 3654e952c64edc73723701a8a02f867b3450bb5d..3e42f97cd23e9b0d99052aa6c49b20c550718449 100644 --- a/src/extension/internal/latex-pstricks.cpp +++ b/src/extension/internal/latex-pstricks.cpp @@ -19,6 +19,7 @@ #include "util/units.h" #include "helper/geom-curves.h" +#include "colors/color.h" #include "extension/print.h" #include "extension/system.h" #include "inkscape-version.h" @@ -173,13 +174,12 @@ unsigned int PrintLatex::fill(Inkscape::Extension::Print * /*mod*/, if (style->fill.isColor()) { Inkscape::SVGOStringStream os; - float rgb[3]; float fill_opacity; os.setf(std::ios::fixed); fill_opacity=SP_SCALE24_TO_FLOAT(style->fill_opacity.value); - style->fill.value.color.get_rgb_floatv(rgb); + auto rgb = *style->fill.getColor().converted(Colors::Space::Type::RGB); os << "{\n\\newrgbcolor{curcolor}{" << rgb[0] << " " << rgb[1] << " " << rgb[2] << "}\n"; os << "\\pscustom[linestyle=none,fillstyle=solid,fillcolor=curcolor"; if (fill_opacity!=1.0) { @@ -208,14 +208,12 @@ unsigned int PrintLatex::stroke(Inkscape::Extension::Print * /*mod*/, if (style->stroke.isColor()) { Inkscape::SVGOStringStream os; - float rgb[3]; - float stroke_opacity; Geom::Affine tr_stack = m_tr_stack.top(); double const scale = tr_stack.descrim(); os.setf(std::ios::fixed); - stroke_opacity=SP_SCALE24_TO_FLOAT(style->stroke_opacity.value); - style->stroke.value.color.get_rgb_floatv(rgb); + double stroke_opacity = style->stroke_opacity; + auto rgb = *style->stroke.getColor().converted(Colors::Space::Type::RGB); os << "{\n\\newrgbcolor{curcolor}{" << rgb[0] << " " << rgb[1] << " " << rgb[2] << "}\n"; os << "\\pscustom[linewidth=" << style->stroke_width.computed*scale<< ",linecolor=curcolor"; diff --git a/src/extension/internal/latex-text-renderer.cpp b/src/extension/internal/latex-text-renderer.cpp index 29aa127114ff3e075ebd6f57e2bcd18c24084795..aba66c656a7188b6dca1a362310b0747a02b6028 100644 --- a/src/extension/internal/latex-text-renderer.cpp +++ b/src/extension/internal/latex-text-renderer.cpp @@ -28,6 +28,8 @@ #include <2geom/transforms.h> #include <2geom/rect.h> +#include "colors/color.h" +#include "colors/manager.h" #include "object/sp-item.h" #include "object/sp-item-group.h" #include "object/sp-root.h" @@ -281,25 +283,15 @@ void LaTeXTextRenderer::sp_text_render(SPText *textobj) g_warning("LaTeXTextRenderer::sp_text_render: baselineAnchorPoint unset, text position will be wrong. Please report the issue."); } - // determine color and transparency (for now, use rgb color model as it is most native to Inkscape) - bool has_color = false; // if the item has no color set, don't force black color - bool has_transparency = false; - // TODO: how to handle ICC colors? - // give priority to fill color - guint32 rgba = 0; - float opacity = SP_SCALE24_TO_FLOAT(style->opacity.value); + Inkscape::Colors::Color color(0x0); if (style->fill.set && style->fill.isColor()) { - has_color = true; - rgba = style->fill.value.color.toRGBA32(1.); - opacity *= SP_SCALE24_TO_FLOAT(style->fill_opacity.value); + color = style->fill.getColor(); + color.addOpacity(style->fill_opacity); } else if (style->stroke.set && style->stroke.isColor()) { - has_color = true; - rgba = style->stroke.value.color.toRGBA32(1.); - opacity *= SP_SCALE24_TO_FLOAT(style->stroke_opacity.value); - } - if (opacity < 1.0) { - has_transparency = true; + color = style->stroke.getColor(); + color.addOpacity(style->stroke_opacity); } + color.addOpacity(style->opacity); // get rotation Geom::Affine i2doc = textobj->i2doc_affine(); @@ -322,11 +314,10 @@ void LaTeXTextRenderer::sp_text_render(SPText *textobj) os.setf(std::ios::fixed); // don't use scientific notation os << " \\put(" << anchor[Geom::X] << "," << anchor[Geom::Y] << "){"; - if (has_color) { - os << "\\color[rgb]{" << SP_RGBA32_R_F(rgba) << "," << SP_RGBA32_G_F(rgba) << "," << SP_RGBA32_B_F(rgba) << "}"; - } - if (_pdflatex && has_transparency) { - os << "\\transparent{" << opacity << "}"; + color.convert(Colors::Space::Type::RGB); + os << "\\color[rgb]{" << color[0] << "," << color[1] << "," << color[2] << "}"; + if (_pdflatex && color.getOpacity() < 1.0) { + os << "\\transparent{" << color.getOpacity() << "}"; } if (has_rotation) { os << "\\rotatebox{" << degrees << "}{"; @@ -475,11 +466,11 @@ Flowing in rectangle is possible, not in arb shape. float opacity = SP_SCALE24_TO_FLOAT(style->opacity.value); if (style->fill.set && style->fill.isColor()) { has_color = true; - rgba = style->fill.value.color.toRGBA32(1.); + rgba = style->fill.getColor().toRGBA(); opacity *= SP_SCALE24_TO_FLOAT(style->fill_opacity.value); } else if (style->stroke.set && style->stroke.isColor()) { has_color = true; - rgba = style->stroke.value.color.toRGBA32(1.); + rgba = style->stroke.getColor().toRGBA(); opacity *= SP_SCALE24_TO_FLOAT(style->stroke_opacity.value); } if (opacity < 1.0) { diff --git a/src/extension/internal/metafile-inout.h b/src/extension/internal/metafile-inout.h index c742a64dfc281a9ae800bc9b8790be7a31db525a..c3494e512cdb821a5f84dfc4b0e0a3ccf50ea355 100644 --- a/src/extension/internal/metafile-inout.h +++ b/src/extension/internal/metafile-inout.h @@ -25,6 +25,16 @@ #include <2geom/pathvector.h> #include "extension/implementation/implementation.h" +#include "colors/utils.h" + +constexpr uint32_t U_RGB_COMPOSE(U_COLORREF c) +{ + return SP_RGBA32_U_COMPOSE(U_RGBAGetR(c), U_RGBAGetG(c), U_RGBAGetB(c), 0xff); +} +constexpr uint32_t U_RGBA_COMPOSE(U_COLORREF c) +{ + return SP_RGBA32_U_COMPOSE(U_RGBAGetR(c), U_RGBAGetG(c), U_RGBAGetB(c), U_RGBAGetA(c)); +} class SPObject; diff --git a/src/extension/internal/metafile-print.cpp b/src/extension/internal/metafile-print.cpp index 41775daeaca91b2ce42603756f792427a912b592..d9471cb12b0c26ca403158ea00b95958d31ea886 100644 --- a/src/extension/internal/metafile-print.cpp +++ b/src/extension/internal/metafile-print.cpp @@ -31,6 +31,14 @@ namespace Inkscape { namespace Extension { namespace Internal { +U_COLORREF toColorRef(std::optional color) +{ + // Make sure it's RGB first, if wmf can support other color formats, this can be changed. + auto c = color ? *color->converted(Colors::Space::Type::RGB) : Colors::Color(0x000000ff); + return U_RGBA(255 * c[0], 255 * c[1], 255 * c[2], 255 * c[3]); +} + + PrintMetafile::~PrintMetafile() { #ifndef G_OS_WIN32 @@ -146,20 +154,14 @@ U_COLORREF PrintMetafile::avg_stop_color(SPGradient *gr) U_COLORREF cr; int last = gr->vector.stops.size() - 1; if (last >= 1) { - float rgbs[3]; - float rgbe[3]; - float ops, ope; - - ops = gr->vector.stops[0 ].opacity; - ope = gr->vector.stops[last].opacity; - gr->vector.stops[0 ].color.get_rgb_floatv(rgbs); - gr->vector.stops[last].color.get_rgb_floatv(rgbe); + auto rgbs = *gr->vector.stops[0 ].color->converted(Colors::Space::Type::RGB); + auto rgbe = *gr->vector.stops[last].color->converted(Colors::Space::Type::RGB); /* Replace opacity at start & stop with that fraction background color, then average those two for final color. */ cr = U_RGB( - 255 * ((opweight(rgbs[0], gv.rgb[0], ops) + opweight(rgbe[0], gv.rgb[0], ope)) / 2.0), - 255 * ((opweight(rgbs[1], gv.rgb[1], ops) + opweight(rgbe[1], gv.rgb[1], ope)) / 2.0), - 255 * ((opweight(rgbs[2], gv.rgb[2], ops) + opweight(rgbe[2], gv.rgb[2], ope)) / 2.0) + 255 * ((opweight(rgbs[0], gv.rgb[0], rgbs[3]) + opweight(rgbe[0], gv.rgb[0], rgbe[3])) / 2.0), + 255 * ((opweight(rgbs[1], gv.rgb[1], rgbs[3]) + opweight(rgbe[1], gv.rgb[1], rgbe[3])) / 2.0), + 255 * ((opweight(rgbs[2], gv.rgb[2], rgbs[3]) + opweight(rgbe[2], gv.rgb[2], rgbe[3])) / 2.0) ); } else { cr = U_RGB(0, 0, 0); // The default fill diff --git a/src/extension/internal/metafile-print.h b/src/extension/internal/metafile-print.h index 2266ba5b95e0b21fdb2f8464fa925dafbba52493..f7f691797f20cb608563c6319c49e2a61e4e4d0f 100644 --- a/src/extension/internal/metafile-print.h +++ b/src/extension/internal/metafile-print.h @@ -20,6 +20,7 @@ #include <2geom/affine.h> #include <2geom/pathvector.h> +#include "colors/color.h" #include "extension/implementation/implementation.h" #include "style-enums.h" // Fill rule @@ -34,6 +35,7 @@ class Pixbuf; namespace Extension { namespace Internal { +U_COLORREF toColorRef(std::optional color); enum MFDrawMode {DRAW_PAINT, DRAW_PATTERN, DRAW_IMAGE, DRAW_LINEAR_GRADIENT, DRAW_RADIAL_GRADIENT}; struct FontfixParams { @@ -73,7 +75,7 @@ protected: uint32_t htextalignment; uint32_t hpolyfillmode; // used to minimize redundant records that set this - float htextcolor_rgb[3]; // used to minimize redundant records that set this + std::optional htextcolor_rgb; // used to minimize redundant records that set this std::stack m_tr_stack; Geom::PathVector fill_pathv; diff --git a/src/extension/internal/odf.cpp b/src/extension/internal/odf.cpp index 5ed491e6445726a7ad134e5da327f434fc3312d2..26872b1de61735a0d3407b875b89de2fa267e7da 100644 --- a/src/extension/internal/odf.cpp +++ b/src/extension/internal/odf.cpp @@ -45,7 +45,6 @@ //# Inkscape includes #include "attributes.h" // for SPAttr -#include "color.h" // for SPColor #include "document.h" // for SPDocument #include "preferences.h" // for guint32 #include "style-internal.h" // for SPIPaint, SPIScale24, SPILe... @@ -1291,7 +1290,7 @@ bool OdfOutput::processStyle(SPItem *item, const Glib::ustring &id, const Glib:: // FILL if (style->fill.isColor()) { - guint32 fillCol = style->fill.value.color.toRGBA32( 0 ); + guint32 fillCol = style->fill.getColor().toRGBA(); char buf[16]; int r = (fillCol >> 24) & 0xff; int g = (fillCol >> 16) & 0xff; @@ -1316,7 +1315,7 @@ bool OdfOutput::processStyle(SPItem *item, const Glib::ustring &id, const Glib:: // STROKE if (style->stroke.isColor()) { - guint32 strokeCol = style->stroke.value.color.toRGBA32( 0 ); + guint32 strokeCol = style->stroke.getColor().toRGBA(); char buf[16]; int r = (strokeCol >> 24) & 0xff; int g = (strokeCol >> 16) & 0xff; @@ -1431,10 +1430,10 @@ bool OdfOutput::processGradient(SPItem *item, for (SPStop *stop = grvec->getFirstStop(); stop ; stop = stop->getNextStop()) { - unsigned long rgba = stop->get_rgba32(); - unsigned long rgb = (rgba >> 8) & 0xffffff; - double opacity = (static_cast(rgba & 0xff)) / 256.0; - GradientStop gs(rgb, opacity); + // TODO: Replace these with a color object to support more than RGB + auto color = stop->getColor().toRGBA(false); + auto opacity = stop->getColor().getOpacity(); + GradientStop gs(color, opacity); gi.stops.push_back(gs); } diff --git a/src/extension/internal/pdfinput/svg-builder.cpp b/src/extension/internal/pdfinput/svg-builder.cpp index f0a9c1ac696bb2323fe0c4c61e06ef6e364ac610..8a015a91bbacd847ca46287b9458baa51b933dba 100644 --- a/src/extension/internal/pdfinput/svg-builder.cpp +++ b/src/extension/internal/pdfinput/svg-builder.cpp @@ -29,16 +29,15 @@ #include "GfxState.h" #include "Page.h" #include "Stream.h" -#include "color.h" #include "document.h" #include "extract-uri.h" #include "pdf-parser.h" #include "pdf-utils.h" #include "png.h" #include "poppler-cairo-font-engine.h" -#include "profile-manager.h" -#include "color/cms-util.h" +#include "colors/cms/profile.h" +#include "colors/document-cms.h" #include "display/cairo-utils.h" #include "display/nr-filter-utils.h" #include "object/sp-defs.h" @@ -920,30 +919,23 @@ std::string SvgBuilder::_getColorProfile(cmsHPROFILE hp) if (_icc_profiles.find(hp) != _icc_profiles.end()) return _icc_profiles[hp]; - std::string name = get_color_profile_name(hp); + auto profile = Colors::CMS::Profile::create(hp); + std::string name = profile->getName(); // Find the named profile in the document (if already added) - if (_doc->getProfileManager().find(name.c_str())) + if (_doc->getDocumentCMS().getSpace(name)) return name; // Add the profile, we've never seen it before. - cmsUInt32Number len = 0; - cmsSaveProfileToMem(hp, nullptr, &len); - auto buf = (unsigned char *)malloc(len * sizeof(unsigned char)); - cmsSaveProfileToMem(hp, buf, &len); - Inkscape::XML::Node *icc_node = _xml_doc->createElement("svg:color-profile"); icc_node->setAttribute("inkscape:label", name); icc_node->setAttribute("name", name); - auto *base64String = g_base64_encode(buf, len); - auto icc_data = std::string("data:application/vnd.iccprofile;base64,") + base64String; - g_free(base64String); + auto icc_data = std::string("data:application/vnd.iccprofile;base64,") + profile->dumpBase64(); icc_node->setAttributeOrRemoveIfEmpty("xlink:href", icc_data); _doc->getDefs()->getRepr()->appendChild(icc_node); Inkscape::GC::release(icc_node); - free(buf); _icc_profiles[hp] = name; return name; } diff --git a/src/extension/internal/pov-out.cpp b/src/extension/internal/pov-out.cpp index 963a9d3dcf55781915aa3db2cf6a606c0f7b7679..9c8358324f73ef3ef3b762eab72f4295dbc7261e 100644 --- a/src/extension/internal/pov-out.cpp +++ b/src/extension/internal/pov-out.cpp @@ -290,25 +290,17 @@ bool PovOutput::doCurve(SPItem *item, const String &id) SPStyle *style = shape->style; /* fixme: Handle other fill types, even if this means translating gradients to a single flat colour. */ - if (style) - { - if (style->fill.isColor()) - { - // see color.h for how to parse SPColor - float rgb[3]; - style->fill.value.color.get_rgb_floatv(rgb); - double const dopacity = ( SP_SCALE24_TO_FLOAT(style->fill_opacity.value) - * effective_opacity(shape) ); - //gchar *str = g_strdup_printf("rgbf < %1.3f, %1.3f, %1.3f %1.3f>", - // rgb[0], rgb[1], rgb[2], 1.0 - dopacity); - String rgbf = "rgbf <"; - rgbf.append(dstr(rgb[0])); rgbf.append(", "); - rgbf.append(dstr(rgb[1])); rgbf.append(", "); - rgbf.append(dstr(rgb[2])); rgbf.append(", "); - rgbf.append(dstr(1.0 - dopacity)); rgbf.append(">"); - shapeInfo.color += rgbf; - } - } + if (style && style->fill.isColor()) { + auto rgba = *style->fill.getColor().converted(Colors::Space::Type::RGB); + rgba.addOpacity(style->fill_opacity); + rgba.addOpacity(effective_opacity(shape)); + String rgbf = "rgbf <"; + rgbf.append(dstr(rgba[0])); rgbf.append(", "); + rgbf.append(dstr(rgba[1])); rgbf.append(", "); + rgbf.append(dstr(rgba[2])); rgbf.append(", "); + rgbf.append(dstr(1.0 - rgba[3])); rgbf.append(">"); + shapeInfo.color += rgbf; + } povShapes.push_back(shapeInfo); //passed all tests. save the info diff --git a/src/extension/internal/wmf-inout.cpp b/src/extension/internal/wmf-inout.cpp index bc9b554266850e4b7d87fff7b4fc8e84e4b1b022..998daa421362a8506bc978ae493f3c47337e9729 100644 --- a/src/extension/internal/wmf-inout.cpp +++ b/src/extension/internal/wmf-inout.cpp @@ -680,10 +680,8 @@ Wmf::output_style(PWMF_CALLBACK_DATA d) SVGOStringStream tmp_style; char tmp[1024] = {0}; - float fill_rgb[3]; - d->dc[d->level].style.fill.value.color.get_rgb_floatv(fill_rgb); - float stroke_rgb[3]; - d->dc[d->level].style.stroke.value.color.get_rgb_floatv(stroke_rgb); + auto fill = d->dc[d->level].style.fill.getColor(); + auto stroke = d->dc[d->level].style.stroke.getColor(); // for U_WMR_BITBLT with no image, try to approximate some of these operations/ // Assume src color is "white" @@ -695,13 +693,13 @@ Wmf::output_style(PWMF_CALLBACK_DATA d) case U_BLACKNESS: case U_SRCERASE: case U_NOTSRCCOPY: - fill_rgb[0]=fill_rgb[1]=fill_rgb[2]=0.0; + fill.set("black"); break; case U_SRCCOPY: // treat all of these as white case U_NOTSRCERASE: case U_PATCOPY: case U_WHITENESS: - fill_rgb[0]=fill_rgb[1]=fill_rgb[2]=1.0; + fill.set("white"); break; case U_SRCPAINT: // use the existing color case U_SRCAND: @@ -720,19 +718,15 @@ Wmf::output_style(PWMF_CALLBACK_DATA d) // pen color through. switch(d->dwRop2){ case U_R2_BLACK: - fill_rgb[0] = fill_rgb[1] = fill_rgb[2] = 0.0; - stroke_rgb[0]= stroke_rgb[1]= stroke_rgb[2] = 0.0; + fill.set("black"); + stroke.set("black"); break; case U_R2_NOTMERGEPEN: case U_R2_MASKNOTPEN: break; case U_R2_NOTCOPYPEN: - fill_rgb[0] = 1.0 - fill_rgb[0]; - fill_rgb[1] = 1.0 - fill_rgb[1]; - fill_rgb[2] = 1.0 - fill_rgb[2]; - stroke_rgb[0] = 1.0 - stroke_rgb[0]; - stroke_rgb[1] = 1.0 - stroke_rgb[1]; - stroke_rgb[2] = 1.0 - stroke_rgb[2]; + fill.invert(); + stroke.invert(); break; case U_R2_MASKPENNOT: case U_R2_NOT: @@ -747,8 +741,8 @@ Wmf::output_style(PWMF_CALLBACK_DATA d) case U_R2_MERGEPEN: break; case U_R2_WHITE: - fill_rgb[0] = fill_rgb[1] = fill_rgb[2] = 1.0; - stroke_rgb[0]= stroke_rgb[1]= stroke_rgb[2] = 1.0; + fill.set("white"); + stroke.set("white"); break; default: break; @@ -773,14 +767,8 @@ Wmf::output_style(PWMF_CALLBACK_DATA d) break; case DRAW_PAINT: default: // <-- this should never happen, but just in case... - snprintf( - tmp, 1023, - "fill:#%02x%02x%02x;", - SP_COLOR_F_TO_U(fill_rgb[0]), - SP_COLOR_F_TO_U(fill_rgb[1]), - SP_COLOR_F_TO_U(fill_rgb[2]) - ); - tmp_style << tmp; + fill.convert(Colors::Space::Type::RGB); + tmp_style << "fill:" << fill.toString(false).c_str() << ";"; break; } snprintf( @@ -799,11 +787,7 @@ Wmf::output_style(PWMF_CALLBACK_DATA d) (d->dc[d->level].fill_mode == d->dc[d->level].stroke_mode) && ( (d->dc[d->level].fill_mode != DRAW_PAINT) || - ( - (fill_rgb[0]==stroke_rgb[0]) && - (fill_rgb[1]==stroke_rgb[1]) && - (fill_rgb[2]==stroke_rgb[2]) - ) + fill == stroke ) ){ d->dc[d->level].stroke_set = false; @@ -825,14 +809,8 @@ Wmf::output_style(PWMF_CALLBACK_DATA d) break; case DRAW_PAINT: default: // <-- this should never happen, but just in case... - snprintf( - tmp, 1023, - "stroke:#%02x%02x%02x;", - SP_COLOR_F_TO_U(stroke_rgb[0]), - SP_COLOR_F_TO_U(stroke_rgb[1]), - SP_COLOR_F_TO_U(stroke_rgb[2]) - ); - tmp_style << tmp; + stroke.convert(Colors::Space::Type::RGB); + tmp_style << "stroke:" << stroke.toString(false).c_str() << ";"; break; } if(d->dc[d->level].style.stroke_width.value){ @@ -1028,12 +1006,7 @@ Wmf::select_pen(PWMF_CALLBACK_DATA d, int index) d->level = cur_level; } d->dc[d->level].style.stroke_width.value = pen_width; - - double r, g, b; - r = SP_COLOR_U_TO_F( U_RGBAGetR(up.Color) ); - g = SP_COLOR_U_TO_F( U_RGBAGetG(up.Color) ); - b = SP_COLOR_U_TO_F( U_RGBAGetB(up.Color) ); - d->dc[d->level].style.stroke.value.color.set( r, g, b ); + d->dc[d->level].style.stroke.setColor(Colors::Color(U_RGB_COMPOSE(up.Color), false)); } @@ -1055,11 +1028,7 @@ Wmf::select_brush(PWMF_CALLBACK_DATA d, int index) (void) U_WMRCREATEBRUSHINDIRECT_get(record, &membrush); memcpy(&lb, membrush, U_SIZE_WLOGBRUSH); if(lb.Style == U_BS_SOLID){ - double r, g, b; - r = SP_COLOR_U_TO_F( U_RGBAGetR(lb.Color) ); - g = SP_COLOR_U_TO_F( U_RGBAGetG(lb.Color) ); - b = SP_COLOR_U_TO_F( U_RGBAGetB(lb.Color) ); - d->dc[d->level].style.fill.value.color.set( r, g, b ); + d->dc[d->level].style.fill.setColor(Colors::Color(U_RGB_COMPOSE(lb.Color))); d->dc[d->level].fill_mode = DRAW_PAINT; d->dc[d->level].fill_set = true; } @@ -1091,11 +1060,7 @@ Wmf::select_brush(PWMF_CALLBACK_DATA d, int index) tidx = add_bm16_image(d, Bm16, px); } if(tidx == U_WMR_INVALID){ // Problem with the image, for instance, an unsupported bitmap16 type - double r, g, b; - r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor)); - g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor)); - b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor)); - d->dc[d->level].style.fill.value.color.set( r, g, b ); + d->dc[d->level].style.fill.setColor(Colors::Color(U_RGB_COMPOSE(d->dc[d->level].textColor), false)); d->dc[d->level].fill_mode = DRAW_PAINT; } else { @@ -1116,11 +1081,7 @@ Wmf::select_brush(PWMF_CALLBACK_DATA d, int index) if(U_WMRCREATEPATTERNBRUSH_get(record, &Bm16h, &cbPx, &px)){ tidx = add_bm16_image(d, Bm16h, px); if(tidx == 0xFFFFFFFF){ // Problem with the image, for instance, an unsupported bitmap16 type - double r, g, b; - r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor)); - g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor)); - b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor)); - d->dc[d->level].style.fill.value.color.set( r, g, b ); + d->dc[d->level].style.fill.setColor(Colors::Color(U_RGB_COMPOSE(d->dc[d->level].textColor), false)); d->dc[d->level].fill_mode = DRAW_PAINT; } else { @@ -1222,7 +1183,7 @@ Wmf::delete_object(PWMF_CALLBACK_DATA d, int index) d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER; // U_PS_JOIN_MITER; d->dc[d->level].stroke_set = true; d->dc[d->level].style.stroke_width.value = 1.0; - d->dc[d->level].style.stroke.value.color.set( 0, 0, 0 ); + d->dc[d->level].style.stroke.setColor(Colors::Color(0x000000ff)); } else if(index == d->dc[d->level].active_brush){ d->dc[d->level].active_brush = -1; @@ -3111,7 +3072,7 @@ std::unique_ptr Wmf::open(Inkscape::Extension::Input *, char const * d.dc[0].style.stroke_linecap.computed = SP_STROKE_LINECAP_SQUARE; // U_PS_ENDCAP_SQUARE; d.dc[0].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER; // U_PS_JOIN_MITER; d.dc[0].style.stroke_width.value = 1.0; // will be reset to something reasonable once WMF drawing size is known - d.dc[0].style.stroke.value.color.set( 0, 0, 0 ); + d.dc[0].style.stroke.setColor(Colors::Color(0x000000ff)); d.dc[0].stroke_set = true; // Default brush is none - no fill. WMF files that do not specify a brush are unlikely to look very good! diff --git a/src/extension/internal/wmf-print.cpp b/src/extension/internal/wmf-print.cpp index 6780065fb5eed62a182d2463a50f1eaace77d221..f0c20ef159974901808abc8742db1f33767b8cc2 100644 --- a/src/extension/internal/wmf-print.cpp +++ b/src/extension/internal/wmf-print.cpp @@ -235,7 +235,7 @@ unsigned int PrintWmf::begin(Inkscape::Extension::Print *mod, SPDocument *doc) return -1; } - htextcolor_rgb[0] = htextcolor_rgb[1] = htextcolor_rgb[2] = 0.0; + htextcolor_rgb.reset(); rec = U_WMRSETTEXTCOLOR_set(U_RGB(0, 0, 0)); if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { g_warning("Failed in PrintWmf::begin at U_WMRSETTEXTCOLOR_set"); @@ -327,7 +327,6 @@ unsigned int PrintWmf::finish(Inkscape::Extension::Print * /*mod*/) // fcolor is defined when gradients are being expanded, it is the color of one stripe or ring. int PrintWmf::create_brush(SPStyle const *style, U_COLORREF *fcolor) { - float rgb[3]; char *rec; U_WLOGBRUSH lb; uint32_t brush, fmode; @@ -365,12 +364,11 @@ int PrintWmf::create_brush(SPStyle const *style, U_COLORREF *fcolor) opacity = 0.0; // basically the same as no fill } */ - style->fill.value.color.get_rgb_floatv(rgb); - hatchColor = U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]); + hatchColor = toColorRef(style->fill.getColor()); fmode = style->fill_rule.computed == 0 ? U_WINDING : (style->fill_rule.computed == 2 ? U_ALTERNATE : U_ALTERNATE); } else if (is(SP_STYLE_FILL_SERVER(style))) { // must be paint-server - SPPaintServer *paintserver = style->fill.value.href->getObject(); + SPPaintServer *paintserver = style->fill.href->getObject(); auto pat = cast(paintserver); double dwidth = pat->width(); double dheight = pat->height(); @@ -396,7 +394,7 @@ int PrintWmf::create_brush(SPStyle const *style, U_COLORREF *fcolor) brushStyle = U_BS_HATCHED; } else if (is(SP_STYLE_FILL_SERVER(style))) { // must be a gradient // currently we do not do anything with gradients, the code below just sets the color to the average of the stops - SPPaintServer *paintserver = style->fill.value.href->getObject(); + SPPaintServer *paintserver = style->fill.href->getObject(); SPLinearGradient *lg = nullptr; SPRadialGradient *rg = nullptr; @@ -536,11 +534,9 @@ int PrintWmf::create_pen(SPStyle const *style, const Geom::Affine &transform) uint32_t linewidth = 1; if (style) { // override some or all of the preceding - float rgb[3]; // WMF does not support hatched, bitmap, or gradient pens, just set the color. - style->stroke.value.color.get_rgb_floatv(rgb); - penColor = U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]); + penColor = toColorRef(style->stroke.getColor()); using Geom::X; using Geom::Y; @@ -685,23 +681,17 @@ unsigned int PrintWmf::fill( */ destroy_pen(); //this sets the NULL_PEN, otherwise gradient slices may display with boundaries, see longer explanation below Geom::Path cutter; - float rgb[3]; U_COLORREF wc, c1, c2; FillRule frb = SPWR_to_LVFR((SPWindRule) style->fill_rule.computed); double doff, doff_base, doff_range; double divisions = 128.0; int nstops; int istop = 1; - float opa; // opacity at stop SPRadialGradient *tg = (SPRadialGradient *)(gv.grad); // linear/radial are the same here nstops = tg->vector.stops.size(); - tg->vector.stops[0].color.get_rgb_floatv(rgb); - opa = tg->vector.stops[0].opacity; - c1 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); - tg->vector.stops[nstops - 1].color.get_rgb_floatv(rgb); - opa = tg->vector.stops[nstops - 1].opacity; - c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + c1 = toColorRef(tg->vector.stops[0].color); + c2 = toColorRef(tg->vector.stops[nstops - 1].color); doff = 0.0; doff_base = 0.0; @@ -729,9 +719,7 @@ unsigned int PrintWmf::fill( (void) create_brush(style, &wc); print_pathv(pathvr, fill_transform); - tg->vector.stops[istop].color.get_rgb_floatv(rgb); - opa = tg->vector.stops[istop].opacity; - c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + c2 = toColorRef(tg->vector.stops[istop].color); for (start = 0.0; start < range; start += step, doff += 1. / divisions) { stop = start + step + overlap; @@ -754,9 +742,7 @@ unsigned int PrintWmf::fill( doff_base = doff_range; doff_range = tg->vector.stops[istop].offset; // next or last stop c1 = c2; - tg->vector.stops[istop].color.get_rgb_floatv(rgb); - opa = tg->vector.stops[istop].opacity; - c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + c2 = toColorRef(tg->vector.stops[istop].color); } } } else if (gv.mode == DRAW_LINEAR_GRADIENT) { @@ -783,9 +769,7 @@ unsigned int PrintWmf::fill( pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb); print_pathv(pathvr, fill_transform); - tg->vector.stops[istop].color.get_rgb_floatv(rgb); - opa = tg->vector.stops[istop].opacity; - c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + c2 = toColorRef(tg->vector.stops[istop].color); for (start = 0.0; start < range; start += step, doff += 1. / divisions) { stop = start + step + overlap; @@ -807,9 +791,7 @@ unsigned int PrintWmf::fill( doff_base = doff_range; doff_range = tg->vector.stops[istop].offset; // next or last stop c1 = c2; - tg->vector.stops[istop].color.get_rgb_floatv(rgb); - opa = tg->vector.stops[istop].opacity; - c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + c2 = toColorRef(tg->vector.stops[istop].color); } } } else { @@ -1476,12 +1458,11 @@ unsigned int PrintWmf::text(Inkscape::Extension::Print * /*mod*/, char const *te g_error("Fatal programming error in PrintWmf::text at wselectobject_set"); } - float rgb[3]; - style->fill.value.color.get_rgb_floatv(rgb); + auto rgb = style->fill.getColor(); // only change the text color when it needs to be changed - if (memcmp(htextcolor_rgb, rgb, 3 * sizeof(float))) { - memcpy(htextcolor_rgb, rgb, 3 * sizeof(float)); - rec = U_WMRSETTEXTCOLOR_set(U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2])); + if (htextcolor_rgb != rgb) { + htextcolor_rgb = rgb; + rec = U_WMRSETTEXTCOLOR_set(toColorRef(rgb)); if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { g_error("Fatal programming error in PrintWmf::text at U_WMRSETTEXTCOLOR_set"); } diff --git a/src/extension/prefdialog/parameter-color.cpp b/src/extension/prefdialog/parameter-color.cpp index 4e77c1d411dba38b3d964dfc3d87bb5c2e5559b0..86d06e0ea7c14b6810e045cbd0fd622f467f131b 100644 --- a/src/extension/prefdialog/parameter-color.cpp +++ b/src/extension/prefdialog/parameter-color.cpp @@ -17,10 +17,11 @@ #include "parameter-color.h" -#include "color.h" +#include "colors/manager.h" #include "extension/extension.h" #include "preferences.h" #include "ui/pack.h" +#include "ui/util.h" #include "ui/widget/color-notebook.h" #include "xml/node.h" @@ -28,24 +29,19 @@ namespace Inkscape::Extension { ParamColor::ParamColor(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) : InxParameter(xml, ext) + , _colors(std::make_shared()) { - // get value - unsigned int _value = 0x000000ff; // default to black if (xml->firstChild()) { - const char *value = xml->firstChild()->content(); - if (value) - string_to_value(value); - _value = _color.value(); + if (auto parsed = Colors::Color::parse(xml->firstChild()->content())) { + _colors->set(*parsed); + } + } + if (_colors->isEmpty()) { + auto prefs = Preferences::get(); + _colors->set(prefs->getColor(pref_name())); } - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - _value = prefs->getUInt(pref_name(), _value); - - _color.setValue(_value); - - _color_changed = _color.signal_changed.connect(sigc::mem_fun(*this, &ParamColor::_onColorChanged)); - // TODO: SelectedColor does not properly emit signal_changed after dragging, so we also need the following - _color_released = _color.signal_released.connect(sigc::mem_fun(*this, &ParamColor::_onColorChanged)); + _color_changed = _colors->signal_changed.connect(sigc::mem_fun(*this, &ParamColor::_onColorChanged)); // parse appearance if (_appearance) { @@ -61,14 +57,6 @@ ParamColor::ParamColor(Inkscape::XML::Node *xml, Inkscape::Extension::Extension ParamColor::~ParamColor() { _color_changed.disconnect(); - _color_released.disconnect(); -} - -unsigned int ParamColor::set(unsigned int in) -{ - _color.setValue(in); - - return in; } Gtk::Widget *ParamColor::get_widget(sigc::signal *changeSignal) @@ -87,15 +75,12 @@ Gtk::Widget *ParamColor::get_widget(sigc::signal *changeSignal) label->set_visible(true); UI::pack_start(*hbox, *label, true, true); - Gdk::RGBA rgba; - rgba.set_red_u (((_color.value() >> 24) & 255) << 8); - rgba.set_green_u(((_color.value() >> 16) & 255) << 8); - rgba.set_blue_u (((_color.value() >> 8) & 255) << 8); - rgba.set_alpha_u(((_color.value() >> 0) & 255) << 8); - // TODO: It would be nicer to have a custom Gtk::ColorButton() implementation here, // that wraps an Inkscape::UI::Widget::ColorNotebook into a new dialog - _color_button = Gtk::make_managed(rgba); + auto color = _colors->get(); + if (!color) + color = Colors::Color::parse("magenta"); + _color_button = Gtk::make_managed(color_to_rgba(*color)); _color_button->set_title(_text); _color_button->set_use_alpha(); _color_button->set_visible(true); @@ -103,7 +88,7 @@ Gtk::Widget *ParamColor::get_widget(sigc::signal *changeSignal) _color_button->signal_color_set().connect(sigc::mem_fun(*this, &ParamColor::_onColorButtonChanged)); } else { - Gtk::Widget *selector = Gtk::make_managed(_color); + Gtk::Widget *selector = Gtk::make_managed(_colors); UI::pack_start(*hbox, *selector, true, true); selector->set_visible(true); } @@ -116,31 +101,17 @@ Gtk::Widget *ParamColor::get_widget(sigc::signal *changeSignal) void ParamColor::_onColorChanged() { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setUInt(pref_name(), _color.value()); + if (auto color = _colors->get()) { + prefs->setColor(pref_name(), *color); - if (_changeSignal) - _changeSignal->emit(); + if (_changeSignal) + _changeSignal->emit(); + } } void ParamColor::_onColorButtonChanged() { - Gdk::RGBA rgba = _color_button->get_rgba(); - unsigned int value = ((rgba.get_red_u() >> 8) << 24) + - ((rgba.get_green_u() >> 8) << 16) + - ((rgba.get_blue_u() >> 8) << 8) + - ((rgba.get_alpha_u() >> 8) << 0); - set(value); -} - -std::string ParamColor::value_to_string() const -{ - return std::to_string(_color.value()); -} - -void ParamColor::string_to_value(const std::string &in) -{ - // If we canʼt convert this will return 0 or ULONG_MAX - _color.setValue(std::strtoul(in.c_str(), nullptr, 0)); + set(Colors::Color(to_guint32(_color_button->get_rgba()))); } } // namespace Inkscape::Extension diff --git a/src/extension/prefdialog/parameter-color.h b/src/extension/prefdialog/parameter-color.h index 106dd4422b785349da8ab2dfd472ce6a9c834ed0..2b94301574d1549b6f0a943759022be270ede381 100644 --- a/src/extension/prefdialog/parameter-color.h +++ b/src/extension/prefdialog/parameter-color.h @@ -13,8 +13,11 @@ #include #include #include +#include + +#include "colors/color.h" +#include "colors/color-set.h" #include "parameter.h" -#include "ui/selected-color.h" namespace Gtk { class ColorButton; @@ -38,15 +41,11 @@ public: ParamColor(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); ~ParamColor() override; - /** Returns \c _value, with a \i const to protect it. */ - unsigned int get() const { return _color.value(); } - unsigned int set(unsigned int in); + Colors::Color get() const { return _colors->getAverage(); } // copy + void set(Colors::Color const &color) { _colors->set(color); } Gtk::Widget *get_widget(sigc::signal *changeSignal) override; - std::string value_to_string() const override; - void string_to_value(const std::string &in) override; - std::unique_ptr> _changeSignal; private: @@ -54,10 +53,9 @@ private: void _onColorButtonChanged(); /** Internal value of this parameter */ - Inkscape::UI::SelectedColor _color; + std::shared_ptr _colors; sigc::connection _color_changed; - sigc::connection _color_released; Gtk::ColorButton *_color_button; diff --git a/src/extension/prefdialog/parameter.cpp b/src/extension/prefdialog/parameter.cpp index c3594df966ed2c92b00384e3a53ad4336641e378..4d2dd458fe07e6a5e8a78a567f133f09f0d335c7 100644 --- a/src/extension/prefdialog/parameter.cpp +++ b/src/extension/prefdialog/parameter.cpp @@ -163,7 +163,7 @@ bool InxParameter::get_optiongroup_contains(const char *value) const return param->contains(value); } -unsigned int InxParameter::get_color() const +Inkscape::Colors::Color InxParameter::get_color() const { ParamColor const *param = dynamic_cast(this); if (!param) { @@ -214,12 +214,12 @@ const char *InxParameter::set_optiongroup(const char *in) return param->set(in).c_str(); } -unsigned int InxParameter::set_color(unsigned int in) +void InxParameter::set_color(Inkscape::Colors::Color const &in) { ParamColor*param = dynamic_cast(this); if (param == nullptr) throw param_not_color_param(); - return param->set(in); + param->set(in); } diff --git a/src/extension/prefdialog/parameter.h b/src/extension/prefdialog/parameter.h index 3b37951507188c7ba229ad8e30e52271ecaa0150..ac70db8c757b833e9974239a7e5f8e534c18d8bf 100644 --- a/src/extension/prefdialog/parameter.h +++ b/src/extension/prefdialog/parameter.h @@ -21,6 +21,10 @@ namespace Glib { class ustring; } // namespace Glib +namespace Inkscape::Colors { +class Color; +} + namespace Inkscape::Extension { /** @@ -55,7 +59,7 @@ public: bool get_optiongroup_contains(const char *value) const; /** Wrapper to cast to the object and use it's function. */ - unsigned int get_color() const; + Inkscape::Colors::Color get_color() const; /** Wrapper to cast to the object and use it's function. */ bool set_bool(bool in); @@ -73,7 +77,7 @@ public: const char *set_optiongroup(const char *in); /** Wrapper to cast to the object and use it's function. */ - unsigned int set_color(unsigned int in); + void set_color(Inkscape::Colors::Color const &in); char const *name() const { return _name; } diff --git a/src/gradient-chemistry.cpp b/src/gradient-chemistry.cpp index 5af41778a0b5b94dca8129f47caf8644244f872a..1f9a868f4c4a3f755c6fa8f6bf836e6139ed4972 100644 --- a/src/gradient-chemistry.cpp +++ b/src/gradient-chemistry.cpp @@ -46,7 +46,6 @@ #include "style.h" #include "svg/svg.h" -#include "svg/svg-color.h" #include "svg/css-ostringstream.h" #include "ui/icon-names.h" @@ -54,6 +53,8 @@ #include "ui/widget/gradient-vector-selector.h" #include "xml/href-attribute-helper.h" +#include "colors/color.h" + #define noSP_GR_VERBOSE using Inkscape::DocumentUndo; @@ -705,16 +706,6 @@ SPStop *sp_get_stop_i(SPGradient *gradient, guint stop_i) return stop; } -guint32 average_color(guint32 c1, guint32 c2, gdouble p) -{ - guint32 r = static_cast(SP_RGBA32_R_U (c1) * (1 - p) + SP_RGBA32_R_U (c2) * p); - guint32 g = static_cast(SP_RGBA32_G_U (c1) * (1 - p) + SP_RGBA32_G_U (c2) * p); - guint32 b = static_cast(SP_RGBA32_B_U (c1) * (1 - p) + SP_RGBA32_B_U (c2) * p); - guint32 a = static_cast(SP_RGBA32_A_U (c1) * (1 - p) + SP_RGBA32_A_U (c2) * p); - - return SP_RGBA32_U_COMPOSE(r, g, b, a); -} - void sp_repr_set_css_double(Inkscape::XML::Node* node, const char* key, double value) { if (node) { node->setAttributeCssDouble(key, value); @@ -731,7 +722,7 @@ SPStop *sp_vector_add_stop(SPGradient *vector, SPStop* prev_stop, SPStop* next_s if (!prev_stop && !next_stop) return newstop; // This function completely breaks CMYK gradients. - guint cnew = 0; // new color + Color cnew(0x000000ff); // new color Inkscape::XML::Node *new_stop_repr = nullptr; if (!prev_stop || !next_stop) { @@ -741,23 +732,19 @@ SPStop *sp_vector_add_stop(SPGradient *vector, SPStop* prev_stop, SPStop* next_s new_stop_repr = repr->duplicate(vector->getRepr()->document()); vector->getRepr()->addChild(new_stop_repr, prev_stop ? repr : nullptr); - cnew = stop->get_rgba32(); + cnew = stop->getColor(); } else { auto repr = prev_stop->getRepr(); new_stop_repr = repr->duplicate(vector->getRepr()->document()); vector->getRepr()->addChild(new_stop_repr, repr); - - guint32 const c1 = prev_stop->get_rgba32(); - guint32 const c2 = next_stop->get_rgba32(); - cnew = average_color (c1, c2, (offset - prev_stop->offset) / (next_stop->offset - prev_stop->offset)); + cnew = prev_stop->getColor().averaged(next_stop->getColor(), (offset - prev_stop->offset) / (next_stop->offset - prev_stop->offset)); } newstop = reinterpret_cast(vector->document->getObjectByRepr(new_stop_repr)); newstop->offset = offset; newstop->getRepr()->setAttributeCssDouble("offset", (double)offset); - // XXX This is removing icc color information - newstop->setColor({cnew}, SP_RGBA32_A_F(cnew)); + newstop->setColor(cnew); Inkscape::GC::release(new_stop_repr); return newstop; @@ -797,13 +784,13 @@ static bool verify_grad(SPGradient* gradient) { child = xml_doc->createElement("svg:stop"); sp_repr_set_css_double(child, "offset", 0.0); - SPStop::setColorRepr(child, {0, 0, 0}, 1.0); + SPStop::setColorRepr(child, Colors::Color(0x000000ff)); gradient->getRepr()->addChild(child, nullptr); Inkscape::GC::release(child); child = xml_doc->createElement("svg:stop"); sp_repr_set_css_double(child, "offset", 1.0); - SPStop::setColorRepr(child, {0, 0, 0}, 1.0); + SPStop::setColorRepr(child, Colors::Color(0x000000ff)); gradient->getRepr()->addChild(child, nullptr); Inkscape::GC::release(child); modified = true; @@ -854,11 +841,7 @@ SPStop* sp_gradient_add_stop(SPGradient* gradient, SPStop* current) { newstop->offset = (stop->offset + next->offset) * 0.5 ; - guint32 const c1 = stop->get_rgba32(); - guint32 const c2 = next->get_rgba32(); - guint32 cnew = average_color(c1, c2); - - newstop->setColor({cnew}, SP_RGBA32_A_F(cnew)); + newstop->setColor(stop->getColor().averaged(next->getColor())); sp_repr_set_css_double(newstop->getRepr(), "offset", (double)newstop->offset); Inkscape::GC::release(new_stop_repr); DocumentUndo::done(gradient->document, _("Add gradient stop"), INKSCAPE_ICON("color-gradient")); @@ -887,9 +870,9 @@ SPStop* sp_gradient_add_stop_at(SPGradient* gradient, double offset) { } } -void sp_set_gradient_stop_color(SPDocument* document, SPStop* stop, SPColor color, double opacity) { +void sp_set_gradient_stop_color(SPDocument* document, SPStop* stop, Color const &color) { sp_repr_set_css_double(stop->getRepr(), "offset", stop->offset); - stop->setColor(color, opacity); + stop->setColor(color); DocumentUndo::done(document, _("Change gradient stop color"), INKSCAPE_ICON("color-gradient")); } @@ -931,20 +914,19 @@ SPStop* sp_item_gradient_get_stop(SPItem *item, GrPointType point_type, guint po return nullptr; } -guint32 sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke) +Color sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke) { SPGradient *gradient = getGradient(item, fill_or_stroke); - if (!gradient) { - return 0; - } + if (!gradient) + return Colors::Color(0x000000ff); if (is(gradient) || is(gradient) ) { SPGradient *vector = gradient->getVector(); if (!vector) // orphan! - return 0; // what else to do? + return Colors::Color(0x000000ff); switch (point_type) { case POINT_LG_BEGIN: @@ -953,7 +935,7 @@ guint32 sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, { SPStop *first = vector->getFirstStop(); if (first) { - return first->get_rgba32(); + return first->getColor(); } } break; @@ -964,7 +946,7 @@ guint32 sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, { SPStop *last = sp_last_stop (vector); if (last) { - return last->get_rgba32(); + return last->getColor(); } } break; @@ -975,7 +957,7 @@ guint32 sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, { SPStop *stopi = sp_get_stop_i (vector, point_i); if (stopi) { - return stopi->get_rgba32(); + return stopi->getColor(); } } break; @@ -984,7 +966,6 @@ guint32 sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, g_warning( "Bad linear/radial gradient handle type" ); break; } - return 0; } else if (is(gradient)) { // Mesh gradient @@ -993,16 +974,12 @@ guint32 sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, switch (point_type) { case POINT_MG_CORNER: { if (point_i >= mg->array.corners.size()) { - return 0; + return Colors::Color(0x000000ff); } SPMeshNode const* cornerpoint = mg->array.corners[ point_i ]; if (cornerpoint) { - SPColor color = cornerpoint->color; - double opacity = cornerpoint->opacity; - return color.toRGBA32( opacity ); - } else { - return 0; + return *cornerpoint->color; } break; } @@ -1017,10 +994,8 @@ guint32 sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, default: g_warning( "Bad mesh handle type" ); } - return 0; } - - return 0; + return Colors::Color(0x000000ff); } void sp_item_gradient_stop_set_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke, SPCSSAttr *stop) @@ -1088,37 +1063,22 @@ void sp_item_gradient_stop_set_style(SPItem *item, GrPointType point_type, guint // Mesh gradient auto mg = cast(gradient); - bool changed = false; switch (point_type) { case POINT_MG_CORNER: { // Update mesh array (which is not updated automatically when stop is changed?) - gchar const* color_str = sp_repr_css_property( stop, "stop-color", nullptr ); - if( color_str ) { - SPColor color( 0 ); - SPIPaint paint; - paint.read( color_str ); - if( paint.isColor() ) { - color = paint.value.color; - } - mg->array.corners[ point_i ]->color = color; - changed = true; - } - gchar const* opacity_str = sp_repr_css_property( stop, "stop-opacity", nullptr ); - if( opacity_str ) { - std::stringstream os( opacity_str ); - double opacity = 1.0; - os >> opacity; - mg->array.corners[ point_i ]->opacity = opacity; - changed = true; - } - // Update stop - if( changed ) { - SPStop *stopi = mg->array.corners[ point_i ]->stop; - if (stopi) { - sp_repr_css_change(stopi->getRepr(), stop, "style"); - } else { - std::cerr << "sp_item_gradient_stop_set_style: null stopi" << std::endl; + gchar const* color_str = sp_repr_css_property(stop, "stop-color", nullptr); + if (auto color = Color::parse(color_str)) { + color->addOpacity(sp_repr_css_double_property(stop, "stop-opacity", 1.0)); + if (*color != mg->array.corners[point_i]->color) { + mg->array.corners[ point_i ]->color = *color; + // Update stop + SPStop *stopi = mg->array.corners[ point_i ]->stop; + if (stopi) { + sp_repr_css_change(stopi->getRepr(), stop, "style"); + } else { + std::cerr << "sp_item_gradient_stop_set_style: null stopi" << std::endl; + } } } break; @@ -1209,16 +1169,9 @@ void sp_item_gradient_invert_vector_color(SPItem *item, Inkscape::PaintTarget fi for (auto &child: vector->children) { if (auto stop = cast(&child)) { - // XXX This breaks icc / cmyk colors! - guint32 color = stop->get_rgba32(); - //g_message("Stop color %d", color); - color = SP_RGBA32_U_COMPOSE( - (255 - SP_RGBA32_R_U(color)), - (255 - SP_RGBA32_G_U(color)), - (255 - SP_RGBA32_B_U(color)), - SP_RGBA32_A_U(color) - ); - stop->setColor({color}, SP_RGBA32_A_U(color)); + auto color = stop->getColor(); + color.invert(); + stop->setColor(color); } } } @@ -1723,14 +1676,17 @@ static void sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *lin } -static void addStop(Inkscape::XML::Node *parent, SPColor color, double opacity, gchar const *offset) +static void addStop(Inkscape::XML::Node *parent, Color const &color, double opacity, gchar const *offset) { #ifdef SP_GR_VERBOSE g_message("addStop(%p, %s, %d, %s)", parent, color.c_str(), opacity, offset); #endif auto doc = parent->document(); Inkscape::XML::Node *repr = doc->createElement("svg:stop"); - SPStop::setColorRepr(repr, color, opacity); + + Color copy = color; + copy.addOpacity(opacity); + SPStop::setColorRepr(repr, copy); repr->setAttribute( "offset", offset ); parent->appendChild(repr); Inkscape::GC::release(repr); @@ -1739,7 +1695,7 @@ static void addStop(Inkscape::XML::Node *parent, SPColor color, double opacity, /* * Get default normalized gradient vector of document, create if there is none */ -SPGradient *sp_document_default_gradient_vector( SPDocument *document, SPColor const &color, double opacity, bool singleStop ) +SPGradient *sp_document_default_gradient_vector( SPDocument *document, Color const &color, double opacity, bool singleStop ) { SPDefs *defs = document->getDefs(); @@ -1783,10 +1739,14 @@ SPGradient *sp_document_default_gradient_vector( SPDocument *document, SPColor c SPGradient *sp_gradient_vector_for_object( SPDocument *const doc, SPDesktop *const desktop, SPObject *const o, Inkscape::PaintTarget const fill_or_stroke, bool singleStop ) { - SPColor color; + Color color(0x000000ff); double opacity = 1.0; bool for_fill = fill_or_stroke == Inkscape::FOR_FILL; + // if not o or o doesn't use flat color, then take current color of the desktop. + if (auto tmp = sp_desktop_get_color(desktop, for_fill)) + color = *tmp; + if (o && o->style) { // take the color of the object SPStyle const &style = *(o->style); @@ -1797,15 +1757,11 @@ SPGradient *sp_gradient_vector_for_object( SPDocument *const doc, SPDesktop *con return cast(server)->getVector(true); } } else if (paint.isColor()) { - color = paint.value.color; - opacity = SP_SCALE24_TO_FLOAT(for_fill ? style.fill_opacity.value : style.stroke_opacity.value); + color = paint.getColor(); + opacity = for_fill ? style.fill_opacity : style.stroke_opacity; } } - if (!color) { - // if not o or o doesn't use flat color, then take current color of the desktop. - color = sp_desktop_get_color(desktop, for_fill); - } return sp_document_default_gradient_vector(doc, color, opacity, singleStop); } diff --git a/src/gradient-chemistry.h b/src/gradient-chemistry.h index 5d3a71ffa3aa574fe1e19775e6c59a674648af27..1af443a679613ad9fc48553d54e3c042da6aeb0f 100644 --- a/src/gradient-chemistry.h +++ b/src/gradient-chemistry.h @@ -26,6 +26,10 @@ class SPItem; class SPGradient; class SPDesktop; +namespace Inkscape::Colors { +class Color; +} +using Inkscape::Colors::Color; /** * Either normalizes given gradient to vector, or returns fresh normalized @@ -46,7 +50,7 @@ SPGradient *sp_item_set_gradient(SPItem *item, SPGradient *gr, SPGradientType ty /* * Get default normalized gradient vector of document, create if there is none */ -SPGradient *sp_document_default_gradient_vector( SPDocument *document, SPColor const &color, double opacity, bool singleStop ); +SPGradient *sp_document_default_gradient_vector( SPDocument *document, Color const &color, double opacity, bool singleStop ); /** * Return the preferred vector for \a o, made from (in order of preference) its current vector, @@ -72,14 +76,12 @@ std::pair sp_get_before_after_stops(SPStop* stop); unsigned int sp_number_of_stops(SPGradient const *gradient); unsigned int sp_number_of_stops_before_stop(SPGradient* gradient, SPStop *target); -guint32 average_color(guint32 c1, guint32 c2, double p = 0.5); - SPStop *sp_vector_add_stop(SPGradient *vector, SPStop* prev_stop, SPStop* next_stop, gfloat offset); void sp_gradient_delete_stop(SPGradient* gradient, SPStop* stop); SPStop* sp_gradient_add_stop(SPGradient* gradient, SPStop* current); SPStop* sp_gradient_add_stop_at(SPGradient* gradient, double offset); -void sp_set_gradient_stop_color(SPDocument* document, SPStop* stop, SPColor color, double opacity); +void sp_set_gradient_stop_color(SPDocument* document, SPStop* stop, Color const &color); void sp_gradient_transform_multiply(SPGradient *gradient, Geom::Affine postmul, bool set); @@ -120,7 +122,7 @@ SPGradientSpread sp_item_gradient_get_spread(SPItem *item, Inkscape::PaintTarget SPStop* sp_item_gradient_get_stop(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke); void sp_item_gradient_stop_set_style(SPItem *item, GrPointType point_type, unsigned int point_i, Inkscape::PaintTarget fill_or_stroke, SPCSSAttr *stop); -guint32 sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, unsigned int point_i, Inkscape::PaintTarget fill_or_stroke); +Color sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, unsigned int point_i, Inkscape::PaintTarget fill_or_stroke); void sp_item_gradient_reverse_vector(SPItem *item, Inkscape::PaintTarget fill_or_stroke); void sp_item_gradient_invert_vector_color(SPItem *item, Inkscape::PaintTarget fill_or_stroke); diff --git a/src/gradient-drag.cpp b/src/gradient-drag.cpp index 55b108e204ca7cf5c6111767e58d731e97d73e3d..8bb3799df4d29db4ed5de5ade0351c3df148eda6 100644 --- a/src/gradient-drag.cpp +++ b/src/gradient-drag.cpp @@ -20,6 +20,9 @@ #include +#include "colors/utils.h" +#include "colors/color-set.h" + #include "desktop-style.h" #include "desktop.h" #include "document-undo.h" @@ -125,12 +128,9 @@ static int gr_drag_style_query(SPStyle *style, int property, gpointer data) return QUERY_STYLE_NOTHING; } else { int ret = QUERY_STYLE_NOTHING; - - float cf[4]; - cf[0] = cf[1] = cf[2] = cf[3] = 0; + auto colors = Colors::ColorSet(); SPStop* selected = nullptr; - int count = 0; for(auto d : drag->selected) { //for all selected draggers for(auto draggable : d->draggables) { //for all draggables of dragger if (ret == QUERY_STYLE_NOTHING) { @@ -140,39 +140,32 @@ static int gr_drag_style_query(SPStyle *style, int property, gpointer data) ret = QUERY_STYLE_MULTIPLE_AVERAGED; } - guint32 c = sp_item_gradient_stop_query_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke); - cf[0] += SP_RGBA32_R_F (c); - cf[1] += SP_RGBA32_G_F (c); - cf[2] += SP_RGBA32_B_F (c); - cf[3] += SP_RGBA32_A_F (c); - - count ++; + colors.set( + draggable->item->getId(), + sp_item_gradient_stop_query_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke) + ); } } - if (count) { - cf[0] /= count; - cf[1] /= count; - cf[2] /= count; - cf[3] /= count; + if (!colors.isEmpty()) { + auto avg = colors.getAverage(); + auto opacity = avg.stealOpacity(); // set both fill and stroke with our stop-color and stop-opacity style->fill.clear(); - style->fill.setColor( cf[0], cf[1], cf[2] ); + style->fill.setColor(avg); style->fill.set = TRUE; style->fill.setTag(selected); style->stroke.clear(); - style->stroke.setColor( cf[0], cf[1], cf[2] ); + style->stroke.setColor(avg); style->stroke.set = TRUE; style->stroke.setTag(selected); - style->fill_opacity.value = SP_SCALE24_FROM_FLOAT (cf[3]); - style->fill_opacity.set = TRUE; - style->stroke_opacity.value = SP_SCALE24_FROM_FLOAT (cf[3]); - style->stroke_opacity.set = TRUE; + style->fill_opacity.set_double(opacity); + style->stroke_opacity.set_double(opacity); - style->opacity.value = SP_SCALE24_FROM_FLOAT (cf[3]); - style->opacity.set = TRUE; + // This seems wrong, it duplicates the opacity + style->opacity.set_double(opacity); } return ret; @@ -304,35 +297,20 @@ bool GrDrag::styleSet( const SPCSSAttr *css, bool switch_style) return local_change; // true if handled } -guint32 GrDrag::getColor() +Inkscape::Colors::Color GrDrag::getColor() { - if (selected.empty()) return 0; - - float cf[4]; - cf[0] = cf[1] = cf[2] = cf[3] = 0; - - int count = 0; + if (selected.empty()) + return Color(0x000000ff); + Inkscape::Colors::ColorSet colors; for(auto d : selected) { //for all selected draggers for(auto draggable : d->draggables) { //for all draggables of dragger - guint32 c = sp_item_gradient_stop_query_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke); - cf[0] += SP_RGBA32_R_F (c); - cf[1] += SP_RGBA32_G_F (c); - cf[2] += SP_RGBA32_B_F (c); - cf[3] += SP_RGBA32_A_F (c); - - count ++; + colors.set( + draggable->item->getId(), + sp_item_gradient_stop_query_style (draggable->item, draggable->point_type, draggable->point_i, draggable->fill_or_stroke)); } } - - if (count) { - cf[0] /= count; - cf[1] /= count; - cf[2] /= count; - cf[3] /= count; - } - - return SP_RGBA32_F_COMPOSE(cf[0], cf[1], cf[2], cf[3]); + return colors.getAverage(); } // TODO refactor early returns diff --git a/src/gradient-drag.h b/src/gradient-drag.h index 98e8957244efa89ac319768e781f6885fbd3ca96..86fef80ca34b8e6f5e8966203df83c2eac9f719d 100644 --- a/src/gradient-drag.h +++ b/src/gradient-drag.h @@ -44,6 +44,9 @@ class SPRadialGradient; class SPStop; namespace Inkscape { +namespace Colors { +class Color; +} class Selection; class CanvasItemCurve; struct KeyPressEvent; @@ -177,7 +180,7 @@ public: // FIXME: make more of this private! void deleteSelected(bool just_one = false); - guint32 getColor(); + Inkscape::Colors::Color getColor(); bool keep_selection; diff --git a/src/hsluv.cpp b/src/hsluv.cpp deleted file mode 100644 index 64f30e74cb8614c1265f2c3adab02097e3e2d345..0000000000000000000000000000000000000000 --- a/src/hsluv.cpp +++ /dev/null @@ -1,506 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * HSLuv-C: Human-friendly HSL - * - * Authors: - * 2015 Alexei Boronine (original idea, JavaScript implementation) - * 2015 Roger Tallada (Obj-C implementation) - * 2017 Martin Mitas (C implementation, based on Obj-C implementation) - * 2021 Massinissa Derriche (C++ implementation for Inkscape, based on C implementation) - * - * Copyright (C) 2021 Authors - * - * 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 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 "hsluv.h" - -#include -#include -#include -#include -#include <2geom/line.h> -#include <2geom/ray.h> - -namespace Hsluv { - -/* for sRGB, reference white D65 */ -static const Triplet m[3] = { - { 3.24096994190452134377, -1.53738317757009345794, -0.49861076029300328366 }, - { -0.96924363628087982613, 1.87596750150772066772, 0.04155505740717561247 }, - { 0.05563007969699360846, -0.20397695888897656435, 1.05697151424287856072 } -}; - -/* for XYZ */ -static const Triplet m_inv[3] = { - { 0.41239079926595948129, 0.35758433938387796373, 0.18048078840183428751 }, - { 0.21263900587151035754, 0.71516867876775592746, 0.07219231536073371500 }, - { 0.01933081871559185069, 0.11919477979462598791, 0.95053215224966058086 } -}; - -static const double REF_U = 0.19783000664283680764; -static const double REF_V = 0.46831999493879100370; - -// CIE LUV constants -static const double KAPPA = 903.29629629629629629630; -static const double EPSILON = 0.00885645167903563082; - -/** - * Calculate the bounds of the Luv colors in RGB gamut. - * - * @param l Lightness. Between 0.0 and 100.0. - * @return Bounds of Luv colors in RGB gamut. - */ -std::array get_bounds(double l) -{ - std::array bounds; - - double tl = l + 16.0; - double sub1 = (tl * tl * tl) / 1560896.0; - double sub2 = (sub1 > EPSILON ? sub1 : (l / KAPPA)); - int channel; - int t; - - for(channel = 0; channel < 3; channel++) { - double m1 = m[channel][0]; - double m2 = m[channel][1]; - double m3 = m[channel][2]; - - for (t = 0; t < 2; t++) { - double top1 = (284517.0 * m1 - 94839.0 * m3) * sub2; - double top2 = (838422.0 * m3 + 769860.0 * m2 + 731718.0 * m1) * l * sub2 - 769860.0 * t * l; - double bottom = (632260.0 * m3 - 126452.0 * m2) * sub2 + 126452.0 * t; - - bounds[channel * 2 + t].setCoefficients(top1, -bottom, top2); - } - } - - return bounds; -} - -/** - * Calculate the maximum in gamut chromaticity for the given luminance and hue. - * - * @param l Luminance. - * @param h Hue. - * @return The maximum chromaticity. - */ -static double max_chroma_for_lh(double l, double h) -{ - double min_len = std::numeric_limits::max(); - auto const ray = Geom::Ray(Geom::Point(0, 0), Geom::rad_from_deg(h)); - - for (auto const &line : get_bounds(l)) { - auto intersections = line.intersect(ray); - if (intersections.empty()) { - continue; - } - double len = intersections[0].point().length(); - - if (len >= 0 && len < min_len) { - min_len = len; - } - } - - return min_len; -} - -/** - * Calculate the dot product of the given arrays. - * - * @param t1 The first array. - * @param t2 The second array. - * @return The resulting dot product. - */ -static double dot_product(const Triplet &t1, const Triplet &t2) -{ - return (t1[0] * t2[0] + t1[1] * t2[1] + t1[2] * t2[2]); -} - -/** - * Convenience function used for RGB conversions. - * - * @param c Value. - * @return RGB color component. - */ -double from_linear(double c) -{ - if (c <= 0.0031308) { - return 12.92 * c; - } else { - return 1.055 * std::pow(c, 1.0 / 2.4) - 0.055; - } -} - -/** - * Convenience function used for RGB conversions. - * - * @param c Value. - * @return XYZ color component. - */ -double to_linear(double c) -{ - if (c > 0.04045) { - return std::pow((c + 0.055) / 1.055, 2.4); - } else { - return c / 12.92; - } -} - -/** - * @overload - * @param t RGB color components. - * @return XYZ color components. - */ -static Triplet to_linear(const Triplet &t) -{ - return { - to_linear(t[0]), - to_linear(t[1]), - to_linear(t[2]) - }; -} - -/** - * Convert a color from the the XYZ colorspace to the RGB colorspace. - * - * @param in_out[in,out] The XYZ color converted to a RGB color. - */ -static void xyz2rgb(Triplet &in_out) -{ - Triplet result; - for (size_t i : {0, 1, 2}) { - result[i] = from_linear(dot_product(m[i], in_out)); - } - in_out = result; -} - -/** - * Convert a color from the the RGB colorspace to the XYZ colorspace. - * - * @param in_out[in,out] The RGB color converted to a XYZ color. - */ -static void rgb2xyz(Triplet &in_out) -{ - Triplet rgbl = to_linear(in_out); - for (size_t i : {0, 1, 2}) { - in_out[i] = dot_product(m_inv[i], rgbl); - } -} - -/** - * Utility function used to convert from the XYZ colorspace to CIELuv. - * https://en.wikipedia.org/wiki/CIELUV - * - * @param y Y component of the XYZ color. - * @return Luminance component of Luv color. - */ -static double y2l(double y) -{ - if (y <= EPSILON) - return y * KAPPA; - else - return 116.0 * std::cbrt(y) - 16.0; -} - -/** - * Utility function used to convert from CIELuv colorspace to XYZ. - * - * @param l Luminance component of Luv color. - * @return Y component of the XYZ color. - */ -static double l2y(double l) -{ - if (l <= 8.0) { - return l / KAPPA; - } else { - double x = (l + 16.0) / 116.0; - return (x * x * x); - } -} - -/** - * Convert a color from the the XYZ colorspace to the Luv colorspace. - * - * @param in_out[in,out] The XYZ color converted to a Luv color. - */ -static void xyz2luv(Triplet &in_out) -{ - double const denominator = in_out[0] + (15.0 * in_out[1]) + (3.0 * in_out[2]); - double var_u = 4.0 * in_out[0] / denominator; - double var_v = 9.0 * in_out[1] / denominator; - double l = y2l(in_out[1]); - double u = 13.0 * l * (var_u - REF_U); - double v = 13.0 * l * (var_v - REF_V); - - in_out[0] = l; - if (l < 0.00000001) { - in_out[1] = 0.0; - in_out[2] = 0.0; - } else { - in_out[1] = u; - in_out[2] = v; - } -} - -/** - * Convert a color from the the Luv colorspace to the XYZ colorspace. - * - * @param in_out[in,out] The Luv color converted to a XYZ color. - */ -static void luv2xyz(Triplet &in_out) -{ - if (in_out[0] <= 0.00000001) { - /* Black would create a divide-by-zero error. */ - in_out[0] = 0.0; - in_out[1] = 0.0; - in_out[2] = 0.0; - return; - } - - double var_u = in_out[1] / (13.0 * in_out[0]) + REF_U; - double var_v = in_out[2] / (13.0 * in_out[0]) + REF_V; - double y = l2y(in_out[0]); - double x = -(9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v); - double z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v); - - in_out[0] = x; - in_out[1] = y; - in_out[2] = z; -} - -/** - * Convert a color from the the Luv colorspace to the LCH colorspace. - * - * @param in_out[in,out] The Luv color converted to a LCH color. - */ -static void luv2lch(Triplet &in_out) -{ - double l = in_out[0]; - auto uv = Geom::Point(in_out[1], in_out[2]); - double h; - double const c = uv.length(); - - /* Grays: disambiguate hue */ - if (c < 0.00000001) { - h = 0; - } else { - h = Geom::deg_from_rad(Geom::atan2(uv)); - if (h < 0.0) { - h += 360.0; - } - } - - in_out[0] = l; - in_out[1] = c; - in_out[2] = h; -} - -/** - * Convert a color from the the LCH colorspace to the Luv colorspace. - * - * @param in_out[in,out] The LCH color converted to a Luv color. - */ -static void lch2luv(Triplet &in_out) -{ - double sinhrad, coshrad; - Geom::sincos(Geom::rad_from_deg(in_out[2]), sinhrad, coshrad); - double u = coshrad * in_out[1]; - double v = sinhrad * in_out[1]; - - in_out[1] = u; - in_out[2] = v; -} - -/** - * Convert a color from the the HSLuv colorspace to the LCH colorspace. - * - * @param in_out[in,out] The HSLuv color converted to a LCH color. - */ -static void hsluv2lch(Triplet &in_out) -{ - double h = in_out[0]; - double s = in_out[1]; - double l = in_out[2]; - double c; - - /* White and black: disambiguate chroma */ - if(l > 99.9999999 || l < 0.00000001) { - c = 0.0; - } else { - c = max_chroma_for_lh(l, h) / 100.0 * s; - } - - /* Grays: disambiguate hue */ - if (s < 0.00000001) { - h = 0.0; - } - - in_out[0] = l; - in_out[1] = c; - in_out[2] = h; -} - -/** - * Convert a color from the the LCH colorspace to the HSLuv colorspace. - * - * @param in_out[in,out] The LCH color converted to a HSLuv color. - */ -static void lch2hsluv(Triplet &in_out) -{ - double l = in_out[0]; - double c = in_out[1]; - double h = in_out[2]; - double s; - - /* White and black: disambiguate saturation */ - if (l > 99.9999999 || l < 0.00000001) { - s = 0.0; - } else { - s = c / max_chroma_for_lh(l, h) * 100.0; - } - - /* Grays: disambiguate hue */ - if (c < 0.00000001) { - h = 0.0; - } - - in_out[0] = h; - in_out[1] = s; - in_out[2] = l; -} - -// Interface functions -Triplet luv_to_rgb(double l, double u, double v) -{ - Triplet result{l, u, v}; - luv2xyz(result); - xyz2rgb(result); - - for (size_t i : {0, 1, 2}) { - result[i] = std::clamp(result[i], 0.0, 1.0); - } - return result; -} - -Triplet hsluv_to_luv(double h, double s, double l) -{ - Triplet result{h, s, l}; - hsluv2lch(result); - lch2luv(result); - return result; -} - -Triplet luv_to_hsluv(double l, double u, double v) -{ - Triplet result{l, u, v}; - luv2lch(result); - lch2hsluv(result); - return result; -} - -Triplet rgb_to_hsluv(double r, double g, double b) -{ - Triplet result{r, g, b}; - rgb2xyz(result); - xyz2luv(result); - luv2lch(result); - lch2hsluv(result); - return result; -} - -Triplet hsluv_to_rgb(double h, double s, double l) -{ - Triplet result{h, s, l}; - hsluv2lch(result); - lch2luv(result); - luv2xyz(result); - xyz2rgb(result); - - for (size_t i : {0, 1, 2}) { - result[i] = std::clamp(result[i], 0.0, 1.0); - } - return result; -} - -Triplet hsluv_to_luv(double *hsl) -{ - return hsluv_to_luv(hsl[0], hsl[1], hsl[2]); -} - -double perceptual_lightness(double l) -{ - return l <= 0.885645168 ? l * 0.09032962963 : std::cbrt(l) * 0.249914424 - 0.16; -} - -double rgb_to_perceptual_lightness(Triplet const &rgb) -{ - return perceptual_lightness(rgb_to_hsluv(rgb[0], rgb[1], rgb[2])[2]); -} - -std::pair get_contrasting_color(double l) -{ - double constexpr l_threshold = 0.85; - if (l > l_threshold) { // Draw dark over light. - auto t = (l - l_threshold) / (1.0 - l_threshold); - return {0.0, 0.4 - 0.1 * t}; - } else { // Draw light over dark. - auto t = (l_threshold - l) / l_threshold; - return {1.0, 0.6 + 0.1 * t}; - } -} - -// http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html -Triplet cielab_to_xyz(double L, double a, double b, const Triplet& ref_white_xyz) { - auto fy = (L + 16) / 116; - auto fx = a / 500 + fy; - auto fz = fy - b / 200; - - constexpr auto e = 0.008856; - constexpr auto k = 903.3; - - auto fx3 = std::pow(fx, 3); - auto fy3 = std::pow(fy, 3); - auto fz3 = std::pow(fz, 3); - - auto xr = fx3 > e ? fx3 : (116 * fx - 16) / k; - auto yr = L > k * e ? fy3 : L / k; - auto zr = fz3 > e ? fz3 : (116 * fz - 16) / k; - - return {xr * ref_white_xyz[0], yr * ref_white_xyz[1], zr * ref_white_xyz[2]}; -} - -Triplet lab_to_rgb(double L, double a, double b, const Triplet& ref_white_xyz) { - auto result = cielab_to_xyz(L, a, b, ref_white_xyz); - xyz2rgb(result); - for (size_t i : {0, 1, 2}) { - result[i] = std::clamp(result[i], 0.0, 1.0); - } - return result; -} - -// CIE standard illuminant D65, Observer= 2° [0.9504, 1.0000, 1.0888]. -// Simulates noon daylight with correlated color temperature of 6504 K. -constexpr Triplet illuminant_d65{ 0.9504, 1.0000, 1.0888 }; - -Triplet lab_to_rgb(double L, double a, double b) { - return lab_to_rgb(L, a, b, illuminant_d65); -} - -} // namespace Hsluv diff --git a/src/hsluv.h b/src/hsluv.h deleted file mode 100644 index b8d814d9b8dc6c476e11952131d19971b9fc4b2d..0000000000000000000000000000000000000000 --- a/src/hsluv.h +++ /dev/null @@ -1,174 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * HSLuv-C: Human-friendly HSL - * - * Authors: - * 2015 Alexei Boronine (original idea, JavaScript implementation) - * 2015 Roger Tallada (Obj-C implementation) - * 2017 Martin Mitas (C implementation, based on Obj-C implementation) - * 2021 Massinissa Derriche (C++ implementation for Inkscape, based on C implementation) - * - * Copyright (C) 2021 Authors - * - * 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 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. - */ - -#ifndef SEEN_HSLUV_H -#define SEEN_HSLUV_H - -#include -#include <2geom/line.h> - -namespace Hsluv { - -// Types -using Triplet = std::array; - -/** - * Used to represent the in RGB gamut colors polygon of the HSLuv color wheel. - */ -struct PickerGeometry { - std::vector vertices; ///< Vertices, in counter-clockwise order. - double outer_circle_radius; ///< Smallest circle with center at origin such that polygon fits inside. - double inner_circle_radius; ///< Largest circle with center at origin such that it fits inside polygon. -}; - -// Functions -/** Apply sRGB gamma compression to a linear RGB color component. */ -double from_linear(double c); - -/** Convert an sRGB color component to linear RGB (de-gamma). */ -double to_linear(double c); - -/** - * Return the bounds of the Luv colors in RGB gamut. - * - * @param l Lightness. Between 0.0 and 100.0. - * @return Bounds of Luv colors in RGB gamut. - */ -std::array get_bounds(double l); - -/** - * Convert Luv to RGB. - * - * @param l Luminance. Between 0.0 and 100.0. - * @param u U coordinate. - * @param v V coordinate. - * @return An RGB triplet, with all components between 0.0 and 1.0. - */ -Triplet luv_to_rgb(double l, double u, double v); - -/** - * Convert Lab to sRGB. - * - * @param L Luminance. Between 0.0 and 100.0. - * @param a A coordinate. - * @param b B coordinate. - * @param ref_white_xyz White reference in normalized XYZ color space. - * @return An sRGB triplet, with all components between 0.0 and 1.0. - */ -Triplet lab_to_rgb(double L, double a, double b, const Triplet& ref_white_xyz); -/** - * Convert Lab to sRGB using D65 illuminant reference point. - * - * @param L Luminance. Between 0.0 and 100.0. - * @param a A coordinate. - * @param b B coordinate. - * @return An sRGB triplet, with all components between 0.0 and 1.0. - */ -Triplet lab_to_rgb(double L, double a, double b); - -Triplet cielab_to_xyz(double L, double a, double b, const Triplet& ref_white_xyz); - -/** - * Convert HSLuv to Luv. - * - * @param hsl A pointer to a buffer of length 3 containing an HSLuv color: - * [0]: Hue between 0.0 and 360.0. - * [1]: Saturation between 0.0 and 100.0. - * [2]: Lightness between 0.0 and 100.0. - * @return An LUV triplet, with luminance between 0.0 and 100.0. - */ -Triplet hsluv_to_luv(double *hsl); - -/** - * Convert Luv to HSLuv. - * - * @param l Luminance. Between 0.0 and 100.0. - * @param u U coordinate. - * @param v V coordinate. - * @return An HSLuv triplet containing: - * [0]: Hue between 0.0 and 360.0; - * [1]: Saturation between 0.0 and 100.0; - * [2]: Lightness between 0.0 and 100.0. - */ -Triplet luv_to_hsluv(double l, double u, double v); - -/** - * Convert RGB to HSLuv. - * - * @param r Red. Between 0.0 and 1.0. - * @param g Green. Between 0.0 and 1.0. - * @param b Blue. Between 0.0 and 1.0. - * @return An HSLuv triplet containing: - * [0]: Hue between 0.0 and 360.0; - * [1]: Saturation between 0.0 and 100.0; - * [2]: Lightness between 0.0 and 100.0. - */ -Triplet rgb_to_hsluv(double r, double g, double b); - -/** - * Convert HSLuv to RGB. - * - * @param h Hue. Between 0.0 and 360.0. - * @param s Saturation. Between 0.0 and 100.0. - * @param l Lightness. Between 0.0 and 100.0. - * @return An RGB triplet, with all components between 0.0 and 1.0. - */ -Triplet hsluv_to_rgb(double h, double s, double l); - -/** - * Calculate the perceptual lightness of an HSLuv color. - * - * @param l The lightness component in HSLuv coordinates, between 0.0 and 100.0. - * @return The perceptual lightness between 0.0 (black) and 1.0 (white). - */ -double perceptual_lightness(double l); - -/** - * Calculate the perceptual lightness of an RGB color. - * - * @param rgb A triplet of RGB components between 0.0 and 1.0. - * @return The perceptual lightness between 0.0 (black) and 1.0 (white). - */ -double rgb_to_perceptual_lightness(Triplet const &rgb); - -/** - * Get a contrasting grayscale color suitable for UI elements shown against - * a background color with the specified perceptual lightness. - * - * @param perceptual_lightness The perceptual lightness of the background, between 0.0 and 1.0. - * @return A pair consisting of grayscale and alpha components representing a color which will - * be easy to spot against the background. Both components are between 0.0 and 1.0. - */ -std::pair get_contrasting_color(double perceptual_lightness); - -} // namespace Hsluv - -#endif // SEEN_HSLUV_H diff --git a/src/id-clash.cpp b/src/id-clash.cpp index 44740509284a0d27bbf25c77a932cf0e1f69b448..3c1443351472b0f202619f9ab00da5a8d406f473 100644 --- a/src/id-clash.cpp +++ b/src/id-clash.cpp @@ -306,8 +306,8 @@ static void find_references(SPObject *elem, refmap_type &refmap, bool from_clipb for (unsigned i = 0; i < NUM_SPIPAINT_PROPERTIES; ++i) { const SPIPaint SPStyle::*prop = SPIPaint_members[i]; const SPIPaint *paint = &(style->*prop); - if (paint->isPaintserver() && paint->value.href) { - const SPObject *obj = paint->value.href->getObject(); + if (paint->isPaintserver() && paint->href) { + const SPObject *obj = paint->href->getObject(); if (obj) { const gchar *id = obj->getId(); IdReference idref = { REF_STYLE, elem, SPIPaint_properties[i] }; diff --git a/src/inkscape.cpp b/src/inkscape.cpp index 8be02304e7522189bb49e1c7c36500cf2f951d2f..7b23590b6ba7ee3b36b59ef912d38a76353b0208 100644 --- a/src/inkscape.cpp +++ b/src/inkscape.cpp @@ -43,7 +43,6 @@ #include "path-prefix.h" #include "selection.h" -#include "color/cms-system.h" #include "debug/simple-event.h" #include "debug/event-tracker.h" #include "io/resource.h" @@ -296,7 +295,6 @@ Application::~Application() } Inkscape::Preferences::unload(); - Inkscape::CMSSystem::unload(); _S_inst = nullptr; // this will probably break things diff --git a/src/inkscape.h b/src/inkscape.h index 361f57b31cf070e7d5d4e07cf72b39806a72fbd2..a5f0cf0698d153003f6cb0b896f94fb8e0b9e9c5 100644 --- a/src/inkscape.h +++ b/src/inkscape.h @@ -25,7 +25,6 @@ class SPDesktop; class SPDocument; -struct SPColor; namespace Inkscape { @@ -139,7 +138,6 @@ public: // these are orphaned signals (nothing emits them and nothing connects to them) sigc::signal signal_destroy_document; - sigc::signal signal_color_set; // inkscape is quitting sigc::signal signal_shut_down; diff --git a/src/io/file-export-cmd.cpp b/src/io/file-export-cmd.cpp index aeb1706560dd7a024bb33cc2a7d5437a0d47557c..34a8e1e2cb8f5dac9b8aa536ebe2762e6e271ee0 100644 --- a/src/io/file-export-cmd.cpp +++ b/src/io/file-export-cmd.cpp @@ -24,6 +24,8 @@ #include #include // PNG export +#include "colors/color.h" +#include "colors/manager.h" #include "document.h" #include "extension/db.h" #include "extension/extension.h" @@ -42,7 +44,6 @@ #include "page-manager.h" #include "path-chemistry.h" // sp_item_list_to_curves #include "selection-chemistry.h" // fit_canvas_to_drawing -#include "svg/svg-color.h" // Background color #include "text-editing.h" // te_update_layout_now_recursive #include "util/units.h" #include "util/parse-int-range.h" @@ -439,10 +440,12 @@ int InkFileExportCmd::do_export_vector(SPDocument *doc, std::string const &expor } guint32 InkFileExportCmd::get_bgcolor(SPDocument *doc) { - guint32 bgcolor = 0x00000000; + Inkscape::Colors::Color bgcolor(0xffffffff); if (!export_background.empty()) { // override the page color - bgcolor = sp_svg_read_color(export_background.c_str(), 0xffffff00); + if (auto c = Color::parse(export_background)) { + bgcolor = *c; + } // default is opaque if a color is given on commandline if (export_background_opacity < -.5 ) { export_background_opacity = 255; @@ -451,26 +454,27 @@ guint32 InkFileExportCmd::get_bgcolor(SPDocument *doc) { // read from namedview Inkscape::XML::Node *nv = doc->getReprNamedView(); if (nv && nv->attribute("pagecolor")){ - bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00); + if (auto c = Color::parse(nv->attribute("pagecolor"))) { + bgcolor = *c; + } } } if (export_background_opacity > -.5) { // if the value is manually set if (export_background_opacity > 1.0) { float value = CLAMP (export_background_opacity, 1.0f, 255.0f); - bgcolor |= (guint32) floor(value); + bgcolor.addOpacity(floor(value) / 255.0); } else { float value = CLAMP (export_background_opacity, 0.0f, 1.0f); - bgcolor |= SP_COLOR_F_TO_U(value); + bgcolor.addOpacity(value); } } else { Inkscape::XML::Node *nv = doc->getReprNamedView(); if (nv && nv->attribute("inkscape:pageopacity")){ - double opacity = nv->getAttributeDouble("inkscape:pageopacity", 1.0); - bgcolor |= SP_COLOR_F_TO_U(opacity); + bgcolor.addOpacity(nv->getAttributeDouble("inkscape:pageopacity", 1.0)); } // else it's transparent } - return bgcolor; + return bgcolor.toRGBA(); } /** diff --git a/src/layer-manager.cpp b/src/layer-manager.cpp index 1ecd0bc5f34e4c0669c26868275c793a54e53f23..2fd4e174d582a83c200b26e7b560592378207d6b 100644 --- a/src/layer-manager.cpp +++ b/src/layer-manager.cpp @@ -85,6 +85,8 @@ void LayerManager::_layer_activated(SPObject *layer) void LayerManager::_layer_deactivated(SPObject *layer) { + if (!_document) + return; // happens on destruction if (auto group = cast(layer)) { group->setLayerDisplayMode(_desktop->dkey, SPGroup::GROUP); } diff --git a/src/live_effects/fill-conversion.cpp b/src/live_effects/fill-conversion.cpp index 4d8c11560fca9d6481c3782bb9d2e7087ecdda7a..99b46a1188a4daa5091ba6ad7685c16e9beee047 100644 --- a/src/live_effects/fill-conversion.cpp +++ b/src/live_effects/fill-conversion.cpp @@ -19,7 +19,6 @@ #include "object/sp-shape.h" #include "object/sp-defs.h" #include "object/sp-paint-server.h" -#include "svg/svg-color.h" #include "svg/css-ostringstream.h" #include "style.h" #include "util/units.h" @@ -81,26 +80,23 @@ static SPObject *get_linked_fill(SPObject *source) static void convert_fill_color(SPCSSAttr *css, SPObject *source) { - char c[64]; - - sp_svg_write_color(c, sizeof(c), source->style->fill.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(source->style->fill_opacity.value))); - sp_repr_css_set_property(css, "fill", c); + auto color = source->style->fill.getColor(); + color.addOpacity(source->style->fill_opacity.value); + sp_repr_css_set_property_string(css, "fill", color.toString()); } static void convert_stroke_color(SPCSSAttr *css, SPObject *source) { - char c[64]; - - sp_svg_write_color(c, sizeof(c), source->style->stroke.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(source->style->stroke_opacity.value))); - sp_repr_css_set_property(css, "fill", c); + auto color = source->style->stroke.getColor(); + color.addOpacity(source->style->stroke_opacity.value); + sp_repr_css_set_property_string(css, "fill", color.toString()); } static void revert_stroke_color(SPCSSAttr *css, SPObject *source) { - char c[64]; - - sp_svg_write_color(c, sizeof(c), source->style->fill.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(source->style->fill_opacity.value))); - sp_repr_css_set_property(css, "stroke", c); + auto color = source->style->fill.getColor(); + color.addOpacity(source->style->fill_opacity.value); + sp_repr_css_set_property_string(css, "stroke", color.toString()); } static void convert_fill_server(SPCSSAttr *css, SPObject *source) diff --git a/src/live_effects/lpe-measure-segments.cpp b/src/live_effects/lpe-measure-segments.cpp index 2926a88802fa050dc6468597717489c9a05331a9..bc073d299d207c5487048f3bcda380b83c8c5707 100644 --- a/src/live_effects/lpe-measure-segments.cpp +++ b/src/live_effects/lpe-measure-segments.cpp @@ -46,7 +46,6 @@ #include "object/sp-shape.h" #include "object/sp-text.h" #include "svg/stringstream.h" -#include "svg/svg-color.h" #include "svg/svg.h" #include "text-editing.h" #include "ui/pack.h" @@ -73,7 +72,7 @@ LPEMeasureSegments::LPEMeasureSegments(LivePathEffectObject *lpeobject) : Effect(lpeobject), unit(_("Unit"), _("Unit of measurement"), "unit", &wr, this, "mm"), orientation(_("Orientation"), _("Orientation of the line and labels"), "orientation", OMConverter, &wr, this, OM_PARALLEL, false), - coloropacity(_("Color and opacity"), _("Set color and opacity of the dimensions"), "coloropacity", &wr, this, 0x000000ff), + coloropacity(_("Color and opacity"), _("Set color and opacity of the dimensions"), "coloropacity", &wr, this, Colors::Color(0x000000ff)), fontbutton(_("Font"), _("Select font for labels"), "fontbutton", &wr, this), precision(_("Precision"), _("Number of digits after the decimal point"), "precision", &wr, this, 2), fix_overlaps(_("Merge overlaps °"), _("Minimum angle at which overlapping dimension lines are merged into one, use 180° to disable merging"), "fix_overlaps", &wr, this, 0), @@ -108,6 +107,7 @@ LPEMeasureSegments::LPEMeasureSegments(LivePathEffectObject *lpeobject) : centers(_("Add object center"), _("Add the projected object center"), "centers", &wr, this, false), maxmin(_("Only max and min"), _("Compute only max/min projection values"), "maxmin", &wr, this, false), helpdata(_("Help"), _("Measure segments help"), "helpdata", &wr, this, "", "") + , color(0x000000ff) { //set to true the parameters you want to be changed his default values registerParameter(&unit); @@ -192,7 +192,6 @@ LPEMeasureSegments::LPEMeasureSegments(LivePathEffectObject *lpeobject) : pagenumber = 0; anotation_width = 0; fontsize = 0; - rgb32 = 0; arrow_gap = 0; } @@ -290,12 +289,12 @@ LPEMeasureSegments::createArrowMarker(Glib::ustring mode) } Glib::ustring lpobjid = this->lpeobj->getId(); Glib::ustring itemid = sp_lpe_item->getId(); - Glib::ustring style; - style = Glib::ustring("fill:context-stroke;"); - Inkscape::SVGOStringStream os; - os << SP_RGBA32_A_F(coloropacity.get_value()); - style = style + Glib::ustring(";fill-opacity:") + Glib::ustring(os.str()); - style = style + Glib::ustring(";stroke:none"); + + auto const css = sp_repr_css_attr_new(); + sp_repr_css_set_property_string(css, "fill", "context-stroke"); + sp_repr_css_set_property_double(css, "fill-opacity", coloropacity.get_value()->getOpacity()); + sp_repr_css_set_property_string(css, "stroke", "none"); + Inkscape::XML::Document *xml_doc = document->getReprDoc(); SPObject *elemref = nullptr; Inkscape::XML::Node *arrow = nullptr; @@ -307,7 +306,7 @@ LPEMeasureSegments::createArrowMarker(Glib::ustring mode) Inkscape::XML::Node *arrow_data = arrow->firstChild(); if (arrow_data) { arrow_data->removeAttribute("transform"); - arrow_data->setAttribute("style", style); + sp_repr_css_change(arrow_data, css, "style"); } } } else { @@ -342,12 +341,13 @@ LPEMeasureSegments::createArrowMarker(Glib::ustring mode) arrow_path->setAttributeOrRemoveIfEmpty("class", classarrowpath); Glib::ustring arrowpath = mode + Glib::ustring("_path"); arrow_path->setAttribute("id", arrowpath); - arrow_path->setAttribute("style", style); + sp_repr_css_change(arrow_path, css, "style"); arrow->addChild(arrow_path, nullptr); Inkscape::GC::release(arrow_path); elemref = document->getDefs()->appendChildRepr(arrow); Inkscape::GC::release(arrow); } + sp_repr_css_attr_unref(css); items.push_back(mode); } @@ -418,21 +418,15 @@ LPEMeasureSegments::createTextLabel(Geom::Point &pos, size_t counter, double len setlocale (LC_NUMERIC, "C"); font_size << fontsize << "px"; setlocale (LC_NUMERIC, locale_base); - gchar c[32]; - safeprintf(c, "#%06x", rgb32 >> 8); - sp_repr_css_set_property (css, "fill",c); - Inkscape::SVGOStringStream os; - os << SP_RGBA32_A_F(coloropacity.get_value()); - sp_repr_css_set_property (css, "fill-opacity",os.str().c_str()); + sp_repr_css_set_property_string(css, "fill", color.toString(false)); + sp_repr_css_set_property_double(css, "fill-opacity", color.getOpacity()); sp_repr_css_set_property (css, "font-size",font_size.str().c_str()); sp_repr_css_unset_property (css, "-inkscape-font-specification"); if (remove) { sp_repr_css_set_property (css, "display","none"); } - Glib::ustring css_str; - sp_repr_css_write_string(css,css_str); - rtext->setAttributeOrRemoveIfEmpty("style", css_str); - rtspan->setAttributeOrRemoveIfEmpty("style", css_str); + sp_repr_css_change(rtext, css, "style"); + sp_repr_css_change(rtspan, css, "style"); rtspan->removeAttribute("transform"); sp_repr_css_attr_unref (css); if (legacy) { @@ -605,23 +599,18 @@ LPEMeasureSegments::createLine(Geom::Point start,Geom::Point end, Glib::ustring setlocale (LC_NUMERIC, locale_base); style += "stroke-width:"; style += stroke_w.str(); - gchar c[32]; - safeprintf(c, "#%06x", rgb32 >> 8); - style += ";stroke:"; - style += Glib::ustring(c); - Inkscape::SVGOStringStream os; - os << SP_RGBA32_A_F(coloropacity.get_value()); - style = style + Glib::ustring(";stroke-opacity:") + Glib::ustring(os.str()); + SPCSSAttr *css = sp_repr_css_attr_new(); sp_repr_css_attr_add_from_string(css, style.c_str()); - Glib::ustring css_str; - sp_repr_css_write_string(css,css_str); - line->setAttributeOrRemoveIfEmpty("style", css_str); + sp_repr_css_set_property_string(css, "stroke", color.toString(false)); + sp_repr_css_set_property_double(css, "stroke-opacity", color.getOpacity()); + sp_repr_css_change(line, css, "style"); + sp_repr_css_attr_unref (css); + if (!elemref) { elemref = document->getRoot()->appendChildRepr(line); Inkscape::GC::release(line); } - sp_repr_css_attr_unref (css); } void @@ -965,12 +954,12 @@ LPEMeasureSegments::doBeforeEffect (SPLPEItem const* lpeitem) //end projection prepare auto shape = cast(splpeitem); if (shape) { - guint32 color32 = coloropacity.get_value(); + auto opacity = coloropacity.get_value(); bool colorchanged = false; - if (color32 != rgb32) { + if (opacity != color) { colorchanged = true; } - rgb32 = std::move(color32); + color = *opacity; bool unitchanged = false; Glib::ustring currentunit = unit.get_abbreviation(); if (currentunit != prevunit) { diff --git a/src/live_effects/lpe-measure-segments.h b/src/live_effects/lpe-measure-segments.h index b0d9ccc3984829cd278f5f18b6b761a49c526ca3..575f9186a582beeef82a0bf89e1d185e5fc15651 100644 --- a/src/live_effects/lpe-measure-segments.h +++ b/src/live_effects/lpe-measure-segments.h @@ -93,12 +93,12 @@ private: ScalarParam angle_projection; BoolParam avoid_overlapping; MessageParam helpdata; + Colors::Color color; bool legacy = false; double fontsize; double prevfontsize = 0; double anotation_width; double previous_size; - guint32 rgb32; Glib::ustring prevunit = ""; double arrow_gap; guint pagenumber; diff --git a/src/live_effects/lpe-powermask.cpp b/src/live_effects/lpe-powermask.cpp index 63e748abeb3c8bc0c53fc9830329abcb26d45d6a..a069559c25c956228fb271ada722aee2a70ab6c6 100644 --- a/src/live_effects/lpe-powermask.cpp +++ b/src/live_effects/lpe-powermask.cpp @@ -35,7 +35,7 @@ LPEPowerMask::LPEPowerMask(LivePathEffectObject *lpeobject) //wrap(_("Wrap mask data"), _("Wrap mask data allowing previous filters"), "wrap", &wr, this, false), hide_mask(_("Hide mask"), _("Hide mask"), "hide_mask", &wr, this, false), background(_("Add background to mask"), _("Add background to mask"), "background", &wr, this, false), - background_color(_("Background color and opacity"), _("Set color and opacity of the background"), "background_color", &wr, this, 0xffffffff) + background_color(_("Background color and opacity"), _("Set color and opacity of the background"), "background_color", &wr, this, Colors::Color(0xffffffff)) { registerParameter(&uri); registerParameter(&invert); @@ -115,8 +115,8 @@ LPEPowerMask::doBeforeEffect (SPLPEItem const* lpeitem){ } mask = sp_lpe_item->getMaskObject(); if (mask) { - if (previous_color != background_color.get_value()) { - previous_color = background_color.get_value(); + if (previous_color != *background_color.get_value()) { + previous_color = *background_color.get_value(); setMask(); } else { uri.param_setValue(Glib::ustring(extract_uri(sp_lpe_item->getAttribute("mask"))), true); @@ -237,16 +237,12 @@ LPEPowerMask::setMask(){ box->setAttribute("id", box_id); exist = false; } - Glib::ustring style; - gchar c[32]; - unsigned const rgb24 = background_color.get_value() >> 8; - safeprintf(c, "#%06x", rgb24); - style = Glib::ustring("fill:") + Glib::ustring(c); - Inkscape::SVGOStringStream os; - os << SP_RGBA32_A_F(background_color.get_value()); - style = style + Glib::ustring(";fill-opacity:") + Glib::ustring(os.str()); - SPCSSAttr *css = sp_repr_css_attr_new(); - sp_repr_css_attr_add_from_string(css, style.c_str()); + + auto const css = sp_repr_css_attr_new(); + sp_repr_css_set_property_string(css, "fill", background_color.get_value()->toString(false)); + sp_repr_css_set_property_double(css, "fill-opacity", background_color.get_value()->getOpacity()); + sp_repr_css_set_property_string(css, "stroke", "none"); + char const* filter = sp_repr_css_property (css, "filter", nullptr); if(!filter || !strcmp(filter, filter_uri.c_str())) { if (invert && is_visible) { @@ -254,11 +250,9 @@ LPEPowerMask::setMask(){ } else { sp_repr_css_set_property (css, "filter", nullptr); } - } - Glib::ustring css_str; - sp_repr_css_write_string(css, css_str); - box->setAttributeOrRemoveIfEmpty("style", css_str); + sp_repr_css_change(box, css, "style"); + sp_repr_css_attr_unref(css); box->setAttribute("d", sp_svg_write_path(mask_box)); if (!exist) { elemref = mask->appendChildRepr(box); diff --git a/src/live_effects/lpe-powermask.h b/src/live_effects/lpe-powermask.h index aed32fc9945e2c8885dc3552fd635362cba2fd58..6bd72f79d56e846333feb7f1bd878327d3e5e240 100644 --- a/src/live_effects/lpe-powermask.h +++ b/src/live_effects/lpe-powermask.h @@ -36,8 +36,8 @@ private: BoolParam hide_mask; BoolParam background; ColorPickerParam background_color; + std::optional previous_color; Geom::Path mask_box; - guint32 previous_color; }; void sp_remove_powermask(Inkscape::Selection *sel); diff --git a/src/live_effects/parameter/colorpicker.cpp b/src/live_effects/parameter/colorpicker.cpp index 16c7e20f1bc3fda71e8ab5989025b30b68bfc578..ae53315d543cf0d1ad5df7061d0ba3f531186b9e 100644 --- a/src/live_effects/parameter/colorpicker.cpp +++ b/src/live_effects/parameter/colorpicker.cpp @@ -13,7 +13,6 @@ #include "document-undo.h" // for DocumentUndo #include "live_effects/effect.h" // for Effect #include "live_effects/parameter/colorpicker.h" // for ColorPickerParam -#include "svg/svg-color.h" // for guint32 #include "ui/icon-names.h" // for INKSCAPE_ICON #include "ui/pack.h" // for pack_start #include "ui/widget/registered-widget.h" // for RegisteredColorPicker @@ -26,7 +25,7 @@ namespace LivePathEffect { ColorPickerParam::ColorPickerParam( const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr, - Effect* effect, const guint32 default_color ) + Effect* effect, std::optional default_color) : Parameter(label, tip, key, wr, effect), value(default_color), defvalue(default_color) @@ -39,61 +38,29 @@ ColorPickerParam::param_set_default() param_setValue(defvalue); } -static guint32 sp_read_color_alpha(gchar const *str, guint32 def) -{ - guint32 val = 0; - if (str == nullptr) return def; - while ((*str <= ' ') && *str) str++; - if (!*str) return def; - - if (str[0] == '#') { - gint i; - for (i = 1; str[i]; i++) { - int hexval; - if (str[i] >= '0' && str[i] <= '9') - hexval = str[i] - '0'; - else if (str[i] >= 'A' && str[i] <= 'F') - hexval = str[i] - 'A' + 10; - else if (str[i] >= 'a' && str[i] <= 'f') - hexval = str[i] - 'a' + 10; - else - break; - val = (val << 4) + hexval; - } - if (i != 1 + 8) { - return def; - } - } - return val; -} - void ColorPickerParam::param_update_default(const gchar * default_value) { - defvalue = sp_read_color_alpha(default_value, 0x000000ff); + defvalue->set(default_value ? default_value : ""); } bool -ColorPickerParam::param_readSVGValue(const gchar * strvalue) +ColorPickerParam::param_readSVGValue(const gchar *val) { - param_setValue(sp_read_color_alpha(strvalue, 0x000000ff)); + param_setValue(Colors::Color::parse(val)); return true; } Glib::ustring ColorPickerParam::param_getSVGValue() const { - gchar c[32]; - safeprintf(c, "#%08x", value); - return c; + return value->toString(); } Glib::ustring ColorPickerParam::param_getDefaultSVGValue() const { - gchar c[32]; - safeprintf(c, "#%08x", defvalue); - return c; + return defvalue->toString(); } Gtk::Widget * @@ -114,7 +81,7 @@ ColorPickerParam::param_newWidget() { SPDocument *document = param_effect->getSPDoc(); DocumentUndo::ScopedInsensitive _no_undo(document); - colorpickerwdg->setRgba32(value); + colorpickerwdg->setColor(*value); } colorpickerwdg->set_undo_parameters(_("Change color button parameter"), INKSCAPE_ICON("dialog-path-effects")); @@ -124,7 +91,7 @@ ColorPickerParam::param_newWidget() } void -ColorPickerParam::param_setValue(const guint32 newvalue) +ColorPickerParam::param_setValue(std::optional newvalue) { value = newvalue; } diff --git a/src/live_effects/parameter/colorpicker.h b/src/live_effects/parameter/colorpicker.h index ff7abe0e5df3540420fabe15c39a57d237dac553..3f0416b015259b7f17836e540ae2f683ab79955b 100644 --- a/src/live_effects/parameter/colorpicker.h +++ b/src/live_effects/parameter/colorpicker.h @@ -22,7 +22,7 @@ public: const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr, Effect* effect, - const guint32 default_color = 0x000000ff); + std::optional = {}); ~ColorPickerParam() override = default; Gtk::Widget * param_newWidget() override; @@ -31,18 +31,18 @@ public: Glib::ustring param_getSVGValue() const override; Glib::ustring param_getDefaultSVGValue() const override; - void param_setValue(guint32 newvalue); + void param_setValue(std::optional newvalue); void param_set_default() override; - guint32 get_value() const { return value; }; + std::optional get_value() const { return value; }; ParamType paramType() const override { return ParamType::COLOR_PICKER; }; private: ColorPickerParam(const ColorPickerParam&) = delete; ColorPickerParam& operator=(const ColorPickerParam&) = delete; - guint32 value; - guint32 defvalue; + std::optional value; + std::optional defvalue; }; } //namespace LivePathEffect diff --git a/src/object/color-profile.cpp b/src/object/color-profile.cpp index 8e5dda9fc52aea672ff06c03e0384403575a4468..79996837f5144ee620224b3efaaa4fab69e52e93 100644 --- a/src/object/color-profile.cpp +++ b/src/object/color-profile.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /** @file - * TODO: insert short description here + * The color profile tag in an svg document *//* * Authors: see git history * @@ -11,481 +11,192 @@ #include "color-profile.h" #include - -#include // for g_free, guchar, g_utf8_case... - -#ifdef _WIN32 -#undef NOGDI -#include -#include -#endif - -#include +#include #include "attributes.h" -#include "color.h" +#include "colors/cms/profile.h" #include "document.h" #include "inkscape.h" #include "preferences.h" +#include "sp-defs.h" #include "uri.h" - -#include "color/color-profile-cms-fns.h" - #include "xml/document.h" #include "xml/href-attribute-helper.h" -using Inkscape::ColorProfile; -using Inkscape::ColorProfileImpl; - namespace Inkscape { -class ColorProfileImpl { -public: - static cmsHPROFILE _sRGBProf; - static cmsHPROFILE _NullProf; - - ColorProfileImpl(); - - static cmsUInt32Number _getInputFormat( cmsColorSpaceSignature space ); - - static cmsHPROFILE getNULLProfile(); - static cmsHPROFILE getSRGBProfile(); - - void _clearProfile(); - - cmsHPROFILE _profHandle; - cmsProfileClassSignature _profileClass; - cmsColorSpaceSignature _profileSpace; - cmsHTRANSFORM _transf; - cmsHTRANSFORM _revTransf; - cmsHTRANSFORM _gamutTransf; -}; - -cmsColorSpaceSignature asICColorSpaceSig(ColorSpaceSig const & sig) +void ColorProfile::release() { - return ColorSpaceSigWrapper(sig); -} - -cmsProfileClassSignature asICColorProfileClassSig(ColorProfileClassSig const & sig) -{ - return ColorProfileClassSigWrapper(sig); -} - -} // namespace Inkscape - -ColorProfileImpl::ColorProfileImpl() - : - _profHandle(nullptr), - _profileClass(cmsSigInputClass), - _profileSpace(cmsSigRgbData), - _transf(nullptr), - _revTransf(nullptr), - _gamutTransf(nullptr) -{ -} - - -cmsHPROFILE ColorProfileImpl::_sRGBProf = nullptr; - -cmsHPROFILE ColorProfileImpl::getSRGBProfile() { - if ( !_sRGBProf ) { - _sRGBProf = cmsCreate_sRGBProfile(); - } - return ColorProfileImpl::_sRGBProf; -} - -cmsHPROFILE ColorProfileImpl::_NullProf = nullptr; - -cmsHPROFILE ColorProfileImpl::getNULLProfile() { - if ( !_NullProf ) { - _NullProf = cmsCreateNULLProfile(); - } - return _NullProf; -} - -ColorProfile::ColorProfile() : SPObject() { - this->impl = new ColorProfileImpl(); - - this->href = nullptr; - this->local = nullptr; - this->name = nullptr; - this->intentStr = nullptr; - this->rendering_intent = Inkscape::RENDERING_INTENT_UNKNOWN; -} - -ColorProfile::~ColorProfile() = default; - -bool ColorProfile::operator<(ColorProfile const &other) const { - gchar *a_name_casefold = g_utf8_casefold(this->name, -1 ); - gchar *b_name_casefold = g_utf8_casefold(other.name, -1 ); - int result = g_strcmp0(a_name_casefold, b_name_casefold); - g_free(a_name_casefold); - g_free(b_name_casefold); - return result < 0; -} - -/** - * Callback: free object - */ -void ColorProfile::release() { // Unregister ourselves - if ( this->document ) { + if (this->document) { this->document->removeResource("iccprofile", this); } - if ( this->href ) { - g_free( this->href ); - this->href = nullptr; - } - - if ( this->local ) { - g_free( this->local ); - this->local = nullptr; - } - - if ( this->name ) { - g_free( this->name ); - this->name = nullptr; - } - - if ( this->intentStr ) { - g_free( this->intentStr ); - this->intentStr = nullptr; - } - - this->impl->_clearProfile(); - - delete this->impl; - this->impl = nullptr; - SPObject::release(); } -void ColorProfileImpl::_clearProfile() -{ - _profileSpace = cmsSigRgbData; - - if ( _transf ) { - cmsDeleteTransform( _transf ); - _transf = nullptr; - } - if ( _revTransf ) { - cmsDeleteTransform( _revTransf ); - _revTransf = nullptr; - } - if ( _gamutTransf ) { - cmsDeleteTransform( _gamutTransf ); - _gamutTransf = nullptr; - } - if ( _profHandle ) { - cmsCloseProfile( _profHandle ); - _profHandle = nullptr; - } -} - /** * Callback: set attributes from associated repr. */ -void ColorProfile::build(SPDocument *document, Inkscape::XML::Node *repr) { - g_assert(this->href == nullptr); - g_assert(this->local == nullptr); - g_assert(this->name == nullptr); - g_assert(this->intentStr == nullptr); - +void ColorProfile::build(SPDocument *document, Inkscape::XML::Node *repr) +{ SPObject::build(document, repr); this->readAttr(SPAttr::XLINK_HREF); - this->readAttr(SPAttr::ID); this->readAttr(SPAttr::LOCAL); this->readAttr(SPAttr::NAME); this->readAttr(SPAttr::RENDERING_INTENT); // Register - if ( document ) { - document->addResource( "iccprofile", this ); + if (document) { + document->addResource("iccprofile", this); } } - /** * Callback: set attribute. */ -void ColorProfile::set(SPAttr key, gchar const *value) { +void ColorProfile::set(SPAttr key, gchar const *value) +{ switch (key) { case SPAttr::XLINK_HREF: - if ( this->href ) { - g_free( this->href ); - this->href = nullptr; - } - if ( value ) { - this->href = g_strdup( value ); - if ( *this->href ) { - - // TODO open filename and URIs properly - //FILE* fp = fopen_utf8name( filename, "r" ); - //LCMSAPI cmsHPROFILE LCMSEXPORT cmsOpenProfileFromMem(LPVOID MemPtr, cmsUInt32Number dwSize); - - // Try to open relative - SPDocument *doc = this->document; - if (!doc) { - doc = SP_ACTIVE_DOCUMENT; - g_warning("this has no document. using active"); - } - //# 1. Get complete filename of document - gchar const *docbase = doc->getDocumentFilename(); - - Inkscape::URI docUri(""); - if (docbase) { // The file has already been saved - docUri = Inkscape::URI::from_native_filename(docbase); - } - - this->impl->_clearProfile(); - - try { - auto hrefUri = Inkscape::URI(this->href, docUri); - auto contents = hrefUri.getContents(); - this->impl->_profHandle = cmsOpenProfileFromMem(contents.data(), contents.size()); - } catch (...) { - g_warning("Failed to open CMS profile URI '%.100s'", this->href); - } - - if ( this->impl->_profHandle ) { - this->impl->_profileSpace = cmsGetColorSpace( this->impl->_profHandle ); - this->impl->_profileClass = cmsGetDeviceClass( this->impl->_profHandle ); - } - } + // Href is the filename or the data of the icc profile itself and is used before local + if (value) { + auto fn = document->getDocumentFilename(); + _uri = std::make_unique(value, fn ? ("file://" + std::string(fn)).c_str() : nullptr); + } else { + _uri.reset(); } - this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SPAttr::LOCAL: - if ( this->local ) { - g_free( this->local ); - this->local = nullptr; - } - this->local = g_strdup( value ); - this->requestModified(SP_OBJECT_MODIFIED_FLAG); + // Local is the ID of the profile as a hex string. Provided by Colors::CMS::Profile::getId() + // it's only used if the href isn't set or isn't found on this system in the specified place + _local = value ? value : ""; break; case SPAttr::NAME: - if ( this->name ) { - g_free( this->name ); - this->name = nullptr; - } - this->name = g_strdup( value ); - this->requestModified(SP_OBJECT_MODIFIED_FLAG); + // Name is used by the icc-color format to match this profile to a color. It over-rides the + // name given in the icc profile if it's provided. + _name = value ? value : ""; break; case SPAttr::RENDERING_INTENT: - if ( this->intentStr ) { - g_free( this->intentStr ); - this->intentStr = nullptr; - } - this->intentStr = g_strdup( value ); - - if ( value ) { - if ( strcmp( value, "auto" ) == 0 ) { - this->rendering_intent = RENDERING_INTENT_AUTO; - } else if ( strcmp( value, "perceptual" ) == 0 ) { - this->rendering_intent = RENDERING_INTENT_PERCEPTUAL; - } else if ( strcmp( value, "relative-colorimetric" ) == 0 ) { - this->rendering_intent = RENDERING_INTENT_RELATIVE_COLORIMETRIC; - } else if ( strcmp( value, "saturation" ) == 0 ) { - this->rendering_intent = RENDERING_INTENT_SATURATION; - } else if ( strcmp( value, "absolute-colorimetric" ) == 0 ) { - this->rendering_intent = RENDERING_INTENT_ABSOLUTE_COLORIMETRIC; - } else { - this->rendering_intent = RENDERING_INTENT_UNKNOWN; + // There is a standard set of rendering intents, the default fallback intent is decided in the + // color CMS system and not here. + _intent = Colors::RenderingIntent::UNKNOWN; + if (value) { + for (auto &pair : Colors::intentIds) { + if (pair.second == value) { + _intent = pair.first; + break; + } } - } else { - this->rendering_intent = RENDERING_INTENT_UNKNOWN; } - - this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; default: - SPObject::set(key, value); - break; + return SPObject::set(key, value); } + this->requestModified(SP_OBJECT_MODIFIED_FLAG); } /** * Callback: write attributes to associated repr. */ -Inkscape::XML::Node* ColorProfile::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { +Inkscape::XML::Node *ColorProfile::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) +{ if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { repr = xml_doc->createElement("svg:color-profile"); } - if ( (flags & SP_OBJECT_WRITE_ALL) || this->href ) { - Inkscape::setHrefAttribute(*repr, this->href ); - } - - if ( (flags & SP_OBJECT_WRITE_ALL) || this->local ) { - repr->setAttribute( "local", this->local ); - } - - if ( (flags & SP_OBJECT_WRITE_ALL) || this->name ) { - repr->setAttribute( "name", this->name ); + if ((flags & SP_OBJECT_WRITE_ALL) || _uri) { + auto fn = document->getDocumentFilename(); + Inkscape::setHrefAttribute(*repr, fn ? _uri->str(("file://" + std::string(fn)).c_str()) : nullptr); } - if ( (flags & SP_OBJECT_WRITE_ALL) || this->intentStr ) { - repr->setAttribute( "rendering-intent", this->intentStr ); - } + repr->setAttributeOrRemoveIfEmpty("local", _local); + repr->setAttributeOrRemoveIfEmpty("name", _name); + repr->setAttributeOrRemoveIfEmpty("rendering-intent", Colors::intentIds[_intent]); SPObject::write(xml_doc, repr, flags); - return repr; } - -struct MapMap { - cmsColorSpaceSignature space; - cmsUInt32Number inForm; -}; - -cmsUInt32Number ColorProfileImpl::_getInputFormat( cmsColorSpaceSignature space ) +/** + * Return the profile data, if any. Returns empty string if none + * is available. + */ +std::string ColorProfile::getProfileData() const { - MapMap possible[] = { - {cmsSigXYZData, TYPE_XYZ_16}, - {cmsSigLabData, TYPE_Lab_16}, - //cmsSigLuvData - {cmsSigYCbCrData, TYPE_YCbCr_16}, - {cmsSigYxyData, TYPE_Yxy_16}, - {cmsSigRgbData, TYPE_RGB_16}, - {cmsSigGrayData, TYPE_GRAY_16}, - {cmsSigHsvData, TYPE_HSV_16}, - {cmsSigHlsData, TYPE_HLS_16}, - {cmsSigCmykData, TYPE_CMYK_16}, - {cmsSigCmyData, TYPE_CMY_16}, - }; - - int index = 0; - for ( guint i = 0; i < G_N_ELEMENTS(possible); i++ ) { - if ( possible[i].space == space ) { - index = i; - break; + // Note: The returned data could be Megabytes in length, but we're + // copying the data. We should find a way to pass the const string back + if (_uri) { + try { + return _uri->getContents(); + } catch (const Gio::Error &e) { + g_warning("Couldn't get color profile: %s", e.what()); } } - - return possible[index].inForm; -} - -static int getLcmsIntent( guint svgIntent ) -{ - int intent = INTENT_PERCEPTUAL; - switch ( svgIntent ) { - case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC: - intent = INTENT_RELATIVE_COLORIMETRIC; - break; - case Inkscape::RENDERING_INTENT_SATURATION: - intent = INTENT_SATURATION; - break; - case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC: - intent = INTENT_ABSOLUTE_COLORIMETRIC; - break; - case Inkscape::RENDERING_INTENT_PERCEPTUAL: - case Inkscape::RENDERING_INTENT_UNKNOWN: - case Inkscape::RENDERING_INTENT_AUTO: - default: - intent = INTENT_PERCEPTUAL; - } - return intent; -} - -Inkscape::ColorSpaceSig ColorProfile::getColorSpace() const { - return ColorSpaceSigWrapper(impl->_profileSpace); -} - -Inkscape::ColorProfileClassSig ColorProfile::getProfileClass() const { - return ColorProfileClassSigWrapper(impl->_profileClass); + return ""; } -cmsHTRANSFORM ColorProfile::getTransfToSRGB8() +/** + * Set the rendering intent for this color profile. + */ +void ColorProfile::setRenderingIntent(Colors::RenderingIntent intent) { - if ( !impl->_transf && impl->_profHandle ) { - int intent = getLcmsIntent(rendering_intent); - impl->_transf = cmsCreateTransform( impl->_profHandle, ColorProfileImpl::_getInputFormat(impl->_profileSpace), ColorProfileImpl::getSRGBProfile(), TYPE_RGBA_8, intent, 0 ); - } - return impl->_transf; + setAttribute("rendering-intent", Colors::intentIds[intent]); } -cmsHTRANSFORM ColorProfile::getTransfFromSRGB8() +/** + * Create a profile for the given profile in the given document. + * + * @args doc - The SPDocument to add this profile into, creating a new color profile element in it's defs. + * @args profile - The color profile object to use as the data source + * @args name - The name to use, this over-rides the name in the profile + * @args storage - This sets the prefered data source. + * - HREF_DATA - The profile is embeded as a base64 encoded stream. + * - HREF_FILE - The href is a relative or absolute link to the icc profile file. + * the profile MUST be a file. If the document has a file and the path is close + * to the icc profile, it will be relative. + * - LOCAL_ID - The profile's unique id will be stored, no href will be added. + */ +ColorProfile *ColorProfile::createFromProfile(SPDocument *doc, Colors::CMS::Profile const &profile, + std::string const &name, ColorProfileStorage storage) { - if ( !impl->_revTransf && impl->_profHandle ) { - int intent = getLcmsIntent(rendering_intent); - impl->_revTransf = cmsCreateTransform( ColorProfileImpl::getSRGBProfile(), TYPE_RGBA_8, impl->_profHandle, ColorProfileImpl::_getInputFormat(impl->_profileSpace), intent, 0 ); + if (name.empty()) { + g_error("Refusing to create a color profile with an empty name!"); + return nullptr; } - return impl->_revTransf; -} - -cmsHTRANSFORM ColorProfile::getTransfGamutCheck() -{ - if ( !impl->_gamutTransf ) { - impl->_gamutTransf = cmsCreateProofingTransform(ColorProfileImpl::getSRGBProfile(), - TYPE_BGRA_8, - ColorProfileImpl::getNULLProfile(), - TYPE_GRAY_8, - impl->_profHandle, - INTENT_RELATIVE_COLORIMETRIC, - INTENT_RELATIVE_COLORIMETRIC, - (cmsFLAGS_GAMUTCHECK | cmsFLAGS_SOFTPROOFING)); + if (storage == ColorProfileStorage::HREF_FILE && profile.getPath().empty()) { + storage = ColorProfileStorage::HREF_DATA; // fallback to data } - return impl->_gamutTransf; -} - -// Check if a particular color is out of gamut. -bool ColorProfile::GamutCheck(SPColor color) -{ - guint32 val = color.toRGBA32(0); - cmsUInt16Number oldAlarmCodes[cmsMAXCHANNELS] = {0}; - cmsGetAlarmCodes(oldAlarmCodes); - cmsUInt16Number newAlarmCodes[cmsMAXCHANNELS] = {0}; - newAlarmCodes[0] = ~0; - cmsSetAlarmCodes(newAlarmCodes); + // Create new object and attach it to the document + auto repr = doc->getReprDoc()->createElement("svg:color-profile"); - cmsUInt8Number outofgamut = 0; - guchar check_color[4] = { - static_cast(SP_RGBA32_R_U(val)), - static_cast(SP_RGBA32_G_U(val)), - static_cast(SP_RGBA32_B_U(val)), - 255}; + // It's expected that the color manager will hace checked for collisions before this call. + repr->setAttributeOrRemoveIfEmpty("name", name); - cmsHTRANSFORM gamutCheck = ColorProfile::getTransfGamutCheck(); - if (gamutCheck) { - cmsDoTransform(gamutCheck, &check_color, &outofgamut, 1); + switch (storage) { + case ColorProfileStorage::LOCAL_ID: + repr->setAttributeOrRemoveIfEmpty("local", profile.getId()); + break; + case ColorProfileStorage::HREF_DATA: + Inkscape::setHrefAttribute(*repr, "data:application/vnd.iccprofile;base64," + profile.dumpBase64()); + break; + case ColorProfileStorage::HREF_FILE: { + auto uri = Inkscape::URI::from_native_filename(profile.getPath().c_str()); + auto fn = doc->getDocumentFilename(); + Inkscape::setHrefAttribute(*repr, fn ? (uri.str((std::string("file://") + fn).c_str())) : nullptr); + } break; } - - cmsSetAlarmCodes(oldAlarmCodes); - - return (outofgamut != 0); -} - -gint ColorProfile::getChannelCount() const -{ - return cmsChannelsOf(asICColorSpaceSig(getColorSpace())); -} - -bool ColorProfile::isPrintColorSpace() -{ - ColorSpaceSigWrapper colorspace = getColorSpace(); - return (colorspace == cmsSigCmykData) || (colorspace == cmsSigCmyData); -} - -cmsHPROFILE ColorProfile::getHandle() -{ - return impl->_profHandle; -} - -void errorHandlerCB(cmsContext /*contextID*/, cmsUInt32Number errorCode, char const *errorText) -{ - g_message("lcms: Error %d", errorCode); - g_message(" %p", errorText); - //g_message("lcms: Error %d; %s", errorCode, errorText); + // Complete the creation by appending to the defs. This must be done last. + return cast(doc->getDefs()->appendChildRepr(repr)); } +} // namespace Inkscape /* Local Variables: diff --git a/src/object/color-profile.h b/src/object/color-profile.h index 0e93e04ae33e2f13e1241ca7012e8b8ce4ff0295..96c6f54b04b43e38240f03c7fbeaf3e54a16bf29 100644 --- a/src/object/color-profile.h +++ b/src/object/color-profile.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /** @file - * TODO: insert short description here + * SPObject of the color-profile object found a direct child of defs. *//* * Authors: see git history * @@ -10,72 +10,54 @@ #ifndef SEEN_COLOR_PROFILE_H #define SEEN_COLOR_PROFILE_H -#ifdef HAVE_CONFIG_H -# include "config.h" // only include where actually required! -#endif - -#include -#include - -#include - +#include "colors/cms/system.h" +#include "colors/spaces/enum.h" // RenderingIntent #include "sp-object.h" -#include "color/cms-color-types.h" - -struct SPColor; namespace Inkscape { -enum { - RENDERING_INTENT_UNKNOWN = 0, - RENDERING_INTENT_AUTO = 1, - RENDERING_INTENT_PERCEPTUAL = 2, - RENDERING_INTENT_RELATIVE_COLORIMETRIC = 3, - RENDERING_INTENT_SATURATION = 4, - RENDERING_INTENT_ABSOLUTE_COLORIMETRIC = 5 -}; - -class ColorProfileImpl; +class URI; +enum class ColorProfileStorage +{ + HREF_DATA, + HREF_FILE, + LOCAL_ID, +}; -/** - * Color Profile. - */ -class ColorProfile final : public SPObject { +class ColorProfile final : public SPObject +{ public: - ColorProfile(); - ~ColorProfile() override; + ColorProfile() = default; + ~ColorProfile() override = default; int tag() const override { return tag_of; } - bool operator<(ColorProfile const &other) const; - - ColorSpaceSig getColorSpace() const; - ColorProfileClassSig getProfileClass() const; - cmsHTRANSFORM getTransfToSRGB8(); - cmsHTRANSFORM getTransfFromSRGB8(); - cmsHTRANSFORM getTransfGamutCheck(); - bool GamutCheck(SPColor color); - int getChannelCount() const; - bool isPrintColorSpace(); - cmsHPROFILE getHandle(); + static ColorProfile *createFromProfile(SPDocument *doc, Colors::CMS::Profile const &profile, + std::string const &name, ColorProfileStorage storage); + std::string getName() const { return _name; } + std::string getLocalProfileId() const { return _local; } + std::string getProfileData() const; + Colors::RenderingIntent getRenderingIntent() const { return _intent; } - // TODO: Make private - char* href; - char* local; - char* name; - char* intentStr; - unsigned int rendering_intent; // FIXME: type the enum and hold that instead + // This is the only variable we expect inkscape to modify. Changing the icc + // profile data or ID should instead involve creating a new ColorProfile element. + void setRenderingIntent(Colors::RenderingIntent intent); protected: - ColorProfileImpl *impl; - - void build(SPDocument* doc, Inkscape::XML::Node* repr) override; + void build(SPDocument *doc, Inkscape::XML::Node *repr) override; void release() override; - void set(SPAttr key, char const* value) override; + void set(SPAttr key, char const *value) override; + + Inkscape::XML::Node *write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags) override; + +private: + std::string _name; + std::string _local; + Colors::RenderingIntent _intent; - Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags) override; + std::unique_ptr _uri; }; } // namespace Inkscape diff --git a/src/object/filters/diffuselighting.cpp b/src/object/filters/diffuselighting.cpp index 4d462b2fc65ac2ddeaf8c24e6c7fda507004b887..b6a0793c7e96fb1e19ea0dacf8a5cab0a809238a 100644 --- a/src/object/filters/diffuselighting.cpp +++ b/src/object/filters/diffuselighting.cpp @@ -28,7 +28,6 @@ #include "display/nr-light-types.h" // for SpotLightData, Light... #include "object/filters/sp-filter-primitive.h" // for SPFilterPrimitive #include "object/sp-object.h" // for SP_OBJECT_MODIFIED_FLAG -#include "svg/svg-color.h" // for sp_svg_read_color #include "xml/node.h" // for Node class SPDocument; @@ -104,28 +103,7 @@ void SPFeDiffuseLighting::set(SPAttr key, char const *value) requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SPAttr::LIGHTING_COLOR: { - char const *end_ptr = nullptr; - lighting_color = sp_svg_read_color(value, &end_ptr, 0xffffffff); - - // if a value was read - if (end_ptr) { - while (g_ascii_isspace(*end_ptr)) { - ++end_ptr; - } - - if (std::strncmp(end_ptr, "icc-color(", 10) == 0) { - icc.emplace(); - if (!sp_svg_read_icc_color(end_ptr, &*icc)) { - icc.reset(); - } - } - - lighting_color_set = true; - } else { - // lighting_color already contains the default value - lighting_color_set = false; - } - + lighting_color = Inkscape::Colors::Color::parse(value); requestModified(SP_OBJECT_MODIFIED_FLAG); break; } @@ -168,12 +146,8 @@ Inkscape::XML::Node *SPFeDiffuseLighting::write(Inkscape::XML::Document *doc, In } /*TODO kernelUnits */ - if (lighting_color_set) { - char c[64]; - sp_svg_write_color(c, sizeof(c), lighting_color); - repr->setAttribute("lighting-color", c); - } else { - repr->removeAttribute("lighting-color"); + if (lighting_color) { + repr->setAttributeOrRemoveIfEmpty("lighting-color", lighting_color->toString()); } SPFilterPrimitive::write(doc, repr, flags); @@ -206,10 +180,7 @@ std::unique_ptr SPFeDiffuseLighting::build_r diffuselighting->diffuseConstant = diffuseConstant; diffuselighting->surfaceScale = surfaceScale; - diffuselighting->lighting_color = lighting_color; - if (icc) { - diffuselighting->set_icc(*icc); - } + diffuselighting->lighting_color = lighting_color ? lighting_color->toRGBA() : 0x0; // We assume there is at most one child diffuselighting->light_type = Inkscape::Filters::NO_LIGHT; diff --git a/src/object/filters/diffuselighting.h b/src/object/filters/diffuselighting.h index 221b38553bda5a80539e7e5d62bf51838bf4eaeb..b09fe3ff68e5b30a90710bd08e518a05a647ada2 100644 --- a/src/object/filters/diffuselighting.h +++ b/src/object/filters/diffuselighting.h @@ -16,11 +16,8 @@ #include #include #include "sp-filter-primitive.h" -#include "svg/svg-icc-color.h" #include "number-opt-number.h" -struct SVGICCColor; - namespace Inkscape { namespace Filters { class FilterDiffuseLighting; @@ -36,14 +33,13 @@ public: private: float surfaceScale = 1.0f; float diffuseConstant = 1.0f; - uint32_t lighting_color = 0xffffffff; + + std::optional lighting_color; bool surfaceScale_set = false; bool diffuseConstant_set = false; - bool lighting_color_set = false; NumberOptNumber kernelUnitLength; // TODO - std::optional icc; protected: void build(SPDocument *doc, Inkscape::XML::Node *repr) override; diff --git a/src/object/filters/flood.cpp b/src/object/filters/flood.cpp index 2bae7ca9ab425e639e404e38dce111d2287f0ab0..50b27c5e935cd550743fd0aa496f7b4fa35ebe97 100644 --- a/src/object/filters/flood.cpp +++ b/src/object/filters/flood.cpp @@ -21,7 +21,6 @@ #include "display/nr-filter-flood.h" // for FilterFlood #include "object/filters/sp-filter-primitive.h" // for SPFilterPrimitive #include "object/sp-object.h" // for SP_OBJECT_MODIFIED_FLAG -#include "svg/svg-color.h" // for sp_svg_read_color class SPDocument; @@ -47,34 +46,9 @@ void SPFeFlood::set(SPAttr key, char const *value) { switch (key) { case SPAttr::FLOOD_COLOR: { - char const *end_ptr = nullptr; - uint32_t n_color = sp_svg_read_color(value, &end_ptr, 0x0); - bool modified = false; - if (n_color != color) { - color = n_color; - modified = true; - } - - if (end_ptr) { - while (g_ascii_isspace(*end_ptr)) { - ++end_ptr; - } - - if (std::strncmp(end_ptr, "icc-color(", 10) == 0) { - icc.emplace(); - - if (!sp_svg_read_icc_color(end_ptr, &*icc)) { - icc.reset(); - } - - modified = true; - } - } - - if (modified) { - requestModified(SP_OBJECT_MODIFIED_FLAG); - } + flood_color = Inkscape::Colors::Color::parse(value); + requestModified(SP_OBJECT_MODIFIED_FLAG); break; } case SPAttr::FLOOD_OPACITY: { @@ -107,13 +81,7 @@ std::unique_ptr SPFeFlood::build_renderer(In { auto flood = std::make_unique(); build_renderer_common(flood.get()); - - flood->set_opacity(opacity); - flood->set_color(color); - if (icc) { - flood->set_icc(*icc); - } - + flood->set_color(flood_color ? flood_color->toRGBA(opacity) : 0x0); return flood; } diff --git a/src/object/filters/flood.h b/src/object/filters/flood.h index 99cc4cfc76e2636a1d18150db264a53c46756a84..f67183937c2a194969dc779beb73c80fa916d9b7 100644 --- a/src/object/filters/flood.h +++ b/src/object/filters/flood.h @@ -16,7 +16,6 @@ #include #include #include "sp-filter-primitive.h" -#include "svg/svg-icc-color.h" class SPFeFlood final : public SPFilterPrimitive @@ -25,9 +24,9 @@ public: int tag() const override { return tag_of; } private: - uint32_t color = 0x0; + + std::optional flood_color; double opacity = 1.0; - std::optional icc; protected: void build(SPDocument *doc, Inkscape::XML::Node *repr) override; diff --git a/src/object/filters/sp-filter-primitive.h b/src/object/filters/sp-filter-primitive.h index 4935170b10a3378d5148e862146b5dd6cdbb8146..d15ba9e753212d80bcffdf035942ff53d8063067 100644 --- a/src/object/filters/sp-filter-primitive.h +++ b/src/object/filters/sp-filter-primitive.h @@ -19,6 +19,9 @@ #include #include #include "2geom/rect.h" +#include "colors/color.h" +#include "colors/manager.h" +#include "document.h" #include "object/sp-object.h" #include "object/sp-dimensions.h" #include "display/nr-filter-types.h" diff --git a/src/object/filters/specularlighting.cpp b/src/object/filters/specularlighting.cpp index 10a9d61fdd456571fa63985184b4102cab7c7333..c59490c15a33f9333b94b8e915de90dd8cc0c2dc 100644 --- a/src/object/filters/specularlighting.cpp +++ b/src/object/filters/specularlighting.cpp @@ -26,7 +26,6 @@ #include "display/nr-light-types.h" // for SpotLightData, Light... #include "object/filters/sp-filter-primitive.h" // for SPFilterPrimitive #include "object/sp-object.h" // for SP_OBJECT_MODIFIED_FLAG -#include "svg/svg-color.h" // for sp_svg_read_color #include "xml/node.h" // for Node class SPDocument; @@ -116,24 +115,7 @@ void SPFeSpecularLighting::set(SPAttr key, char const *value) requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SPAttr::LIGHTING_COLOR: { - char const *end_ptr = nullptr; - lighting_color = sp_svg_read_color(value, &end_ptr, 0xffffffff); - // if a value was read - if (end_ptr) { - while (g_ascii_isspace(*end_ptr)) { - ++end_ptr; - } - if (strneq(end_ptr, "icc-color(", 10)) { - if (!icc) icc.emplace(); - if (!sp_svg_read_icc_color(end_ptr, &*icc)) { - icc.reset(); - } - } - lighting_color_set = true; - } else { - // lighting_color already contains the default value - lighting_color_set = false; - } + lighting_color = Inkscape::Colors::Color::parse(value); requestModified(SP_OBJECT_MODIFIED_FLAG); break; } @@ -177,12 +159,10 @@ Inkscape::XML::Node *SPFeSpecularLighting::write(Inkscape::XML::Document *doc, I } // TODO kernelUnits - if (lighting_color_set) { - char c[64]; - sp_svg_write_color(c, sizeof(c), lighting_color); - repr->setAttribute("lighting-color", c); - } + if (lighting_color) { + repr->setAttributeOrRemoveIfEmpty("lighting-color", lighting_color->toString()); + } SPFilterPrimitive::write(doc, repr, flags); return repr; @@ -214,10 +194,7 @@ std::unique_ptr SPFeSpecularLighting::build_ specularlighting->specularConstant = specularConstant; specularlighting->specularExponent = specularExponent; specularlighting->surfaceScale = surfaceScale; - specularlighting->lighting_color = lighting_color; - if (icc) { - specularlighting->set_icc(*icc); - } + specularlighting->lighting_color = lighting_color ? lighting_color->toRGBA() : 0x0; // We assume there is at most one child specularlighting->light_type = Inkscape::Filters::NO_LIGHT; diff --git a/src/object/filters/specularlighting.h b/src/object/filters/specularlighting.h index 34d6c36c510d30995681d807af02d87c9a828a47..b47428b89be747360fbaf2a5585c71d8a462bfc7 100644 --- a/src/object/filters/specularlighting.h +++ b/src/object/filters/specularlighting.h @@ -17,12 +17,9 @@ #include #include -#include "svg/svg-icc-color.h" #include "sp-filter-primitive.h" #include "number-opt-number.h" -struct SVGICCColor; - namespace Inkscape { namespace Filters { class FilterSpecularLighting; @@ -39,15 +36,14 @@ private: float surfaceScale = 1.0f; float specularConstant = 1.0f; float specularExponent = 1.0f; - uint32_t lighting_color = 0xffffffff; + + std::optional lighting_color; bool surfaceScale_set = false; bool specularConstant_set = false; bool specularExponent_set = false; - bool lighting_color_set = false; NumberOptNumber kernelUnitLength; // TODO - std::optional icc; protected: void build(SPDocument *doc, Inkscape::XML::Node *repr) override; diff --git a/src/object/sp-gradient-vector.h b/src/object/sp-gradient-vector.h index 893d41e5fe67788df9e47eba56076cede1a1ca6f..49a113e0f7cbefc74920d34f0617683c6fbd1caa 100644 --- a/src/object/sp-gradient-vector.h +++ b/src/object/sp-gradient-vector.h @@ -11,7 +11,7 @@ #define SEEN_SP_GRADIENT_VECTOR_H #include -#include "color.h" +#include "colors/color.h" /** * Differs from SPStop in that SPStop mirrors the \ element in the document, whereas @@ -22,9 +22,8 @@ * copying from SPStop to SPGradientStop. */ struct SPGradientStop { + std::optional color; double offset; - SPColor color; - float opacity; }; /** diff --git a/src/object/sp-gradient.cpp b/src/object/sp-gradient.cpp index 7b1406fec4b10f5ff124f42eb2b7ec47d5a0b3be..6723bfadfc4ee35ebc2d1d74eff317d24f7bf858 100644 --- a/src/object/sp-gradient.cpp +++ b/src/object/sp-gradient.cpp @@ -138,8 +138,7 @@ bool SPGradient::isEquivalent(SPGradient *that) bool effective = true; while (effective && (as && bs)) { - if (!as->getColor().isClose(bs->getColor(), 0.001) || - as->offset != bs->offset || as->getOpacity() != bs->getOpacity() ) { + if (!as->getColor().isClose(bs->getColor(), 0.001) || as->offset != bs->offset) { effective = false; break; } @@ -922,7 +921,8 @@ SPGradient::repr_write_vector() /* strictly speaking, offset an SVG rather than a CSS one, but exponents make no * sense for offset proportions. */ auto obj = cast(document->getObjectByRepr(child)); - obj->setColor(stop.color, stop.opacity); + if (auto color = stop.color) + obj->setColor(*color); /* Order will be reversed here */ l.push_back(child); } @@ -1020,10 +1020,7 @@ void SPGradient::rebuildVector() // 0%. Gradient offset values greater than 1 (or greater than 100%) are rounded // down to 100%." gstop.offset = CLAMP(gstop.offset, 0, 1); - gstop.color = stop->getColor(); - gstop.opacity = stop->getOpacity(); - vector.stops.push_back(gstop); } } @@ -1036,15 +1033,11 @@ void SPGradient::rebuildVector() { SPGradientStop gstop; gstop.offset = 0.0; - gstop.color.set( 0x00000000 ); - gstop.opacity = 0.0; vector.stops.push_back(gstop); } { SPGradientStop gstop; gstop.offset = 1.0; - gstop.color.set( 0x00000000 ); - gstop.opacity = 0.0; vector.stops.push_back(gstop); } } else { @@ -1056,7 +1049,6 @@ void SPGradient::rebuildVector() SPGradientStop gstop; gstop.offset = 0.0; gstop.color = vector.stops.front().color; - gstop.opacity = vector.stops.front().opacity; vector.stops.insert(vector.stops.begin(), gstop); } if (vector.stops.back().offset < 1.0) { @@ -1064,7 +1056,6 @@ void SPGradient::rebuildVector() SPGradientStop gstop; gstop.offset = 1.0; gstop.color = vector.stops.back().color; - gstop.opacity = vector.stops.back().opacity; vector.stops.push_back(gstop); } } @@ -1153,11 +1144,9 @@ sp_gradient_pattern_common_setup(cairo_pattern_t *cp, // add stops if (!is(gr)) { - for (auto & stop : gr->vector.stops) - { + for (auto & stop : gr->vector.stops) { // multiply stop opacity by paint opacity - cairo_pattern_add_color_stop_rgba(cp, stop.offset, - stop.color.v.c[0], stop.color.v.c[1], stop.color.v.c[2], stop.opacity * opacity); + ink_cairo_pattern_add_color_stop(cp, stop.offset, *stop.color, opacity); } } @@ -1180,10 +1169,8 @@ SPGradient::create_preview_pattern(double width) pat = cairo_pattern_create_linear(0, 0, width, 0); - for (auto & stop : vector.stops) - { - cairo_pattern_add_color_stop_rgba(pat, stop.offset, - stop.color.v.c[0], stop.color.v.c[1], stop.color.v.c[2], stop.opacity); + for (auto & stop : vector.stops) { + ink_cairo_pattern_add_color_stop(pat, stop.offset, *stop.color); } } else if (unsigned const num_columns = array.patch_columns()) { // For the moment, use the top row of nodes for preview. @@ -1193,8 +1180,7 @@ SPGradient::create_preview_pattern(double width) for (unsigned i = 0; i < num_columns + 1; ++i) { SPMeshNode* node = array.node( 0, i*3 ); - cairo_pattern_add_color_stop_rgba(pat, i*offset, - node->color.v.c[0], node->color.v.c[1], node->color.v.c[2], node->opacity); + ink_cairo_pattern_add_color_stop(pat, i * offset, *node->color); } } diff --git a/src/object/sp-grid.cpp b/src/object/sp-grid.cpp index 9069e6e85826f5c10fa99b0c2f46627bea0634f7..f25db106d6d60d3f9929c9dccf9a585f2f93226b 100644 --- a/src/object/sp-grid.cpp +++ b/src/object/sp-grid.cpp @@ -19,6 +19,7 @@ #include "sp-grid.h" #include "sp-namedview.h" +#include "colors/manager.h" #include "display/control/canvas-item-grid.h" #include "display/control/canvas-item-ptr.h" @@ -28,7 +29,6 @@ #include "grid-snapper.h" #include "page-manager.h" #include "snapper.h" -#include "svg/svg-color.h" #include "svg/svg-length.h" #include "util/units.h" @@ -38,12 +38,18 @@ using Inkscape::Util::UnitTable; +// default colors +static auto const GRID_DEFAULT_MAJOR_COLOR = Inkscape::Colors::Color{0x0099e54d}; +static auto const GRID_DEFAULT_MINOR_COLOR = Inkscape::Colors::Color{0x0099e526}; + SPGrid::SPGrid() : _visible(true) , _enabled(true) , _dotted(false) , _snap_to_visible_only(true) , _legacy(false) + , _major_color{GRID_DEFAULT_MAJOR_COLOR} + , _minor_color{GRID_DEFAULT_MINOR_COLOR} , _pixel(true) , _grid_type(GridType::RECTANGULAR) { } @@ -95,8 +101,6 @@ void SPGrid::build(SPDocument *doc, Inkscape::XML::Node *repr) readAttr(SPAttr::EMPCOLOR); readAttr(SPAttr::VISIBLE); readAttr(SPAttr::ENABLED); - readAttr(SPAttr::OPACITY); - readAttr(SPAttr::EMPOPACITY); readAttr(SPAttr::MAJOR_LINE_INTERVAL); readAttr(SPAttr::DOTTED); readAttr(SPAttr::SNAP_TO_VISIBLE_ONLY); @@ -199,14 +203,20 @@ void SPGrid::set(SPAttr key, const gchar* value) _margin_y.read(value); requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; - case SPAttr::COLOR: - _minor_color = (_minor_color & 0xff) | sp_svg_read_color(value, GRID_DEFAULT_MINOR_COLOR); + case SPAttr::COLOR: { + auto const old_opacity = _minor_color.getOpacity(); + _minor_color = Inkscape::Colors::Color::parse(value).value_or(GRID_DEFAULT_MINOR_COLOR); + _minor_color.setOpacity(old_opacity); requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; - case SPAttr::EMPCOLOR: - _major_color = (_major_color & 0xff) | sp_svg_read_color(value, GRID_DEFAULT_MAJOR_COLOR); + } + case SPAttr::EMPCOLOR: { + auto const old_opacity = _major_color.getOpacity(); + _major_color = Inkscape::Colors::Color::parse(value).value_or(GRID_DEFAULT_MAJOR_COLOR); + _major_color.setOpacity(old_opacity); requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; + } case SPAttr::VISIBLE: _visible.read(value); requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); @@ -217,11 +227,11 @@ void SPGrid::set(SPAttr key, const gchar* value) requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SPAttr::OPACITY: - sp_ink_read_opacity(value, &_minor_color, GRID_DEFAULT_MINOR_COLOR); + _minor_color.setOpacity(value ? g_ascii_strtod(value, nullptr) : 1.0); requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SPAttr::EMPOPACITY: - sp_ink_read_opacity(value, &_major_color, GRID_DEFAULT_MAJOR_COLOR); + _major_color.setOpacity(value ? g_ascii_strtod(value, nullptr) : 1.0); requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); break; case SPAttr::MAJOR_LINE_INTERVAL: @@ -409,8 +419,8 @@ void SPGrid::setPrefValues() Quantity::convert(prefs->getDouble(prefix + "/spacing_x", default_spacing), _display_unit, "px"), Quantity::convert(prefs->getDouble(prefix + "/spacing_y", default_spacing), _display_unit, "px")) * scale); - setMajorColor(prefs->getColor(prefix + "/empcolor", modular ? GRID_DEFAULT_BLOCK_COLOR : GRID_DEFAULT_MAJOR_COLOR)); - setMinorColor(prefs->getColor(prefix + "/color", modular ? GRID_DEFAULT_MINOR_BLOCK_COLOR : GRID_DEFAULT_MINOR_COLOR)); + setMajorColor(prefs->getColor(prefix + "/empcolor", modular ? "#0047cb4d" : "#0099e54d")); + setMinorColor(prefs->getColor(prefix + "/color", modular ? "#0047cb26" : "#0099e526")); setMajorLineInterval(prefs->getInt(prefix + "/empspacing")); // these prefs are bound specifically to one type of grid @@ -478,8 +488,8 @@ void SPGrid::update(SPCtx *ctx, unsigned int flags) if (_enabled) { view->set_origin(origin); view->set_spacing(spacing); - view->set_major_color(_major_color); - view->set_minor_color(_minor_color); + view->set_major_color(getMajorColor().toRGBA()); + view->set_minor_color(getMinorColor().toRGBA()); view->set_dotted(_dotted); view->set_major_line_interval(_major_line_interval); @@ -687,30 +697,17 @@ void SPGrid::setOrigin(Geom::Point const &new_origin) requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } -void SPGrid::setMajorColor(const guint32 color) +void SPGrid::setMajorColor(Inkscape::Colors::Color const &color) { - char color_str[16]; - sp_svg_write_color(color_str, 16, color); - - getRepr()->setAttribute("empcolor", color_str); - - double opacity = (color & 0xff) / 255.0; // convert to value between [0.0, 1.0] - getRepr()->setAttributeSvgDouble("empopacity", opacity); - + getRepr()->setAttribute("empcolor", color.toString(false)); + getRepr()->setAttributeSvgDouble("empopacity", color.getOpacity()); requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } -void SPGrid::setMinorColor(const guint32 color) +void SPGrid::setMinorColor(Inkscape::Colors::Color const &color) { - char color_str[16]; - sp_svg_write_color(color_str, 16, color); - - - getRepr()->setAttribute("color", color_str); - - double opacity = (color & 0xff) / 255.0; // convert to value between [0.0, 1.0] - getRepr()->setAttributeSvgDouble("opacity", opacity); - + getRepr()->setAttribute("color", color.toString(false)); + getRepr()->setAttributeSvgDouble("opacity", color.getOpacity()); requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } diff --git a/src/object/sp-grid.h b/src/object/sp-grid.h index a254cd38cef3aa6069b0c840f3ecbe1ec7851647..848af5e0aa09ee1fa62b0cb0c95db2951a4d9617 100644 --- a/src/object/sp-grid.h +++ b/src/object/sp-grid.h @@ -18,6 +18,7 @@ #ifndef SEEN_SP_GRID_H_ #define SEEN_SP_GRID_H_ +#include "colors/color.h" #include "display/control/canvas-item-ptr.h" #include "object/sp-object.h" #include "svg/svg-bool.h" @@ -69,11 +70,11 @@ public: bool getSnapToVisibleOnly() const { return _snap_to_visible_only; } void setSnapToVisibleOnly(bool v); - guint32 getMajorColor() const { return _major_color; } - void setMajorColor(const guint32 color); + Inkscape::Colors::Color const &getMajorColor() const { return _major_color; } + void setMajorColor(Inkscape::Colors::Color const &color); - guint32 getMinorColor() const { return _minor_color; } - void setMinorColor(const guint32 color); + Inkscape::Colors::Color const &getMinorColor() const { return _minor_color; } + void setMinorColor(Inkscape::Colors::Color const &color); Geom::Point getOrigin() const; void setOrigin(Geom::Point const &new_origin); @@ -144,8 +145,8 @@ private: guint32 _major_line_interval = 0; - guint32 _major_color = 0; - guint32 _minor_color = 0; + Inkscape::Colors::Color _major_color; + Inkscape::Colors::Color _minor_color; bool _pixel; // is in user units bool _legacy; // a grid from versions prior to inkscape 0.98 diff --git a/src/object/sp-guide.cpp b/src/object/sp-guide.cpp index 1ecbfc715182708fe51a5d14e77ec21b6b9380c2..3726f92b32bee60d4aaa7ddd0e44047ca3dfd166 100644 --- a/src/object/sp-guide.cpp +++ b/src/object/sp-guide.cpp @@ -30,9 +30,10 @@ #include "sp-namedview.h" #include "sp-root.h" +#include "colors/color.h" +#include "colors/manager.h" #include "display/control/canvas-item-guideline.h" #include "object/sp-page.h" -#include "svg/svg-color.h" #include "svg/svg.h" #include "util/numeric/converters.h" #include "util/units.h" @@ -88,8 +89,10 @@ void SPGuide::release() void SPGuide::set(SPAttr key, const gchar *value) { switch (key) { case SPAttr::INKSCAPE_COLOR: - if (value) { - this->setColor(sp_svg_read_color(value, 0x0000ff00) | 0x7f); + if (auto c = Inkscape::Colors::Color::parse(value)) { + if (!c->hasOpacity()) + c->addOpacity(0.5); + color = c->toRGBA(); } break; case SPAttr::INKSCAPE_LABEL: diff --git a/src/object/sp-image.cpp b/src/object/sp-image.cpp index bc5c2afe37c60a8328c8eff39b92db3cb0e20e00..a0647655e1a78eddbbefab6f01eaceb315ae25f3 100644 --- a/src/object/sp-image.cpp +++ b/src/object/sp-image.cpp @@ -24,7 +24,6 @@ #include #include -#include #include <2geom/rect.h> #include <2geom/transforms.h> @@ -42,8 +41,9 @@ #include "xml/quote.h" #include "xml/href-attribute-helper.h" -#include "color/cms-system.h" -#include "color-profile.h" +#include "colors/cms/system.h" +#include "colors/manager.h" +#include "colors/document-cms.h" //#define DEBUG_LCMS #ifdef DEBUG_LCMS @@ -238,74 +238,6 @@ void SPImage::set(SPAttr key, const gchar* value) { } // BLIP -void SPImage::apply_profile(Inkscape::Pixbuf *pixbuf) { - - // TODO: this will prevent using MIME data when exporting. - // Integrate color correction into loading. - pixbuf->ensurePixelFormat(Inkscape::Pixbuf::PF_GDK); - int imagewidth = pixbuf->width(); - int imageheight = pixbuf->height(); - int rowstride = pixbuf->rowstride(); - guchar* px = pixbuf->pixels(); - - if ( px ) { - DEBUG_MESSAGE( lcmsFive, "in 's sp_image_update. About to call get_document_profile()"); - - guint profIntent = Inkscape::RENDERING_INTENT_UNKNOWN; - cmsHPROFILE prof = Inkscape::CMSSystem::get_document_profile(document, - &profIntent, - color_profile ); - if ( prof ) { - cmsProfileClassSignature profileClass = cmsGetDeviceClass( prof ); - if ( profileClass != cmsSigNamedColorClass ) { - int intent = INTENT_PERCEPTUAL; - - switch ( profIntent ) { - case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC: - intent = INTENT_RELATIVE_COLORIMETRIC; - break; - case Inkscape::RENDERING_INTENT_SATURATION: - intent = INTENT_SATURATION; - break; - case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC: - intent = INTENT_ABSOLUTE_COLORIMETRIC; - break; - case Inkscape::RENDERING_INTENT_PERCEPTUAL: - case Inkscape::RENDERING_INTENT_UNKNOWN: - case Inkscape::RENDERING_INTENT_AUTO: - default: - intent = INTENT_PERCEPTUAL; - } - - cmsHPROFILE destProf = cmsCreate_sRGBProfile(); - cmsHTRANSFORM transf = cmsCreateTransform( prof, - TYPE_RGBA_8, - destProf, - TYPE_RGBA_8, - intent, 0 ); - if ( transf ) { - guchar* currLine = px; - for ( int y = 0; y < imageheight; y++ ) { - // Since the types are the same size, we can do the transformation in-place - cmsDoTransform( transf, currLine, currLine, imagewidth ); - currLine += rowstride; - } - - cmsDeleteTransform( transf ); - } else { - DEBUG_MESSAGE( lcmsSix, "in 's sp_image_update. Unable to create LCMS transform." ); - } - - cmsCloseProfile( destProf ); - } else { - DEBUG_MESSAGE( lcmsSeven, "in 's sp_image_update. Profile type is named color. Can't transform." ); - } - } else { - DEBUG_MESSAGE( lcmsEight, "in 's sp_image_update. No profile found." ); - } - } -} - void SPImage::update(SPCtx *ctx, unsigned int flags) { SPItem::update(ctx, flags); @@ -333,7 +265,12 @@ void SPImage::update(SPCtx *ctx, unsigned int flags) { } if (pb) { - if (color_profile) apply_profile(pb); + if (color_profile) { + if (auto cp = document->getDocumentCMS().getSpace(color_profile)) { + pb->ensurePixelFormat(Inkscape::Pixbuf::PF_GDK); + // XXX TODO cp->transformToRGB(pb); + } + } pb->ensurePixelFormat(Inkscape::Pixbuf::PF_CAIRO); // Expected by rendering code, so convert now before making immutable. pixbuf = std::shared_ptr(pb); } diff --git a/src/object/sp-item-group.cpp b/src/object/sp-item-group.cpp index f2fa557a9fba79f95b55e0e6372e09533f56d99e..390488d8258a582312e9f47fc8b3aef2001352ea 100644 --- a/src/object/sp-item-group.cpp +++ b/src/object/sp-item-group.cpp @@ -1095,13 +1095,13 @@ std::vector default_highlights; /** * Generate a highlight colour if one isn't set and return it. */ -guint32 SPGroup::highlight_color() const { +Inkscape::Colors::Color SPGroup::highlight_color() const { // Parent must not be a layer (root, or similar) and this group must also be a layer if (!_highlightColor && !SP_IS_LAYER(parent) && this->_layer_mode == SPGroup::LAYER && !default_highlights.empty()) { char const * oid = defaultLabel(); if (oid && *oid) { // Color based on the last few bits of the label or object id. - return default_highlights[oid[(strlen(oid) - 1)] % default_highlights.size()]; + return Inkscape::Colors::Color(default_highlights[oid[(strlen(oid) - 1)] % default_highlights.size()]); } } return SPItem::highlight_color(); diff --git a/src/object/sp-item-group.h b/src/object/sp-item-group.h index 252985cc0be0b4769245725b11ed4bc6c2fcb039..9de52f73427b79d68214fad5bc2204025be25bd7 100644 --- a/src/object/sp-item-group.h +++ b/src/object/sp-item-group.h @@ -91,7 +91,7 @@ public: void update_patheffect(bool write) override; - guint32 highlight_color() const override; + Inkscape::Colors::Color highlight_color() const override; void removeTransformsRecursively(SPObject const *root) override; diff --git a/src/object/sp-item.cpp b/src/object/sp-item.cpp index d0c44e92683bcd4a9f1a7f90f90e2189cd5b8c20..07c2415a1f7a317092c4e6c779c8b74ffcc7cbf9 100644 --- a/src/object/sp-item.cpp +++ b/src/object/sp-item.cpp @@ -18,10 +18,10 @@ #include #include +#include "colors/manager.h" #include "helper/geom.h" #include "path/path-util.h" #include "svg/svg.h" -#include "svg/svg-color.h" #include "print.h" #include "display/drawing-item.h" #include "attributes.h" @@ -59,6 +59,7 @@ #include "live_effects/effect.h" #include "live_effects/lpeobject-reference.h" +#include "ui/util.h" #include "util/units.h" #define noSP_ITEM_DEBUG_IDLE @@ -75,7 +76,6 @@ SPItem::SPItem() sensitive = TRUE; bbox_valid = FALSE; - _highlightColor = 0; transform_center_x = 0; transform_center_y = 0; @@ -251,27 +251,26 @@ bool SPItem::isHidden(unsigned display_key) const return true; } -void SPItem::setHighlight(guint32 color) { - _highlightColor = color; +void SPItem::setHighlight(Inkscape::Colors::Color color) { + _highlightColor = std::move(color); updateRepr(); } bool SPItem::isHighlightSet() const { - return _highlightColor != 0; + return _highlightColor.has_value(); } -guint32 SPItem::highlight_color() const { +Inkscape::Colors::Color SPItem::highlight_color() const { if (isHighlightSet()) { - return _highlightColor; + return *_highlightColor; } SPItem const *item = cast(parent); if (parent && (parent != this) && item) { return item->highlight_color(); - } else { - static Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - return prefs->getInt("/tools/nodes/highlight_color", 0xaaaaaaff); } + auto prefs = Inkscape::Preferences::get(); + return prefs->getColor("/tools/nodes/highlight_color", "#aaaaaaff"); } void SPItem::setEvaluated(bool evaluated) { @@ -573,10 +572,7 @@ void SPItem::set(SPAttr key, gchar const* value) { } case SPAttr::INKSCAPE_HIGHLIGHT_COLOR: { - item->_highlightColor = 0; - if (value) { - item->_highlightColor = sp_svg_read_color(value, 0x0) | 0xff; - } + item->_highlightColor = Inkscape::Colors::Color::parse(value); break; } case SPAttr::CONNECTOR_AVOID: @@ -878,7 +874,7 @@ Inkscape::XML::Node* SPItem::write(Inkscape::XML::Document *xml_doc, Inkscape::X repr->setAttributeOrRemoveIfEmpty("mask", value); } if (item->isHighlightSet()) { - repr->setAttribute("inkscape:highlight-color", SPColor(item->_highlightColor).toString()); + repr->setAttribute("inkscape:highlight-color", item->_highlightColor->toString()); } else { repr->removeAttribute("inkscape:highlight-color"); } diff --git a/src/object/sp-item.h b/src/object/sp-item.h index 1bbcb96d0c2832d4c8664aab28246f0b17bff5ed..2ffbcc23a8fcbd8814ff77c9e57576d3072e83de 100644 --- a/src/object/sp-item.h +++ b/src/object/sp-item.h @@ -27,6 +27,8 @@ #include <2geom/affine.h> #include <2geom/rect.h> +#include "colors/color.h" + #include "sp-object.h" #include "sp-marker-loc.h" #include "display/drawing-item-ptr.h" @@ -172,9 +174,9 @@ public: return sensitive; }; - void setHighlight(uint32_t color); + void setHighlight(Inkscape::Colors::Color color); bool isHighlightSet() const; - virtual uint32_t highlight_color() const; + virtual Inkscape::Colors::Color highlight_color() const; //==================== @@ -402,7 +404,7 @@ public: */ Geom::Affine dt2i_affine() const; - uint32_t _highlightColor; + std::optional _highlightColor; bool isExpanded() const { return _is_expanded; } void setExpanded(bool expand) { _is_expanded = expand; } diff --git a/src/object/sp-mesh-array.cpp b/src/object/sp-mesh-array.cpp index e040e98382ac021bf75c657eefe0f3148d3dd3d9..062b4d844173d57236971d6be07d6a2e871257a7 100644 --- a/src/object/sp-mesh-array.cpp +++ b/src/object/sp-mesh-array.cpp @@ -49,11 +49,12 @@ #include <2geom/line.h> // For color picking +#include "colors/color.h" +#include "colors/utils.h" #include "display/drawing.h" #include "display/drawing-context.h" #include "display/cairo-utils.h" #include "document.h" -#include "gradient-chemistry.h" // average_color() #include "sp-root.h" #include "sp-mesh-gradient.h" #include "sp-mesh-row.h" @@ -66,11 +67,9 @@ #include "sp-ellipse.h" #include "sp-star.h" -// For writing color/opacity to style -#include "svg/css-ostringstream.h" - // For default color #include "style.h" +#include "svg/css-ostringstream.h" #include "xml/document.h" // for Document #include "xml/node.h" // for Node @@ -470,34 +469,27 @@ void SPMeshPatchI::updateNodes() { /** Return color for corner of patch. */ -SPColor SPMeshPatchI::getColor(unsigned const i) +std::optional SPMeshPatchI::getColor(unsigned const i) { assert( i < 4 ); - SPColor color; switch ( i ) { case 0: - color = (*nodes)[ row ][ col ]->color; - break; + return (*nodes)[ row ][ col ]->color; case 1: - color = (*nodes)[ row ][ col+3 ]->color; - break; + return (*nodes)[ row ][ col+3 ]->color; case 2: - color = (*nodes)[ row+3 ][ col+3 ]->color; - break; + return (*nodes)[ row+3 ][ col+3 ]->color; case 3: - color = (*nodes)[ row+3 ][ col ]->color; - break; - + return (*nodes)[ row+3 ][ col ]->color; } - - return color; + return {}; } /** Set color for corner of patch. */ -void SPMeshPatchI::setColor(unsigned const i, SPColor const color) +void SPMeshPatchI::setColor(unsigned const i, Inkscape::Colors::Color const &color) { assert( i < 4 ); @@ -517,57 +509,6 @@ void SPMeshPatchI::setColor(unsigned const i, SPColor const color) } } -/** - Return opacity for corner of patch. -*/ -double SPMeshPatchI::getOpacity(unsigned const i) -{ - assert( i < 4 ); - - double opacity = 0.0; - switch ( i ) { - case 0: - opacity = (*nodes)[ row ][ col ]->opacity; - break; - case 1: - opacity = (*nodes)[ row ][ col+3 ]->opacity; - break; - case 2: - opacity = (*nodes)[ row+3 ][ col+3 ]->opacity; - break; - case 3: - opacity = (*nodes)[ row+3 ][ col ]->opacity; - break; - } - - return opacity; -} - -/** - Set opacity for corner of patch. -*/ -void SPMeshPatchI::setOpacity(unsigned const i, double const opacity) -{ - - assert( i < 4 ); - - switch ( i ) { - case 0: - (*nodes)[ row ][ col ]->opacity = opacity; - break; - case 1: - (*nodes)[ row ][ col+3 ]->opacity = opacity; - break; - case 2: - (*nodes)[ row+3 ][ col+3 ]->opacity = opacity; - break; - case 3: - (*nodes)[ row+3 ][ col ]->opacity = opacity; - break; - - } -} - /** Return stop pointer for corner of patch. */ @@ -836,10 +777,8 @@ bool SPMeshNodeArray::read(SPMeshGradient *mg_in) if( (istop == 0 && irow == 0 && icolumn > 0) || (istop == 1 && irow > 0 ) ) { // skip } else { - SPColor color = stop->getColor(); - double opacity = stop->getOpacity(); + auto color = stop->getColor(); new_patch.setColor( istop, color ); - new_patch.setOpacity( istop, opacity ); new_patch.setStopPtr( istop, stop ); } ++istop; @@ -1038,13 +977,12 @@ void SPMeshNodeArray::write( SPMeshGradient *mg ) ( k == 2 ) || ( k == 3 && j == 0 ) ) { - // Why are we setting attribute and not style? - //stop->setAttribute("stop-color", patchi.getColor(k).toString() ); - //stop->setAttribute("stop-opacity", patchi.getOpacity(k) ); - - Inkscape::CSSOStringStream os; - os << "stop-color:" << patchi.getColor(k).toString() << ";stop-opacity:" << patchi.getOpacity(k); - stop->setAttribute("style", os.str()); + auto color = patchi.getColor(k); + SPCSSAttr *color_css = sp_repr_css_attr_new(); + sp_repr_css_set_property_string(color_css, "stop-color", color->toString(false)); + sp_repr_css_set_property_double(color_css, "stop-opacity", color->getOpacity()); + sp_repr_css_set(stop, color_css, "style"); + sp_repr_css_attr_unref(color_css); } patch->appendChild( stop ); } @@ -1055,38 +993,33 @@ void SPMeshNodeArray::write( SPMeshGradient *mg ) /** * Find default color based on colors in existing fill. */ -static SPColor default_color( SPItem *item ) { - - SPColor color( 0.5, 0.0, 0.5 ); - +static Inkscape::Colors::Color default_color(SPItem *item) +{ if ( item->style ) { SPIPaint const &paint = ( item->style->fill ); // Could pick between style.fill/style.stroke - if ( paint.isColor() ) { - color = paint.value.color; + if (paint.isColor()) { + return paint.getColor(); } else if ( paint.isPaintserver() ) { auto *server = item->style->getFillPaintServer(); auto gradient = cast(server); if (gradient && gradient->getVector()) { SPStop *firstStop = gradient->getVector()->getFirstStop(); if ( firstStop ) { - color = firstStop->getColor(); + return firstStop->getColor(); } } } } else { std::cerr << " SPMeshNodeArray: default_color(): No style" << std::endl; } - - return color; + return Inkscape::Colors::Color(0x800080ff); } /** Create a default mesh. */ -void SPMeshNodeArray::create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bbox ) { - - // std::cout << "SPMeshNodeArray::create: Entrance" << std::endl; - +void SPMeshNodeArray::create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bbox ) +{ if( !bbox ) { // Set default size to bounding box if size not given. std::cerr << "SPMeshNodeArray::create(): bbox empty" << std::endl; @@ -1119,13 +1052,13 @@ void SPMeshNodeArray::create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bb repr->setAttribute("gradientUnits", "userSpaceOnUse"); // Get default color - SPColor color = default_color( item ); + Inkscape::Colors::Color color = default_color( item ); // Set some corners to white so we can see the mesh. - SPColor white( 1.0, 1.0, 1.0 ); + auto white = Inkscape::Colors::Color(0xffffffff); if (color == white) { // If default color is white, set other color to black. - white = SPColor( 0.0, 0.0, 0.0 ); + white.set("black"); } // Get preferences @@ -1214,7 +1147,6 @@ void SPMeshNodeArray::create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bb for( unsigned k = 0; k < 4; ++k ) { patch.setPathType( k, 'l' ); patch.setColor( k, (i+k)%2 ? color : white ); - patch.setOpacity( k, 1.0 ); } patch.setPathType( 0, 'c' ); @@ -1270,7 +1202,6 @@ void SPMeshNodeArray::create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bb patch.setPathType( i, 'c' ); patch.setColor( i, i%2 ? color : white ); - patch.setOpacity( i, 1.0 ); } // Fill out tensor points @@ -1311,7 +1242,6 @@ void SPMeshNodeArray::create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bb for( unsigned s = 0; s < 4; ++s ) { patch.setPathType( s, 'l' ); patch.setColor( s, (i+s)%2 ? color : white ); - patch.setOpacity( s, 1.0 ); } // Set handle and tensor nodes. @@ -1339,10 +1269,8 @@ void SPMeshNodeArray::create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bb for( unsigned s = 0; s < 4; ++s ) { patch0.setPathType( s, 'l' ); patch0.setColor( s, s%2 ? color : white ); - patch0.setOpacity( s, 1.0 ); patch1.setPathType( s, 'l' ); patch1.setColor( s, s%2 ? white : color ); - patch1.setOpacity( s, 1.0 ); } // Set handle and tensor nodes. @@ -1392,7 +1320,6 @@ void SPMeshNodeArray::create( SPMeshGradient *mg, SPItem *item, Geom::OptRect bb node->node_type = MG_NODE_TYPE_CORNER; node->set = true; node->color = (i+j)%2 ? color : white; - node->opacity = 1.0; } else { // Side @@ -1633,11 +1560,11 @@ void SPMeshNodeArray::bicubic(SPMeshNodeArray * const smooth, SPMeshType const t for( unsigned i = 0; i < d.size(); ++i ) { d[i].resize( smooth->patch_columns() + 1 ); for( unsigned j = 0; j < d[i].size(); ++j ) { - float rgb_color[3]; - this->nodes[ i*3 ][ j*3 ]->color.get_rgb_floatv(rgb_color); - d[i][j].g[0][0] = rgb_color[ 0 ]; - d[i][j].g[1][0] = rgb_color[ 1 ]; - d[i][j].g[2][0] = rgb_color[ 2 ]; + // Note: Conversion to RGB happens here + auto rgb_color = this->nodes[i * 3][j * 3]->color->converted(Colors::Space::Type::RGB); + d[i][j].g[0][0] = rgb_color->get(0); + d[i][j].g[1][0] = rgb_color->get(1); + d[i][j].g[2][0] = rgb_color->get(2); d[i][j].p = this->nodes[ i*3 ][ j*3 ]->p; } } @@ -1784,8 +1711,12 @@ void SPMeshNodeArray::bicubic(SPMeshNodeArray * const smooth, SPMeshType const t for( unsigned k = 0; k < 9; ++k ) { for( unsigned l = 0; l < 9; ++l ) { + // We're not sure why opacity isn't smoothed, it's just sort of, retained without explaination + auto op = smooth->nodes[(i*8+k)*3 ][(j*8+l)*3]->color->getOpacity(); // Every third node is a corner node - smooth->nodes[ (i*8+k)*3 ][(j*8+l)*3 ]->color.set( r[0][k][l], r[1][k][l], r[2][k][l] ); + smooth->nodes[(i*8+k)*3 ][(j*8+l)*3]->color->set( + Colors::Color(SP_RGBA32_F_COMPOSE(r[0][k][l], r[1][k][l], r[2][k][l], op)) + ); } } } @@ -2158,9 +2089,9 @@ unsigned SPMeshNodeArray::color_smooth(std::vector const &corners) double slope_diff[3]; // Color of corners - SPColor color0 = n[0]->color; - SPColor color3 = n[3]->color; - SPColor color6 = n[6]->color; + auto &color0 = n[0]->color; + auto &color3 = n[3]->color; + auto &color6 = n[6]->color; // Distance nodes from selected corner Geom::Point d[7]; @@ -2173,17 +2104,17 @@ unsigned SPMeshNodeArray::color_smooth(std::vector const &corners) unsigned cdm = 0; // Color Diff Max (Which color has the maximum difference in slopes) for( unsigned c = 0; c < 3; ++c ) { if( d[2].length() != 0.0 ) { - slope[0][c] = (color3.v.c[c] - color0.v.c[c]) / d[2].length(); + slope[0][c] = (color3->get(c) - color0->get(c)) / d[2].length(); } if( d[4].length() != 0.0 ) { - slope[1][c] = (color6.v.c[c] - color3.v.c[c]) / d[4].length(); + slope[1][c] = (color6->get(c) - color3->get(c)) / d[4].length(); } slope_ave[c] = (slope[0][c]+slope[1][c]) / 2.0; slope_diff[c] = (slope[0][c]-slope[1][c]); // std::cout << " color: " << c << " :" - // << color0.v.c[c] << " " - // << color3.v.c[c] << " " - // << color6.v.c[c] + // << color0[c] << " " + // << color3[c] << " " + // << color6[c] // << " slope: " // << slope[0][c] << " " // << slope[1][c] @@ -2203,8 +2134,8 @@ unsigned SPMeshNodeArray::color_smooth(std::vector const &corners) double length_left = d[0].length(); double length_right = d[6].length(); if( slope_ave[ cdm ] != 0.0 ) { - length_left = std::abs( (color3.v.c[cdm] - color0.v.c[cdm]) / slope_ave[ cdm ] ); - length_right = std::abs( (color6.v.c[cdm] - color3.v.c[cdm]) / slope_ave[ cdm ] ); + length_left = std::abs( (color3->get(cdm) - color0->get(cdm)) / slope_ave[ cdm ] ); + length_right = std::abs( (color6->get(cdm) - color3->get(cdm)) / slope_ave[ cdm ] ); } // Move closest handle a maximum of mid point... but don't shorten @@ -2342,17 +2273,8 @@ unsigned SPMeshNodeArray::color_pick(std::vector const &icorners, SPIt /* Render copy and pick color */ pick_drawing->render(dc, ibox); - double R = 0, G = 0, B = 0, A = 0; - ink_cairo_surface_average_color(s, R, G, B, A); + n->color = ink_cairo_surface_average_color(s); cairo_surface_destroy(s); - - // std::cout << " p: " << p - // << " box: " << ibox - // << " R: " << R - // << " G: " << G - // << " B: " << B - // << std::endl; - n->color.set( R, G, B ); } pick_doc->getRoot()->invoke_hide(pick_visionkey); @@ -2878,14 +2800,7 @@ void SPMeshNodeArray::split_row( unsigned int row, double coord ) { nodes[i+5][j]->node_type = MG_NODE_TYPE_HANDLE; // Color stored in corners - unsigned c0 = nodes[i ][j]->color.toRGBA32( 1.0 ); - unsigned c1 = nodes[i+6][j]->color.toRGBA32( 1.0 ); - double o0 = nodes[i ][j]->opacity; - double o1 = nodes[i+6][j]->opacity; - unsigned cnew = average_color( c0, c1, coord ); - double onew = o0 * (1.0 - coord) + o1 * coord; - nodes[i+3][j]->color.set( cnew ); - nodes[i+3][j]->opacity = onew; + nodes[i+3][j]->color = nodes[i][j]->color->averaged(*nodes[i+6][j]->color, coord); nodes[i+3][j]->node_type = MG_NODE_TYPE_CORNER; nodes[i+3][j]->set = true; @@ -2991,14 +2906,7 @@ void SPMeshNodeArray::split_column( unsigned int col, double coord ) { nodes[i][j+5]->node_type = MG_NODE_TYPE_HANDLE; // Color stored in corners - unsigned c0 = nodes[i][j ]->color.toRGBA32( 1.0 ); - unsigned c1 = nodes[i][j+6]->color.toRGBA32( 1.0 ); - double o0 = nodes[i][j ]->opacity; - double o1 = nodes[i][j+6]->opacity; - unsigned cnew = average_color( c0, c1, coord ); - double onew = o0 * (1.0 - coord) + o1 * coord; - nodes[i][j+3]->color.set( cnew ); - nodes[i][j+3]->opacity = onew; + nodes[i][j+3]->color = nodes[i][j]->color->averaged(*nodes[i][j+6]->color, coord); nodes[i][j+3]->node_type = MG_NODE_TYPE_CORNER; nodes[i][j+3]->set = true; diff --git a/src/object/sp-mesh-array.h b/src/object/sp-mesh-array.h index 66ccf7e6f494543b35d875625be653f0d8bfb660..02a5a821863d5a50144b3e5c574e07578734394f 100644 --- a/src/object/sp-mesh-array.h +++ b/src/object/sp-mesh-array.h @@ -45,7 +45,7 @@ #include #include <2geom/point.h> -#include "color.h" +#include "colors/color.h" // For color picking #include "sp-item.h" @@ -94,7 +94,7 @@ class SPStop; class SPMeshNode { public: - SPMeshNode() {} + SPMeshNode() = default; NodeType node_type = MG_NODE_TYPE_UNKNOWN; unsigned node_edge = MG_NODE_EDGE_NONE; @@ -102,8 +102,7 @@ public: Geom::Point p; unsigned draggable = -1; // index of on-screen node char path_type = 'u'; - SPColor color{0, 0, 0}; // Default black - double opacity = 0.0; + std::optional color; SPStop *stop = nullptr; // Stop corresponding to node. }; @@ -128,10 +127,8 @@ public: bool tensorIsSet( unsigned i ); Geom::Point coonsTensorPoint( unsigned i ); void updateNodes(); - SPColor getColor( unsigned i ); - void setColor( unsigned i, SPColor c ); - double getOpacity( unsigned i ); - void setOpacity( unsigned i, double o ); + std::optional getColor(unsigned i); + void setColor(unsigned i, Inkscape::Colors::Color const &c); SPStop* getStopPtr( unsigned i ); void setStopPtr( unsigned i, SPStop* ); }; diff --git a/src/object/sp-mesh-gradient.cpp b/src/object/sp-mesh-gradient.cpp index 4bbc455534f9d3b31b12031223916363d9512dc1..394360497ebf92950d65d6a7577373ce39cd39db 100644 --- a/src/object/sp-mesh-gradient.cpp +++ b/src/object/sp-mesh-gradient.cpp @@ -216,12 +216,12 @@ std::unique_ptr SPMeshGradient::create_drawing_pai // << " calculated as " << t << "." << std::endl; } - auto color = patch.getColor(k); + // Does this data really need RGB at this stage? + auto color = *patch.getColor(k)->converted(Colors::Space::Type::RGB); for (int r = 0; r < 3; r++) { - data.color[k][r] = color.v.c[r]; + data.color[k][r] = color[r]; } - - data.opacity[k] = patch.getOpacity(k); + data.opacity[k] = color.getOpacity(); } } } diff --git a/src/object/sp-namedview.cpp b/src/object/sp-namedview.cpp index 680c48a25d2c03ea465863ce339b784ef68308b4..a0bac8e772e5ae604a45eef9e783e9691d629417 100644 --- a/src/object/sp-namedview.cpp +++ b/src/object/sp-namedview.cpp @@ -25,6 +25,7 @@ #include #include "attributes.h" +#include "colors/manager.h" #include "conn-avoid-ref.h" // for defaultConnSpacing. #include "desktop.h" #include "document-undo.h" @@ -42,7 +43,6 @@ #include "actions/actions-canvas-snapping.h" #include "display/control/canvas-page.h" -#include "svg/svg-color.h" #include "ui/monitor.h" #include "ui/widget/canvas.h" #include "ui/widget/canvas-grid.h" @@ -52,10 +52,6 @@ using Inkscape::DocumentUndo; using Inkscape::Util::UnitTable; -#define DEFAULTGUIDECOLOR 0x0086e599 -#define DEFAULTGUIDEHICOLOR 0xff00007f -#define DEFAULTDESKCOLOR 0xd1d1d1ff - SPNamedView::SPNamedView() : SPObjectGroup() , snap_manager(this, get_snapping_preferences()) @@ -66,8 +62,6 @@ SPNamedView::SPNamedView() , desk_checkerboard(false) { this->zoom = 0; - this->guidecolor = 0; - this->guidehicolor = 0; this->views.clear(); // this->page_size_units = nullptr; this->window_x = 0; @@ -80,7 +74,6 @@ SPNamedView::SPNamedView() this->window_width = 0; this->window_height = 0; this->window_maximized = 0; - this->desk_color = DEFAULTDESKCOLOR; this->editable = TRUE; this->viewcount = 0; @@ -144,9 +137,8 @@ void SPNamedView::build(SPDocument *document, Inkscape::XML::Node *repr) { for (auto &child : children) { if (auto guide = cast(&child)) { this->guides.push_back(guide); - //g_object_set(G_OBJECT(g), "color", nv->guidecolor, "hicolor", nv->guidehicolor, NULL); - guide->setColor(this->guidecolor); - guide->setHiColor(this->guidehicolor); + guide->setColor(getGuideColor().toRGBA()); + guide->setHiColor(getGuideHiColor().toRGBA()); guide->readAttr(SPAttr::INKSCAPE_COLOR); } if (auto page = cast(&child)) { @@ -171,14 +163,35 @@ void SPNamedView::set_clip_to_page(SPDesktop* desktop, bool enable) { } } +static auto const default_desk_color = Inkscape::Colors::Color{0xd1d1d1, false}; +static auto const default_guide_color = Inkscape::Colors::Color{0x0086e5, false}; +static auto const default_guide_hi_color = Inkscape::Colors::Color{0xff0000, false}; + +Inkscape::Colors::Color SPNamedView::getDeskColor() const +{ + return _desk_color.value_or(default_desk_color); +} + +Inkscape::Colors::Color SPNamedView::getGuideColor() const +{ + auto copy = _guide_color.value_or(default_guide_color); + copy.addOpacity(_guide_opacity); + return copy; +} + +Inkscape::Colors::Color SPNamedView::getGuideHiColor() const +{ + auto copy = _guide_hi_color.value_or(default_guide_hi_color); + copy.addOpacity(_guide_hi_opacity); + return copy; +} + void SPNamedView::set_desk_color(SPDesktop* desktop) { if (desktop) { - if (desk_checkerboard) { - desktop->getCanvas()->set_desk(desk_color); - } else { - desktop->getCanvas()->set_desk(desk_color | 0xff); - } - // Update pages, who's colours sometimes change whe the desk color changes. + auto dkcolor = getDeskColor(); + dkcolor.setOpacity(desk_checkerboard ? 0.0 : 1.0); + desktop->getCanvas()->set_desk(dkcolor.toRGBA()); + // Update pages, whose colours sometimes change whe the desk color changes. document->getPageManager().setDefaultAttributes(_viewport); } } @@ -275,6 +288,14 @@ void SPNamedView::set(SPAttr key, const gchar* value) { return; } + auto update_guides = [this]() { + for(auto guide : guides) { + guide->setColor(getGuideColor().toRGBA()); + guide->setHiColor(getGuideHiColor().toRGBA()); + guide->readAttr(SPAttr::INKSCAPE_COLOR); + } + }; + switch (key) { case SPAttr::VIEWONLY: this->editable = (!value); @@ -305,41 +326,23 @@ void SPNamedView::set(SPAttr key, const gchar* value) { this->snap_manager.snapprefs.setDistributionTolerance(value ? g_ascii_strtod(value, nullptr) : 5); break; case SPAttr::GUIDECOLOR: - this->guidecolor = (this->guidecolor & 0xff) | (DEFAULTGUIDECOLOR & 0xffffff00); - if (value) { - this->guidecolor = (this->guidecolor & 0xff) | sp_svg_read_color(value, this->guidecolor); - } - for(auto guide : this->guides) { - guide->setColor(this->guidecolor); - guide->readAttr(SPAttr::INKSCAPE_COLOR); - } + _guide_color = Inkscape::Colors::Color::parse(value); + update_guides(); break; case SPAttr::GUIDEOPACITY: - sp_ink_read_opacity(value, &this->guidecolor, DEFAULTGUIDECOLOR); - for (auto guide : this->guides) { - guide->setColor(this->guidecolor); - guide->readAttr(SPAttr::INKSCAPE_COLOR); - } + _guide_opacity = value ? g_ascii_strtod(value, nullptr) : 0.6; + update_guides(); break; case SPAttr::GUIDEHICOLOR: - this->guidehicolor = (this->guidehicolor & 0xff) | (DEFAULTGUIDEHICOLOR & 0xffffff00); - if (value) { - this->guidehicolor = (this->guidehicolor & 0xff) | sp_svg_read_color(value, this->guidehicolor); - } - for(auto guide : this->guides) { - guide->setHiColor(this->guidehicolor); - } + _guide_hi_color = Inkscape::Colors::Color::parse(value); + update_guides(); break; case SPAttr::GUIDEHIOPACITY: - sp_ink_read_opacity(value, &this->guidehicolor, DEFAULTGUIDEHICOLOR); - for (auto guide : this->guides) { - guide->setHiColor(this->guidehicolor); - } + _guide_hi_opacity = value ? g_ascii_strtod(value, nullptr) : 0.5; + update_guides(); break; case SPAttr::INKSCAPE_DESK_COLOR: - if (value) { - desk_color = sp_svg_read_color(value, desk_color); - } + _desk_color = Inkscape::Colors::Color::parse(value); break; case SPAttr::INKSCAPE_DESK_CHECKERBOARD: this->desk_checkerboard.readOrUnset(value); @@ -460,8 +463,8 @@ void SPNamedView::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *r this->guides.push_back(g); //g_object_set(G_OBJECT(g), "color", this->guidecolor, "hicolor", this->guidehicolor, NULL); - g->setColor(this->guidecolor); - g->setHiColor(this->guidehicolor); + g->setColor(getGuideColor().toRGBA()); + g->setHiColor(getGuideHiColor().toRGBA()); g->readAttr(SPAttr::INKSCAPE_COLOR); if (this->editable) { @@ -978,14 +981,10 @@ void SPNamedView::scrollAllDesktops(double dx, double dy) { } } -void SPNamedView::change_color(unsigned int rgba, SPAttr color_key, SPAttr opacity_key /*= SPAttr::INVALID*/) { - gchar buf[32]; - sp_svg_write_color(buf, sizeof(buf), rgba); - getRepr()->setAttribute(sp_attribute_name(color_key), buf); - - if (opacity_key != SPAttr::INVALID) { - getRepr()->setAttributeCssDouble(sp_attribute_name(opacity_key), (rgba & 0xff) / 255.0); - } +void SPNamedView::change_color(SPAttr color_key, SPAttr opacity_key, Inkscape::Colors::Color const &color) { + if (color.hasOpacity()) + getRepr()->setAttributeCssDouble(sp_attribute_name(opacity_key), color.getOpacity()); + getRepr()->setAttribute(sp_attribute_name(color_key), color.toString(false)); } void SPNamedView::change_bool_setting(SPAttr key, bool value) { diff --git a/src/object/sp-namedview.h b/src/object/sp-namedview.h index 98ff9d5b184532d6225c2227d37288570a8706da..32533975b37c9e8fbd5c46725137ca07332fcca1 100644 --- a/src/object/sp-namedview.h +++ b/src/object/sp-namedview.h @@ -21,6 +21,7 @@ #include "snap.h" #include "sp-object-group.h" +#include "colors/color.h" #include "svg/svg-bool.h" namespace Inkscape { @@ -28,8 +29,13 @@ namespace Inkscape { namespace Util { class Unit; } + namespace Colors { + class Color; + } } +using namespace Inkscape; + class SPGrid; typedef unsigned int guint32; @@ -53,7 +59,6 @@ public: SVGBool grids_visible; SVGBool clip_to_page; // if true, clip rendered content to pages' boundaries SVGBool antialias_rendering = true; - guint32 desk_color; SVGBool desk_checkerboard; double zoom; @@ -75,9 +80,6 @@ public: double connector_spacing; - guint32 guidecolor; - guint32 guidehicolor; - std::vector guides; std::vector grids; std::vector views; @@ -115,7 +117,7 @@ public: void newGridCreated(); // page background, border, desk colors - void change_color(unsigned int rgba, SPAttr color_key, SPAttr opacity_key = SPAttr::INVALID); + void change_color(SPAttr color_key, SPAttr opacity_key,Colors::Color const &color); // show border, border on top, anti-aliasing, ... void change_bool_setting(SPAttr key, bool value); // sync desk colors @@ -127,6 +129,9 @@ public: SPGrid *getFirstEnabledGrid(); + Colors::Color getDeskColor() const; + Colors::Color getGuideColor() const; + Colors::Color getGuideHiColor() const; private: void updateGuides(); void updateGrids(); @@ -138,6 +143,12 @@ private: Inkscape::CanvasPage *_viewport = nullptr; bool _sync_grids = true; + std::optional _desk_color; + std::optional _guide_color; + std::optional _guide_hi_color; + double _guide_opacity = 0.6; + double _guide_hi_opacity = 0.5; + protected: void build(SPDocument *document, Inkscape::XML::Node *repr) override; void release() override; diff --git a/src/object/sp-object.cpp b/src/object/sp-object.cpp index 14bb97e7bf5b9bc65ba226acd8e6997e68e75713..abdde8080a38c69130636371a612ad75db108cad 100644 --- a/src/object/sp-object.cpp +++ b/src/object/sp-object.cpp @@ -734,8 +734,8 @@ void SPObject::release() { debug("id=%p, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); style->filter.clear(); - style->fill.value.href.reset(); - style->stroke.value.href.reset(); + style->fill.href.reset(); + style->stroke.href.reset(); style->shape_inside.clear(); style->shape_subtract.clear(); diff --git a/src/object/sp-pattern.cpp b/src/object/sp-pattern.cpp index 88857527c233261dbe5170208b78cedf73fccd00..32b0b6fd2d3181296d010cee25396adae0df3192 100644 --- a/src/object/sp-pattern.cpp +++ b/src/object/sp-pattern.cpp @@ -541,7 +541,7 @@ char const *SPPattern::produce(std::vector const &reprs, G // if some elements have undefined color or solid black, then their fill color is customizable if (copy->style && copy->style->isSet(SPAttr::FILL)) { if (auto paint = copy->style->getFillOrStroke(true)) { - if (paint->isColor() && paint->value.color.toRGBA32(255) == 255) { // black color set? + if (paint->isColor() && paint->getColor().toRGBA() == 255) { // black color set? can_colorize = true; // remove black fill, it will be inherited from pattern paint->clear(); diff --git a/src/object/sp-solid-color.cpp b/src/object/sp-solid-color.cpp index b7d520a3573618a9dd6a27b096dcdf79b68bb78f..40a8fa872f918271de57fbc02d47dd7cd0f73ad5 100644 --- a/src/object/sp-solid-color.cpp +++ b/src/object/sp-solid-color.cpp @@ -63,7 +63,9 @@ Inkscape::XML::Node* SPSolidColor::write(Inkscape::XML::Document* xml_doc, Inksc std::unique_ptr SPSolidColor::create_drawing_paintserver() { - return std::make_unique(style->solid_color.value.color.v.c, SP_SCALE24_TO_FLOAT(style->solid_opacity.value)); + auto color = style->solid_color.getColor(); + color.addOpacity(style->solid_opacity); + return std::make_unique(color); } /* diff --git a/src/object/sp-solid-color.h b/src/object/sp-solid-color.h index 6b5b4726adbdae2d4a4190c1ed67260cd561fc0a..f49ef15fdc07fd4756ed266f8fe533645e682997 100644 --- a/src/object/sp-solid-color.h +++ b/src/object/sp-solid-color.h @@ -12,7 +12,6 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ -#include "color.h" #include "sp-paint-server.h" typedef struct _cairo cairo_t; diff --git a/src/object/sp-stop.cpp b/src/object/sp-stop.cpp index 2886688695b89d321c06a7ab2b841200dc618052..4a644bb50cfde2764b35d1e3c84eb861639aecef 100644 --- a/src/object/sp-stop.cpp +++ b/src/object/sp-stop.cpp @@ -20,7 +20,6 @@ #include "attributes.h" #include "streq.h" #include "svg/svg.h" -#include "svg/svg-color.h" #include "svg/css-ostringstream.h" SPStop::SPStop() : SPObject() { @@ -128,45 +127,33 @@ SPStop* SPStop::getPrevStop() { return result; } -SPColor SPStop::getColor() const +Inkscape::Colors::Color SPStop::getColor() const { - if (style->stop_color.currentcolor) { - return style->color.value.color; - } - return style->stop_color.value.color; -} - -gfloat SPStop::getOpacity() const -{ - return SP_SCALE24_TO_FLOAT(style->stop_opacity.value); + // Copy color from the right place + Inkscape::Colors::Color color = style->stop_color.currentcolor ? style->color.getColor() : style->stop_color.getColor(); + // Bundle stop opacity into the color + color.addOpacity(style->stop_opacity); + return color; } /** * Sets the stop color and stop opacity in the style attribute. */ -void SPStop::setColor(SPColor color, double opacity) +void SPStop::setColor(Inkscape::Colors::Color const &color) { - setColorRepr(getRepr(), color, opacity); + setColorRepr(getRepr(), color); } /** * Set the color and opacity directly into the given xml repr. */ -void SPStop::setColorRepr(Inkscape::XML::Node *node, SPColor color, double opacity) +void SPStop::setColorRepr(Inkscape::XML::Node *node, Inkscape::Colors::Color const &color) { Inkscape::CSSOStringStream os; - os << "stop-color:" << color.toString() << ";stop-opacity:" << opacity <<";"; + os << "stop-color:" << color.toString(false) << ";stop-opacity:" << color.getOpacity() <<";"; node->setAttribute("style", os.str()); } -/** - * Return stop's color as 32bit value. - */ -guint32 SPStop::get_rgba32() const -{ - return getColor().toRGBA32(getOpacity()); -} - /* Local Variables: mode:c++ diff --git a/src/object/sp-stop.h b/src/object/sp-stop.h index f22b708f064dc5956f52afed1348499a64b55f1c..a823053971b6d26566e0e55f0cd2c1322df08b9d 100644 --- a/src/object/sp-stop.h +++ b/src/object/sp-stop.h @@ -19,11 +19,14 @@ #include -#include "color.h" #include "sp-object.h" typedef unsigned int guint32; +namespace Inkscape::Colors { + class Color; +} + /** Gradient stop. */ class SPStop final : public SPObject { public: @@ -39,12 +42,10 @@ public: SPStop* getNextStop(); SPStop* getPrevStop(); - SPColor getColor() const; - gfloat getOpacity() const; - guint32 get_rgba32() const; - void setColor(SPColor color, double opacity); + Inkscape::Colors::Color getColor() const; + void setColor(Inkscape::Colors::Color const &color); - static void setColorRepr(Inkscape::XML::Node *node, SPColor color, double opacity); + static void setColorRepr(Inkscape::XML::Node *node, Inkscape::Colors::Color const &color); protected: void build(SPDocument* doc, Inkscape::XML::Node* repr) override; diff --git a/src/oklab.h b/src/oklab.h deleted file mode 100644 index 65c0bd1458ecb67d6998c772708bc82ae8d0279c..0000000000000000000000000000000000000000 --- a/src/oklab.h +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file Support for the OKLab/OKLch perceptual color space. - */ -/* - * Authors: - * Rafał Siejakowski - * - * Copyright (C) 2022 Authors - * - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ - -#ifndef SEEN_OKLAB_H -#define SEEN_OKLAB_H - -#include - -#include "hsluv.h" - -namespace Oklab { - -using Triplet = Hsluv::Triplet; -using Hsluv::to_linear, Hsluv::from_linear; - -/** @brief Convert an OKLab color to the linear RGB color space. - * - * @param oklab_color A triplet containing the L, a, b components. - * @return a triplet containing linear (de-gamma'd) RGB components, all in [0, 1]. - */ -Triplet oklab_to_linear_rgb(Triplet const &oklab_color); - -/** @brief Convert a linear RGB color to OKLab coordinates. - * - * @param linear_rgb_color A triplet containing degamma'd (linearized) R, G, B components. - * @return a triplet containing the (L, a, b) coordinates of the color in the OKLab space. - */ -Triplet linear_rgb_to_oklab(Triplet const &linear_rgb_color); - -/** Convert an OKLab color to a gamma-compressed sRGB color. */ -inline Triplet oklab_to_rgb(Triplet const &oklab_color) -{ - auto rgb = oklab_to_linear_rgb(oklab_color); - for (auto &component : rgb) { - component = from_linear(component); - } - return rgb; -} - -/** Convert a gamma-compressed sRGB color to an OKLab color. */ -inline Triplet rgb_to_oklab(Triplet const &rgb_color) -{ - Triplet linear; - for (size_t i = 0; i < 3; i++) { - linear[i] = to_linear(rgb_color[i]); - } - return linear_rgb_to_oklab(linear); -} - -/** @brief Convert an OKLab color to the OKLch coordinates. - * - * The OKLch coordinates are more closely aligned with the perceptual properties of a color - * and therefore are more convenient for the end user. They consist of: - * L – luminance of the color, in the interval [0, 1] (this is the same as the L in (L, a, b)). - * c – chroma; how far the color is from grayscale. The range of c-values is [0, cmax] but - * cmax depends on L and h; @see Oklab::max_chroma(). - * h – hue. A number in the interval [0, 360), interpreted as a hue angle on the color wheel. - * - * @param ok_lab_color A color in the OKLab color space. - * @return The same color expressed in the Lch coordinates. - */ -Triplet oklab_to_oklch(Triplet const &ok_lab_color); - -/** @brief Convert an OKLch color to the OKLab coordinates. - * - * For the meaning of the Lch color coordinates, @see oklab_to_oklch(). - */ -Triplet oklch_to_oklab(Triplet const &ok_lch_color); -Triplet oklch_radians_to_oklab(Triplet const &ok_lch_with_hue_in_radians); - -/** @brief Convert an OKLab color to an OKHSL representation. - * - * As of late 2022, OK-HSL (hue, saturation, lightness) is not a fully standardized color - * space. The version used here stores colors as triples (h, s, L) of doubles, all in - * the interval [0, 1]. These coordinates have the following meaning: - * - * \li \c h is the hue angle, scaled to the interval [0, 1]. - * \li \c s is the linear saturation. For a given OKLch color (L, c, h), linear saturation - * is the ratio of c to the maximum possible chroma c_max such that (L, c_max, h) - * fits in the sRGB gamut. Therefore, s=1 always corresponds to a maximally saturated - * color and s=0 is always a grayscale color. - * \li \c L is the lightness; it is the same coordinate as the L in OKLab or OKLch. - * - * Note that Björn Ottosson proposes a new, non-standard way of compressing the saturation - * (similar to gamma compression) which results in a different saturation scale. He further - * suggests varying this compression depending on the hue, although he does not standardize - * this proposed OKHSL space or the saturation transfer functions. Instead, he uses a somewhat - * ad-hoc piecewise-linear saturation transfer function in his own picker, which unfortunately - * breaks differentiability of the parametrization, a great advantage of the OKLch color space. - * If an OKHSL space is ever standardized, the behaviour of this function may change. - * See https://bottosson.github.io/posts/colorpicker/ for more details. - * - * @param ok_lab_color A color in the OKLab color space. - * @return The same color expressed in the (h, s, L) coordinates. - */ -Triplet oklab_to_okhsl(Triplet const &ok_lab_color); - -/** @brief Convert an OKHSL color to the OKLab coordinates. - * - * For a description of the OKHSL color scale used here, see oklab_to_okhsl(). - * If an OKHSL space is ever standardized, the behaviour of this function may change. - * - * @param ok_hsl_color A color in the OKHSL coordinates, all normalized to [0, 1]. - * @return The same color expressed in the (L, a, b) coordinates. - */ -Triplet okhsl_to_oklab(Triplet const &ok_hsl_color); - -/** @brief Find the maximum OKLch chroma for the given luminosity and hue. - * - * @param l OKLab/OKLch luminosity, in the interval [0, 1]. - * @param h OKLch hue angle in degrees (interval [0, 360]). - * @return The maximum chroma c such that the color oklch(l, c, h) fits in the sRGB gamut. - */ -double max_chroma(double l, double h); - -/** @brief Helper functions for rendering color strips used in color sliders. - * - * @param h Hue angle in degrees (range 0-360). - * @param s Saturation (percentage of allowed chroma) in the range 0-1. - * @param l OKLab/OKLch lightness in the range 0-1. - * @param[out] map Working store and RGB output showing the range of one of the color coordinates. - * @return A pointer to the raw data of the map. - */ -uint8_t const *render_hue_scale(double s, double l, std::array *map); -uint8_t const *render_saturation_scale(double h, double l, std::array *map); -uint8_t const *render_lightness_scale(double h, double s, std::array *map); - -} // namespace Oklab - -#endif // SEEN_OKLAB_H - -/* - 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 : \ No newline at end of file diff --git a/src/page-manager.cpp b/src/page-manager.cpp index d91ed03a98a8240fde7dcf8dd206d32009205a90..3324d3ef8802261143eea1e385d4c646bb96e2b0 100644 --- a/src/page-manager.cpp +++ b/src/page-manager.cpp @@ -11,6 +11,7 @@ #include #include "attributes.h" +#include "colors/manager.h" #include "desktop.h" #include "display/control/canvas-page.h" #include "document.h" @@ -21,12 +22,16 @@ #include "object/sp-page.h" #include "object/sp-root.h" #include "selection-chemistry.h" -#include "svg/svg-color.h" #include "util/parse-int-range.h" #include "util/numeric/converters.h" namespace Inkscape { +static auto const default_background_color = Colors::Color{0xffffff00}; +static auto const default_margin_color = Colors::Color{0x1699d751}; +static auto const default_bleed_color = Colors::Color{0xbe310e31}; +static auto const default_border_color = Colors::Color{0x0000003f}; + bool PageManager::move_objects() { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); @@ -38,6 +43,10 @@ PageManager::PageManager(SPDocument *document) , border_on_top(true) , shadow_show(true) , checkerboard(false) + , background_color{default_background_color} + , margin_color{default_margin_color} + , bleed_color{default_bleed_color} + , border_color{default_border_color} { _document = document; } @@ -675,19 +684,17 @@ bool PageManager::subset(SPAttr key, const gchar *value) case SPAttr::BORDERLAYER: this->border_on_top.readOrUnset(value); break; - case SPAttr::BORDERCOLOR: - this->border_color = this->border_color & 0xff; - if (value) { - this->border_color = this->border_color | sp_svg_read_color(value, this->border_color); - } + case SPAttr::BORDERCOLOR: { + auto const old_opacity = border_color.getOpacity(); + border_color = Colors::Color::parse(value).value_or(default_border_color); + border_color.setOpacity(old_opacity); break; + } case SPAttr::BORDEROPACITY: - sp_ink_read_opacity(value, &this->border_color, 0x000000ff); + border_color.setOpacity(value ? g_ascii_strtod(value, nullptr) : 1.0); break; case SPAttr::PAGECOLOR: - if (value) { - this->background_color = sp_svg_read_color(value, this->background_color) | 0xff; - } + background_color = Colors::Color::parse(value).value_or(default_background_color); break; case SPAttr::SHOWPAGESHADOW: // Deprecated this->shadow_show.readOrUnset(value); @@ -714,14 +721,28 @@ bool PageManager::subset(SPAttr key, const gchar *value) */ bool PageManager::setDefaultAttributes(Inkscape::CanvasPage *item) { + auto bdcolor = getBorderColor(); + + if (!border_show) { + bdcolor.setOpacity(0.0); + } + + auto bgcolor = getBackgroundColor(); + // note: page background color doesn't have configurable transparency; it is considered to be opaque; // here alpha gets manipulated to reveal checkerboard pattern, if needed - auto bgcolor = checkerboard ? background_color & ~0xff : background_color | 0xff; - auto dkcolor = _document->getNamedView()->desk_color; + if (checkerboard) { + bgcolor.setOpacity(0.0); + } else { + bgcolor.setOpacity(1.0); + } + + auto dkcolor = _document->getNamedView()->getDeskColor(); + bool ret = item->setOnTop(border_on_top); // fixed shadow size, not configurable; shadow changes size with zoom ret |= item->setShadow(border_show && shadow_show ? 2 : 0); - ret |= item->setPageColor(border_show ? border_color : 0x0, bgcolor, dkcolor, margin_color, bleed_color); + ret |= item->setPageColor(bdcolor, bgcolor, dkcolor, getMarginColor(), getBleedColor()); ret |= item->setLabelStyle(label_style); return ret; } diff --git a/src/page-manager.h b/src/page-manager.h index 36d154293ddd189cc10f73cc54a655c342e258db..e239cdefd34ea4269755beb45c5923a940218678 100644 --- a/src/page-manager.h +++ b/src/page-manager.h @@ -11,8 +11,8 @@ #define SEEN_INKSCAPE_PAGE_MANAGER_H #include +#include -#include "color-rgba.h" #include "document.h" #include "object/sp-namedview.h" #include "svg/svg-bool.h" @@ -29,6 +29,9 @@ namespace Dialog { class DocumentProperties; } } // namespace UI +namespace Colors { +class Color; +} class PageManager { @@ -75,7 +78,7 @@ public: bool hasNextPage() const { return getSelectedPageIndex() + 1 < pages.size(); } bool hasPrevPage() const { return getSelectedPageIndex() - 1 >= 0; } - ColorRGBA getDefaultBackgroundColor() const { return ColorRGBA(background_color); } + Colors::Color const &getDefaultBackgroundColor() const { return background_color; } // TODO: move these functions out of here and into the Canvas or InkscapeWindow void zoomToPage(SPDesktop *desktop, SPPage *page, bool width_only = false); @@ -121,10 +124,10 @@ public: return _pages_changed_signal.connect(slot); } - // Access from export.cpp and others for the guint32 - guint32 background_color = 0xffffff00; - guint32 margin_color = 0x1699d751; - guint32 bleed_color = 0xbe310e31; + Colors::Color const &getBackgroundColor() const { return background_color; } + Colors::Color const &getMarginColor() const { return margin_color; } + Colors::Color const &getBleedColor() const { return bleed_color; } + Colors::Color const &getBorderColor() const { return border_color; } void movePages(Geom::Affine tr); std::vector getOverlappingItems(SPDesktop *desktop, SPPage *page, bool hidden = true, bool in_bleed = false, bool in_layers = true); @@ -138,7 +141,6 @@ protected: SVGBool shadow_show; SVGBool checkerboard; - guint32 border_color = 0x0000003f; std::string label_style = "default"; private: @@ -151,6 +153,11 @@ private: sigc::signal _pages_changed_signal; sigc::connection _page_modified_connection; + + Colors::Color background_color; + Colors::Color margin_color; + Colors::Color bleed_color; + Colors::Color border_color; }; } // namespace Inkscape diff --git a/src/pattern-manager.cpp b/src/pattern-manager.cpp index 4a08f0d31c5d56e3912edd58e2cd7156bd18be56..be8f0a3898f0de864bf6028e0ae3a4d6afbb1e93 100644 --- a/src/pattern-manager.cpp +++ b/src/pattern-manager.cpp @@ -179,7 +179,7 @@ Glib::RefPtr create_pattern_item(SPDocument *sandbox, SPPattern* pa // reading color style form "root" pattern; linked one won't have any effect, as it's not a parrent if (root_pattern->style && root_pattern->style->isSet(SPAttr::FILL) && root_pattern->style->fill.isColor()) { - item->color.emplace(SPColor(root_pattern->style->fill.value.color)); + item->color.emplace(root_pattern->style->fill.getColor()); } // uniform scaling? if (link_pattern->aspect_set) { diff --git a/src/pattern-manipulation.cpp b/src/pattern-manipulation.cpp index 29b54ff21f3bc26716c2cb357edc46d96795ae10..6d4b2b28132db679223b5156c69f0a9214cddf55 100644 --- a/src/pattern-manipulation.cpp +++ b/src/pattern-manipulation.cpp @@ -3,8 +3,8 @@ #include #include #include "pattern-manipulation.h" +#include "colors/color.h" #include "document.h" -#include "color.h" #include "helper/stock-items.h" #include "object/sp-pattern.h" #include "xml/repr.h" @@ -34,12 +34,12 @@ std::vector sp_get_pattern_list(SPDocument* source) { return list; } -void sp_pattern_set_color(SPPattern* pattern, unsigned int color) { +void sp_pattern_set_color(SPPattern* pattern, Inkscape::Colors::Color const &c) +{ if (!pattern) return; - SPColor c(color); SPCSSAttr* css = sp_repr_css_attr_new(); - sp_repr_css_set_property(css, "fill", c.toString().c_str()); + sp_repr_css_set_property_string(css, "fill", c.toString()); pattern->changeCSS(css, "style"); sp_repr_css_attr_unref(css); } diff --git a/src/pattern-manipulation.h b/src/pattern-manipulation.h index 45d1e26c8b1fd16c071e7a586e35ae716e2ee82b..5d55c5d7465652bfb80598d0b168655c3fe7b699 100644 --- a/src/pattern-manipulation.h +++ b/src/pattern-manipulation.h @@ -10,6 +10,10 @@ class SPDocument; class SPPattern; +namespace Inkscape::Colors { +class Color; +} + // Find and load stock patterns if not yet loaded and return them. // Their lifetime is bound to StockPaintDocuments. std::vector sp_get_stock_patterns(); @@ -22,7 +26,7 @@ std::vector sp_get_pattern_list(SPDocument* source); // Set fill color for a pattern. // If elements comprising pattern have no fill, they will inherit it. // Some patterns may not be affected at all if not designed to support color change. -void sp_pattern_set_color(SPPattern* pattern, unsigned int color); +void sp_pattern_set_color(SPPattern* pattern, Inkscape::Colors::Color const &color); // Set 'patternTransform' attribute void sp_pattern_set_transform(SPPattern* pattern, const Geom::Affine& transform); diff --git a/src/preferences.cpp b/src/preferences.cpp index e16720a5a263749bf218ac702e13c0f9b07870cb..4ccb41aa91ae669fb4a9ef89d459ae74a567fb19 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -21,6 +21,8 @@ #include #include "attribute-rel-util.h" +#include "colors/color.h" +#include "colors/manager.h" #include "preferences.h" #include "preferences-skeleton.h" #include "io/resource.h" @@ -423,11 +425,9 @@ void Preferences::setDoubleUnit(Glib::ustring const &pref_path, double value, Gl _setRawValue(pref_path, str); } -void Preferences::setColor(Glib::ustring const &pref_path, guint32 value) +void Preferences::setColor(Glib::ustring const &pref_path, Colors::Color const &color) { - gchar buf[16]; - g_snprintf(buf, 16, "#%08x", value); - _setRawValue(pref_path, buf); + _setRawValue(pref_path, color.toString()); } /** @@ -898,23 +898,6 @@ Glib::ustring Preferences::_extractUnit(Entry const &v) } } -guint32 Preferences::_extractColor(Entry const &v) -{ - if (v.cached_color) return v.value_color; - v.cached_color = true; - gchar const *s = static_cast(v._value); - std::istringstream hr(s); - guint32 color; - if (s[0] == '#') { - hr.ignore(1); - hr >> std::hex >> color; - } else { - hr >> color; - } - v.value_color = color; - return color; -} - SPCSSAttr *Preferences::_extractStyle(Entry const &v) { if (v.cached_style) return v.value_style; @@ -1017,6 +1000,26 @@ PrefObserver Preferences::createObserver(Glib::ustring path, std::function(_value))) { + return *res; + } + } + if (auto res = Colors::Color::parse(def)) { + return *res; + } + // Transparent black is the default's default + return Colors::Color(0x00000000); +} + } // namespace Inkscape /* diff --git a/src/preferences.h b/src/preferences.h index 40a1f75c225da72490903e87caa783c9254be4a4..285dcb4732d09e1c35d96a4d4f69a1e48a4b7063 100644 --- a/src/preferences.h +++ b/src/preferences.h @@ -32,6 +32,9 @@ class SPCSSAttr; typedef unsigned int guint32; namespace Inkscape { +namespace Colors { +class Color; +} class ErrorReporter { public: @@ -230,9 +233,9 @@ public: inline Glib::ustring getUnit() const; /** - * Interpret the preference as an RGBA color value. + * Interpret the preference as a css color value. */ - inline guint32 getColor(guint32 def) const; + inline Colors::Color getColor(std::string const &def) const; /** * Interpret the preference as a CSS style. @@ -276,7 +279,6 @@ public: mutable unsigned int value_uint = 0; mutable double value_double = 0.; mutable Glib::ustring value_unit; - mutable guint32 value_color = 0; mutable SPCSSAttr* value_style = nullptr; mutable bool cached_bool = false; @@ -458,9 +460,7 @@ public: return getEntry(pref_path).getUnit(); } - guint32 getColor(Glib::ustring const &pref_path, guint32 def=0x000000ff) { - return getEntry(pref_path).getColor(def); - } + Colors::Color getColor(Glib::ustring const &pref_path, std::string const &def = "black"); /** * Retrieve a CSS style. @@ -541,7 +541,7 @@ public: /** * Set an RGBA color value. */ - void setColor(Glib::ustring const &pref_path, guint32 value); + void setColor(Glib::ustring const &pref_path, Colors::Color const &color); /** * Set a CSS style. @@ -655,7 +655,6 @@ protected: double _extractDouble(Entry const &v, Glib::ustring const &requested_unit); Glib::ustring _extractString(Entry const &v); Glib::ustring _extractUnit(Entry const &v); - guint32 _extractColor(Entry const &v); SPCSSAttr *_extractStyle(Entry const &v); SPCSSAttr *_extractInheritedStyle(Entry const &v); @@ -792,15 +791,6 @@ inline Glib::ustring Preferences::Entry::getUnit() const } } -inline guint32 Preferences::Entry::getColor(guint32 def) const -{ - if (!this->isValid()) { - return def; - } else { - return Inkscape::Preferences::get()->_extractColor(*this); - } -} - inline SPCSSAttr *Preferences::Entry::getStyle() const { if (!this->isValid()) { diff --git a/src/profile-manager.cpp b/src/profile-manager.cpp deleted file mode 100644 index b092980ec8d780707c860658b998bc4de27af415..0000000000000000000000000000000000000000 --- a/src/profile-manager.cpp +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Inkscape::ProfileManager - a view of a document's color profiles. - * - * Copyright 2007 Jon A. Cruz - * Abhishek Sharma - * - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ - -#include -#include - -#include "profile-manager.h" - -#include "document.h" - -#include "object/color-profile.h" - - -namespace Inkscape { - -ProfileManager::ProfileManager(SPDocument *document) : - _doc(document), - _knownProfiles() -{ - _resource_connection = _doc->connectResourcesChanged( "iccprofile", sigc::mem_fun(*this, &ProfileManager::_resourcesChanged) ); -} - -ProfileManager::~ProfileManager() -{ - _resource_connection.disconnect(); - _doc = nullptr; -} - -void ProfileManager::_resourcesChanged() -{ - std::vector newList; - if (_doc) { - std::vector current = _doc->getResourceList( "iccprofile" ); - newList = current; - } - sort( newList.begin(), newList.end() ); - - std::vector diff1; - std::set_difference( _knownProfiles.begin(), _knownProfiles.end(), newList.begin(), newList.end(), - std::insert_iterator >(diff1, diff1.begin()) ); - - std::vector diff2; - std::set_difference( newList.begin(), newList.end(), _knownProfiles.begin(), _knownProfiles.end(), - std::insert_iterator >(diff2, diff2.begin()) ); - - if ( !diff1.empty() ) { - for ( std::vector::iterator it = diff1.begin(); it < diff1.end(); ++it ) { - SPObject* tmp = *it; - _knownProfiles.erase( remove(_knownProfiles.begin(), _knownProfiles.end(), tmp), _knownProfiles.end() ); - if ( includes(tmp) ) { - _removeOne(tmp); - } - } - } - - if ( !diff2.empty() ) { - for ( std::vector::iterator it = diff2.begin(); it < diff2.end(); ++it ) { - SPObject* tmp = *it; - _knownProfiles.push_back(tmp); - _addOne(tmp); - } - sort( _knownProfiles.begin(), _knownProfiles.end() ); - } -} - -ColorProfile* ProfileManager::find(gchar const* name) -{ - ColorProfile* match = nullptr; - if ( name ) { - unsigned int howMany = childCount(nullptr); - for ( unsigned int index = 0; index < howMany; index++ ) { - SPObject *obj = nthChildOf(nullptr, index); - ColorProfile* prof = reinterpret_cast(obj); - if (prof && (prof->name && !strcmp(name, prof->name))) { - match = prof; - break; - } - } - } - return match; -} - -} - - -/* - 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 : diff --git a/src/profile-manager.h b/src/profile-manager.h deleted file mode 100644 index d9ad037e0988c73f1a5a1728111c0850f0ee0ccc..0000000000000000000000000000000000000000 --- a/src/profile-manager.h +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Inkscape::ProfileManager - a view of a document's color profiles. - * - * Copyright 2007 Jon A. Cruz - * - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ - -#ifndef SEEN_INKSCAPE_PROFILE_MANAGER_H -#define SEEN_INKSCAPE_PROFILE_MANAGER_H - -#include - -#include "document-subset.h" - -class SPDocument; - -namespace Inkscape { - -class ColorProfile; - -class ProfileManager : public DocumentSubset -{ -public: - ProfileManager(SPDocument *document); - ~ProfileManager(); - - ColorProfile* find(char const* name); - -private: - ProfileManager(ProfileManager const &) = delete; // no copy - void operator=(ProfileManager const &) = delete; // no assign - - void _resourcesChanged(); - - SPDocument* _doc; - sigc::connection _resource_connection; - std::vector _knownProfiles; -}; - -} - -#endif // SEEN_INKSCAPE_PROFILE_MANAGER_H - -/* - 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 : diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp index e5f3f4f840389febae8a510aed5f425070006124..9c5e98272cc5920fb9544d89b58def56a14da468 100644 --- a/src/selection-chemistry.cpp +++ b/src/selection-chemistry.cpp @@ -83,7 +83,6 @@ #include "path-chemistry.h" #include "selection.h" #include "style.h" -#include "svg/svg-color.h" #include "svg/svg.h" #include "text-chemistry.h" #include "text-editing.h" @@ -2085,8 +2084,8 @@ std::vector sp_get_same_fill_or_stroke_color(SPItem *sel, std::vectorstyle->getFillOrStroke(type == SP_FILL_COLOR); match = false; - if (sel_paint->isColor() && iter_paint->isColor() // color == color comparison doesn't seem to work here. - && (sel_paint->value.color.toRGBA32(1.0) == iter_paint->value.color.toRGBA32(1.0))) { + if (sel_paint->isColor() && iter_paint->isColor() + && (sel_paint->getColor().isSimilar(iter_paint->getColor()))) { match = true; } else if (sel_paint->isPaintserver() && iter_paint->isPaintserver()) { @@ -4168,10 +4167,9 @@ void ObjectSet::swapFillStroke() if (paint->set && paint->isNone()) sp_repr_css_set_property (css, "stroke", "none"); else if (paint->set && paint->isColor()) { - guint32 color = paint->value.color.toRGBA32(SP_SCALE24_TO_FLOAT (item->style->fill_opacity.value)); - gchar c[64]; - sp_svg_write_color (c, sizeof(c), color); - sp_repr_css_set_property (css, "stroke", c); + auto color = paint->getColor(); + color.addOpacity(item->style->fill_opacity); + sp_repr_css_set_property_string(css, "stroke", color.toString()); } else if (!paint->set) sp_repr_css_unset_property (css, "stroke"); @@ -4191,10 +4189,9 @@ void ObjectSet::swapFillStroke() if (paint->set && paint->isNone()) sp_repr_css_set_property (css, "fill", "none"); else if (paint->set && paint->isColor()) { - guint32 color = paint->value.color.toRGBA32(SP_SCALE24_TO_FLOAT (item->style->stroke_opacity.value)); - gchar c[64]; - sp_svg_write_color (c, sizeof(c), color); - sp_repr_css_set_property (css, "fill", c); + auto color = paint->getColor(); + color.addOpacity(item->style->stroke_opacity); + sp_repr_css_set_property_string(css, "fill", color.toString()); } else if (!paint->set) sp_repr_css_unset_property (css, "fill"); diff --git a/src/style-internal.cpp b/src/style-internal.cpp index c0945784ab5bea5b9547070b9695aa64853c7bf1..b5deabf1ed91016edc401a526fcefde80b3759b2 100644 --- a/src/style-internal.cpp +++ b/src/style-internal.cpp @@ -31,6 +31,11 @@ #include "style-internal.h" #include "style.h" +#include "colors/color.h" +#include "colors/document-cms.h" + +#include "document.h" + #include "bad-uri-exception.h" #include "extract-uri.h" #include "preferences.h" @@ -1387,13 +1392,57 @@ void SPIShapes::hrefs_clear() // SPIColor ------------------------------------------------------------- +SPIColor::SPIColor(bool inherits) + : SPIBase(inherits) +{} + +bool SPIColor::canHaveCMS() const +{ + return style && style->document; +} + +Colors::DocumentCMS const &SPIColor::getCMS() const +{ + if (!style || !style->document) { + g_error("Can not get Document CMS manager."); + } + return style->document->getDocumentCMS(); +} + +SPIColor& SPIColor::operator=(const SPIColor& rhs) +{ + SPIBase::operator=(rhs); + currentcolor = rhs.currentcolor; + _color = rhs._color; + return *this; +} + +SPIColor& SPIColor::operator=(const Colors::Color &rhs) +{ + auto copy = rhs; + setColor(copy); + return *this; +} + +void SPIColor::setColor(Colors::Color const &other) +{ + currentcolor = false; + _color = other; + set = true; +} + +Colors::Color const &SPIColor::getColor() const +{ + static auto default_color = Colors::Color(0x0); // Transparent RGB black + return _color ? *_color : default_color; +} + // Used for 'color', 'text-decoration-color', 'flood-color', 'lighting-color', and 'stop-color'. -// (The last three have yet to be implemented.) // CSS3: 'currentcolor' is allowed value and is equal to inherit for the 'color' property. -// FIXME: We should preserve named colors, hsl colors, etc. -void SPIColor::read( gchar const *str ) { - if( !str ) return; +void SPIColor::read(gchar const *str) +{ + if(!str) return; set = false; inherit = false; @@ -1407,12 +1456,22 @@ void SPIColor::read( gchar const *str ) { if (id() == SPAttr::COLOR) { inherit = true; // CSS3 } else if (style) { - setColor( style->color.value.color ); + _color = style->color._color; } else { std::cerr << "SPIColor::read(): value is 'currentColor' but 'color' not available." << std::endl; } } else { - set = value.color.fromString(str); + if (canHaveCMS()) { + _color = getCMS().parse(str); + } else { + if (str) { // DEBUG + if (std::string(str).find("icc") != std::string::npos) { + g_error("CMS color '%s' not parsed, no CMS document available.", str); + } + } + _color = Colors::Color::parse(str); + } + set = (bool)_color; } } @@ -1421,15 +1480,16 @@ const Glib::ustring SPIColor::get_value() const // currentcolor goes first to handle special case for 'color' property if (this->currentcolor) return Glib::ustring("currentColor"); if (this->inherit) return Glib::ustring("inherit"); - return this->value.color.toString(); + return _color ? _color->toString() : ""; } void SPIColor::cascade( const SPIBase* const parent ) { if( const SPIColor* p = dynamic_cast(parent) ) { if( (inherits && !set) || inherit) { // FIXME verify for 'color' - if( !(inherit && currentcolor) ) currentcolor = p->currentcolor; - setColor( p->value.color ); + if (!(inherit && currentcolor)) + currentcolor = p->currentcolor; + _color = p->_color; } else { // Add CSS4 Color: Lighter, Darker } @@ -1447,7 +1507,7 @@ SPIColor::merge( const SPIBase* const parent ) { set = p->set; inherit = p->inherit; currentcolor = p->currentcolor; - value.color = p->value.color; + _color = p->_color; } } } @@ -1457,11 +1517,15 @@ bool SPIColor::equals(const SPIBase& rhs) const { if( const SPIColor* r = dynamic_cast(&rhs) ) { - // ICC support is handled by SPColor== - if (currentcolor != r->currentcolor || value.color != r->value.color) { + if (currentcolor != r->currentcolor) { + return false; + } + if ((_color && !r->_color) || (!_color && r->_color)) { + return false; + } + if (_color && r->_color && *_color != *r->_color) { return false; } - return SPIBase::equals(rhs); } else { @@ -1479,6 +1543,24 @@ SPIColor::equals(const SPIBase& rhs) const { // find the object for creating an href (this is done through document but should be done // directly so document not needed.. FIXME). +SPIPaint::SPIPaint() +{ + clear(); +} + +bool SPIPaint::canHaveCMS() const +{ + return style && style->document; +} + +Colors::DocumentCMS const &SPIPaint::getCMS() const +{ + if (!style || !style->document) { + g_error("Can not get Document CMS manager."); + } + return style->document->getDocumentCMS(); +} + /** * Set SPIPaint object from string. * @@ -1529,21 +1611,21 @@ SPIPaint::read( gchar const *str ) { SPDocument *document = (style->object) ? style->object->document : nullptr; // Create href if not done already - if (!value.href) { + if (!href) { if (style->object) { - value.href = std::make_shared(style->object); + href = std::make_shared(style->object); } else if (document) { - value.href = std::make_shared(document); + href = std::make_shared(document); } else { std::cerr << "SPIPaint::read: No valid object or document!" << std::endl; return; } if (this == &style->fill) { - style->fill_ps_changed_connection = value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), style)); + style->fill_ps_changed_connection = href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), style)); } else { - style->stroke_ps_changed_connection = value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), style)); + style->stroke_ps_changed_connection = href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), style)); } } @@ -1552,6 +1634,7 @@ SPIPaint::read( gchar const *str ) { } } + // Continue to parse after url for fallback colors. while ( g_ascii_isspace(*str) ) { ++str; } @@ -1560,14 +1643,14 @@ SPIPaint::read( gchar const *str ) { set = true; paintOrigin = SP_CSS_PAINT_ORIGIN_CURRENT_COLOR; if (style) { - setColor( style->color.value.color ); + setColor(style->color.getColor()); } else { // Normally an SPIPaint is part of an SPStyle and the value of 'color' is // available. SPIPaint can be used 'stand-alone' (e.g. to parse color values) in // which case a value of 'currentColor' is meaningless, thus we shouldn't reach // here. std::cerr << "SPIPaint::read(): value is 'currentColor' but 'color' not available." << std::endl; - setColor( 0 ); + setColor(Colors::Color(0x000000ff)); } } else if (streq(str, "context-fill")) { set = true; @@ -1578,30 +1661,21 @@ SPIPaint::read( gchar const *str ) { } else if (streq(str, "none")) { set = true; noneSet = true; - } else if (value.color.fromString(str)) { + } else if (auto color = canHaveCMS() ? getCMS().parse(str) : Colors::Color::parse(str)) { set = true; - colorSet = true; + _color = color; } } } -// Stand-alone read (Legacy read()), used multiple places, e.g. sp-stop.cpp -// This function should not be necessary. FIXME -void -SPIPaint::read( gchar const *str, SPStyle &style_in, SPDocument *document_in ) { - style = &style_in; - style->document = document_in; - read( str ); -} - const Glib::ustring SPIPaint::get_value() const { if (this->inherit) return Glib::ustring("inherit"); if (this->noneSet) return Glib::ustring("none"); // url must go first as other values can serve as fallbacks auto ret = Glib::ustring(""); - if (this->value.href && this->value.href->getURI()) { - ret += this->value.href->getURI()->cssStr(); + if (this->href && this->href->getURI()) { + ret += this->href->getURI()->cssStr(); } switch(this->paintOrigin) { case SP_CSS_PAINT_ORIGIN_CURRENT_COLOR: @@ -1617,15 +1691,29 @@ const Glib::ustring SPIPaint::get_value() const ret += "context-stroke"; break; case SP_CSS_PAINT_ORIGIN_NORMAL: - if (this->colorSet) { + if (_color) { if (!ret.empty()) ret += " "; - ret += value.color.toString(); + ret += _color->toString(); } break; } return ret; } +void SPIPaint::setColor(Colors::Color const &other) +{ + _color = other; + set = true; +} + +Colors::Color const &SPIPaint::getColor() const +{ + static auto default_color = Colors::Color(0x0); // Transparent RGB black + if (_color) + return *_color; + return default_color; +} + void SPIPaint::clear() { // std::cout << "SPIPaint::clear(): " << name << std::endl; @@ -1638,15 +1726,13 @@ SPIPaint::reset( bool init ) { // std::cout << "SPIPaint::reset(): " << name << " " << init << std::endl; SPIBase::clear(); paintOrigin = SP_CSS_PAINT_ORIGIN_NORMAL; - colorSet = false; noneSet = false; - value.color.set(0x0); - value.color.unsetColorProfile(); + _color.reset(); tag = nullptr; - value.href.reset(); + href.reset(); if (init && id() == SPAttr::FILL) { - setColor(0.0, 0.0, 0.0); // 'black' is default for 'fill' + _color = Inkscape::Colors::Color(0x000000ff); // 'black' is default for 'fill' } } @@ -1660,19 +1746,19 @@ SPIPaint::cascade( const SPIBase* const parent ) { reset( false ); // Do not init if( p->isPaintserver() ) { - if( p->value.href) { + if( p->href) { // Why can we use p->document ? - sp_style_set_ipaint_to_uri( style, this, p->value.href->getURI(), p->value.href->getOwnerDocument()); + sp_style_set_ipaint_to_uri( style, this, p->href->getURI(), p->href->getOwnerDocument()); } else { std::cerr << "SPIPaint::cascade: Expected paint server not found." << std::endl; } } else if( p->isColor() ) { - setColor( p->value.color ); + _color = p->_color; } else if( p->isNoneSet() ) { noneSet = true; } else if( p->paintOrigin == SP_CSS_PAINT_ORIGIN_CURRENT_COLOR ) { paintOrigin = SP_CSS_PAINT_ORIGIN_CURRENT_COLOR; - setColor( style->color.value.color ); + _color = style->color.getColor(); } else if( isNone() ) { // } else { @@ -1681,7 +1767,7 @@ SPIPaint::cascade( const SPIBase* const parent ) { } else { if( paintOrigin == SP_CSS_PAINT_ORIGIN_CURRENT_COLOR ) { // Update in case color value changed. - setColor( style->color.value.color ); + _color = style->color.getColor(); } } @@ -1715,17 +1801,14 @@ SPIPaint::equals(const SPIBase& rhs) const { } if ( this->isPaintserver() ) { - if( this->value.href == nullptr || r->value.href == nullptr || - this->value.href->getObject() != r->value.href->getObject() ) { + if( this->href == nullptr || r->href == nullptr || + this->href->getObject() != r->href->getObject() ) { return false; } } - if ( this->isColor() ) { - // ICC handled by SPColor== - if (value.color != r->value.color) { - return false; - } + if (this->isColor() && *_color != *r->_color) { + return false; } return SPIBase::equals(rhs); @@ -1957,7 +2040,7 @@ SPIFilter::read( gchar const *str ) { const Glib::ustring SPIFilter::get_value() const { if (this->inherit) return Glib::ustring("inherit"); - if (this->href) return this->href->getURI()->cssStr(); + if (this->href && this->href->getURI()) return this->href->getURI()->cssStr(); return Glib::ustring(""); } @@ -2003,11 +2086,11 @@ SPIFilter::merge( const SPIBase* const parent ) { } } else { // If we don't have an href, create it - if( style->document ) { // FIXME + if (style->object) { + href = new SPFilterReference(style->object); + } else if (style->document) { // FIXME href = new SPFilterReference(style->document); //href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_filter_ref_changed), style)); - } else if (style->object) { - href = new SPFilterReference(style->object); } } if( href ) { diff --git a/src/style-internal.h b/src/style-internal.h index 6c2687328017478103a19f775ce220d553aaf4ed..4574e2b55bc1ade3c1a03b42e97aa1289c18a6c1 100644 --- a/src/style-internal.h +++ b/src/style-internal.h @@ -25,8 +25,6 @@ #include "attributes.h" #include "style-enums.h" -#include "color.h" - #include "object/sp-marker-loc.h" #include "object/sp-filter.h" #include "object/sp-filter-reference.h" @@ -35,14 +33,21 @@ #include "object/uri.h" -#include "svg/svg-icc-color.h" +// TODO: Remove include when we figure out how to store Color without the full class +#include "colors/color.h" #include "xml/repr.h" namespace Inkscape { class ObjectSet; +namespace Colors { +class Color; +class DocumentCMS; +} }; +using namespace Inkscape; + static const unsigned SP_STYLE_FLAG_ALWAYS (1 << 2); static const unsigned SP_STYLE_FLAG_IFSET (1 << 0); static const unsigned SP_STYLE_FLAG_IFDIFF (1 << 1); @@ -307,12 +312,18 @@ public: ~SPIScale24() override = default; + operator double() const { return SP_SCALE24_TO_FLOAT(value); } + void read( gchar const *str ) override; const Glib::ustring get_value() const override; void clear() override { SPIBase::clear(); value = get_default(); } + void set_double(const double& other) { + value = SP_SCALE24_FROM_FLOAT(other); + set = true; + } void cascade( const SPIBase* const parent ) override; void merge( const SPIBase* const parent ) override; @@ -321,7 +332,6 @@ public: bool equals(const SPIBase& rhs) const override; - // To do: make private public: unsigned value : 24; @@ -661,58 +671,39 @@ public: bool containsAnyShape(Inkscape::ObjectSet *set); }; -/// Color type internal to SPStyle, FIXME Add string value to store SVG named color. class SPIColor : public SPIBase { public: - SPIColor(bool inherits = true) - : SPIBase(inherits) - , currentcolor(false) - { - value.color.set(0); - } + SPIColor(bool inherits = true); + ~SPIColor() override = default; - ~SPIColor() override - = default; + void read(gchar const *str) override; + void read(gchar const *str, SPStyle &style); - void read( gchar const *str ) override; const Glib::ustring get_value() const override; void clear() override { SPIBase::clear(); - value.color.set(0); + _color.reset(); } void cascade( const SPIBase* const parent ) override; void merge( const SPIBase* const parent ) override; - SPIColor& operator=(const SPIColor& rhs) { - SPIBase::operator=(rhs); - currentcolor = rhs.currentcolor; - value.color = rhs.value.color; - return *this; - } + SPIColor& operator=(const SPIColor& rhs); + SPIColor& operator=(const Colors::Color &rhs); bool equals(const SPIBase& rhs) const override; - void setColor( float r, float g, float b ) { - value.color.set( r, g, b ); - } - - void setColor( guint32 val ) { - value.color.set( val ); - } - - void setColor( SPColor const& color ) { - value.color = color; - } + void setColor(Colors::Color const &other); + Colors::Color const &getColor() const; + bool canHaveCMS() const; + Colors::DocumentCMS const &getCMS() const; public: - bool currentcolor : 1; - // FIXME: remove structure and derive SPIPaint from this class. - struct { - SPColor color; - } value; + bool currentcolor = false; +private: + std::optional _color; }; @@ -734,11 +725,9 @@ class SPIPaint : public SPIBase { public: - SPIPaint() { clear(); } - + SPIPaint(); ~SPIPaint() override = default; - void read( gchar const *str ) override; - virtual void read( gchar const *str, SPStyle &style, SPDocument *document = nullptr); + void read(gchar const *str) override; const Glib::ustring get_value() const override; void clear() override; virtual void reset( bool init ); // Used internally when reading or cascading @@ -747,18 +736,17 @@ public: SPIPaint& operator=(const SPIPaint& rhs) { SPIBase::operator=(rhs); - paintOrigin = rhs.paintOrigin; - colorSet = rhs.colorSet; - noneSet = rhs.noneSet; - value.color = rhs.value.color; - value.href = rhs.value.href; + paintOrigin = rhs.paintOrigin; + noneSet = rhs.noneSet; + _color = rhs._color; + href = rhs.href; return *this; } bool equals(const SPIBase& rhs) const override; bool isSameType( SPIPaint const & other ) const { - return (isPaintserver() == other.isPaintserver()) && (colorSet == other.colorSet) && (paintOrigin == other.paintOrigin); + return (isPaintserver() == other.isPaintserver()) && (isColor() == other.isColor()) && (paintOrigin == other.paintOrigin); } bool isNoneSet() const { @@ -766,44 +754,38 @@ public: } bool isNone() const { - return !colorSet && !isPaintserver() && (paintOrigin == SP_CSS_PAINT_ORIGIN_NORMAL); - } // TODO refine + return !_color && !isPaintserver() && (paintOrigin == SP_CSS_PAINT_ORIGIN_NORMAL); + } bool isColor() const { - return colorSet && !isPaintserver(); + return _color && !isPaintserver(); } bool isPaintserver() const { - return value.href && value.href->getObject() != nullptr; + return href && href->getObject() != nullptr; } - void setColor( float r, float g, float b ) { - value.color.set( r, g, b ); colorSet = true; + void setNone() { + noneSet = true; + _color.reset(); } - void setColor( guint32 val ) { - value.color.set( val ); colorSet = true; - } - - void setColor( SPColor const& color ) { - value.color = color; colorSet = true; - } - - void setNone() {noneSet = true; colorSet=false;} - void setTag(SPObject* tag) { this->tag = tag; } SPObject* getTag() { return tag; } + void setColor(Colors::Color const &other); + Colors::Color const &getColor() const; + + bool canHaveCMS() const; + Colors::DocumentCMS const &getCMS() const; // To do: make private public: SPPaintOrigin paintOrigin : 2; - bool colorSet : 1; bool noneSet : 1; - struct { - std::shared_ptr href; - SPColor color; - } value; + std::shared_ptr href; SPObject *tag = nullptr; +private: + std::optional _color; }; diff --git a/src/style.cpp b/src/style.cpp index bc37f4d73c97d9ff6d9ccc285df38d578ff8e820..99f36979437934fda575581d5e5661de23000f1e 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -31,11 +31,11 @@ #include "attributes.h" #include "bad-uri-exception.h" +#include "colors/manager.h" #include "document.h" #include "preferences.h" #include "3rdparty/libcroco/src/cr-sel-eng.h" - #include "object/sp-paint-server.h" #include "object/uri.h" @@ -400,7 +400,7 @@ SPStyle::SPStyle(SPDocument *document_in, SPObject *object_in) : // std::cout << " SPIPaint: " << sizeof(SPIPaint) << std::endl; // std::cout << " SPITextDecorationLine" << sizeof(SPITextDecorationLine) << std::endl; // std::cout << " Glib::ustring:" << sizeof(Glib::ustring) << std::endl; - // std::cout << " SPColor: " << sizeof(SPColor) << std::endl; + // std::cout << " Color: " << sizeof(Color) << std::endl; // first = false; // } @@ -476,11 +476,11 @@ SPStyle::~SPStyle() { filter_changed_connection.disconnect(); // The following should be moved into SPIPaint and SPIFilter - if (fill.value.href) { + if (fill.href) { fill_ps_modified_connection.disconnect(); } - if (stroke.value.href) { + if (stroke.href) { stroke_ps_modified_connection.disconnect(); } @@ -525,11 +525,11 @@ SPStyle::clear() { filter.href = new SPFilterReference(document); filter_changed_connection = filter.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_filter_ref_changed), this)); - fill.value.href = std::make_shared(document); - fill_ps_changed_connection = fill.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), this)); + fill.href = std::make_shared(document); + fill_ps_changed_connection = fill.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), this)); - stroke.value.href = std::make_shared(document); - stroke_ps_changed_connection = stroke.value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), this)); + stroke.href = std::make_shared(document); + stroke_ps_changed_connection = stroke.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), this)); } cloned = false; @@ -555,6 +555,10 @@ SPStyle::read( SPObject *object, Inkscape::XML::Node *repr ) { clear(); // FIXME, If this isn't here, EVERYTHING stops working! Why? + if (!document && object) { + document = object->document; + } + if (object && object->cloned) { cloned = true; } @@ -588,7 +592,7 @@ SPStyle::read( SPObject *object, Inkscape::XML::Node *repr ) { } } else if( repr->parent() ) { // When does this happen? // std::cout << "SPStyle::read(): reading via repr->parent()" << std::endl; - SPStyle *parent = new SPStyle(); + SPStyle *parent = new SPStyle(document); parent->read( nullptr, repr->parent() ); cascade( parent ); delete parent; @@ -1246,36 +1250,36 @@ sp_repr_sel_eng() void sp_style_set_ipaint_to_uri(SPStyle *style, SPIPaint *paint, const Inkscape::URI *uri, SPDocument *document) { - if (!paint->value.href) { + if (!paint->href) { if (style->object) { // Should not happen as href should have been created in SPIPaint. (TODO: Removed code duplication.) - paint->value.href = std::make_shared(style->object); + paint->href = std::make_shared(style->object); } else if (document || style->document) { // Used by desktop style (no object to attach to!). - paint->value.href = std::make_shared(document ? document : style->document); + paint->href = std::make_shared(document ? document : style->document); } else { std::cerr << "sp_style_set_ipaint_to_uri: No valid object or document!" << std::endl; return; } if (paint == &style->fill) { - style->fill_ps_changed_connection = paint->value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), style)); + style->fill_ps_changed_connection = paint->href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_fill_paint_server_ref_changed), style)); } else { - style->stroke_ps_changed_connection = paint->value.href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), style)); + style->stroke_ps_changed_connection = paint->href->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_style_stroke_paint_server_ref_changed), style)); } } - if (paint->value.href){ - if (paint->value.href->getObject()){ - paint->value.href->detach(); + if (paint->href){ + if (paint->href->getObject()){ + paint->href->detach(); } try { - paint->value.href->attach(*uri); + paint->href->attach(*uri); } catch (Inkscape::BadURIException &e) { g_warning("%s", e.what()); - paint->value.href->detach(); + paint->href->detach(); } } } diff --git a/src/style.h b/src/style.h index d94d8c9e707dc94097c648b842d712fc4ab52aca..0746b748b1f0b1006e4549ee788b13f03d8e0802 100644 --- a/src/style.h +++ b/src/style.h @@ -336,13 +336,13 @@ public: SPFilter const *getFilter() const { return (filter.href) ? filter.href->getObject() : nullptr; } Inkscape::URI const *getFilterURI() const { return (filter.href) ? filter.href->getURI() : nullptr; } - SPPaintServer *getFillPaintServer() { return (fill.value.href) ? fill.value.href->getObject() : nullptr; } - SPPaintServer const *getFillPaintServer() const { return (fill.value.href) ? fill.value.href->getObject() : nullptr; } - Inkscape::URI const *getFillURI() const { return (fill.value.href) ? fill.value.href->getURI() : nullptr; } + SPPaintServer *getFillPaintServer() { return (fill.href) ? fill.href->getObject() : nullptr; } + SPPaintServer const *getFillPaintServer() const { return (fill.href) ? fill.href->getObject() : nullptr; } + Inkscape::URI const *getFillURI() const { return (fill.href) ? fill.href->getURI() : nullptr; } - SPPaintServer *getStrokePaintServer() { return (stroke.value.href) ? stroke.value.href->getObject() : nullptr; } - SPPaintServer const *getStrokePaintServer() const { return (stroke.value.href) ? stroke.value.href->getObject() : nullptr; } - Inkscape::URI const *getStrokeURI() const { return (stroke.value.href) ? stroke.value.href->getURI() : nullptr; } + SPPaintServer *getStrokePaintServer() { return (stroke.href) ? stroke.href->getObject() : nullptr; } + SPPaintServer const *getStrokePaintServer() const { return (stroke.href) ? stroke.href->getObject() : nullptr; } + Inkscape::URI const *getStrokeURI() const { return (stroke.href) ? stroke.href->getURI() : nullptr; } /** * Return a font feature string useful for Pango. diff --git a/src/svg/CMakeLists.txt b/src/svg/CMakeLists.txt index 6a66a6291a7532ac7149081c50a12f6b876301b8..335eeffcab426cda78d35229b97dbd2873914796 100644 --- a/src/svg/CMakeLists.txt +++ b/src/svg/CMakeLists.txt @@ -9,7 +9,6 @@ set(svg_SRC svg-affine.cpp svg-affine-parser.cpp svg-box.cpp - svg-color.cpp svg-angle.cpp svg-length.cpp svg-bool.cpp @@ -23,8 +22,6 @@ set(svg_SRC stringstream.h strip-trailing-zeros.h svg-box.h - svg-color.h - svg-icc-color.h svg-angle.h svg-length.h svg-bool.h diff --git a/src/svg/svg-color.cpp b/src/svg/svg-color.cpp deleted file mode 100644 index 66ae587c12d0000e2c4a45cf0bf983dbfb7eb4ca..0000000000000000000000000000000000000000 --- a/src/svg/svg-color.cpp +++ /dev/null @@ -1,669 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** - * \file - * Reading \& writing of SVG/CSS colors. - */ -/* - * Authors: - * Lauris Kaplinski - * - * Copyright (C) 1999-2002 Lauris Kaplinski - * - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" // only include where actually required! -#endif - -#include "svg-color.h" - -#include -#include // sprintf -#include -#include -#include -#include -#include -#include - -#include // g_assert - -#include "color.h" -#include "colorspace.h" -#include "document.h" -#include "inkscape.h" -#include "preferences.h" -#include "profile-manager.h" -#include "strneq.h" -#include "svg-icc-color.h" - -#include "color/cms-system.h" -#include "object/color-profile.h" - -struct SPSVGColor { - unsigned long rgb; - const std::string name; -}; - -/* - * These are the colors defined in the SVG standard - */ -static SPSVGColor const sp_svg_color_named[] = { - { 0xF0F8FF, "aliceblue" }, - { 0xFAEBD7, "antiquewhite" }, - { 0x00FFFF, "aqua" }, - { 0x7FFFD4, "aquamarine" }, - { 0xF0FFFF, "azure" }, - { 0xF5F5DC, "beige" }, - { 0xFFE4C4, "bisque" }, - { 0x000000, "black" }, - { 0xFFEBCD, "blanchedalmond" }, - { 0x0000FF, "blue" }, - { 0x8A2BE2, "blueviolet" }, - { 0xA52A2A, "brown" }, - { 0xDEB887, "burlywood" }, - { 0x5F9EA0, "cadetblue" }, - { 0x7FFF00, "chartreuse" }, - { 0xD2691E, "chocolate" }, - { 0xFF7F50, "coral" }, - { 0x6495ED, "cornflowerblue" }, - { 0xFFF8DC, "cornsilk" }, - { 0xDC143C, "crimson" }, - { 0x00FFFF, "cyan" }, - { 0x00008B, "darkblue" }, - { 0x008B8B, "darkcyan" }, - { 0xB8860B, "darkgoldenrod" }, - { 0xA9A9A9, "darkgray" }, - { 0x006400, "darkgreen" }, - { 0xA9A9A9, "darkgrey" }, - { 0xBDB76B, "darkkhaki" }, - { 0x8B008B, "darkmagenta" }, - { 0x556B2F, "darkolivegreen" }, - { 0xFF8C00, "darkorange" }, - { 0x9932CC, "darkorchid" }, - { 0x8B0000, "darkred" }, - { 0xE9967A, "darksalmon" }, - { 0x8FBC8F, "darkseagreen" }, - { 0x483D8B, "darkslateblue" }, - { 0x2F4F4F, "darkslategray" }, - { 0x2F4F4F, "darkslategrey" }, - { 0x00CED1, "darkturquoise" }, - { 0x9400D3, "darkviolet" }, - { 0xFF1493, "deeppink" }, - { 0x00BFFF, "deepskyblue" }, - { 0x696969, "dimgray" }, - { 0x696969, "dimgrey" }, - { 0x1E90FF, "dodgerblue" }, - { 0xB22222, "firebrick" }, - { 0xFFFAF0, "floralwhite" }, - { 0x228B22, "forestgreen" }, - { 0xFF00FF, "fuchsia" }, - { 0xDCDCDC, "gainsboro" }, - { 0xF8F8FF, "ghostwhite" }, - { 0xFFD700, "gold" }, - { 0xDAA520, "goldenrod" }, - { 0x808080, "gray" }, - { 0x808080, "grey" }, - { 0x008000, "green" }, - { 0xADFF2F, "greenyellow" }, - { 0xF0FFF0, "honeydew" }, - { 0xFF69B4, "hotpink" }, - { 0xCD5C5C, "indianred" }, - { 0x4B0082, "indigo" }, - { 0xFFFFF0, "ivory" }, - { 0xF0E68C, "khaki" }, - { 0xE6E6FA, "lavender" }, - { 0xFFF0F5, "lavenderblush" }, - { 0x7CFC00, "lawngreen" }, - { 0xFFFACD, "lemonchiffon" }, - { 0xADD8E6, "lightblue" }, - { 0xF08080, "lightcoral" }, - { 0xE0FFFF, "lightcyan" }, - { 0xFAFAD2, "lightgoldenrodyellow" }, - { 0xD3D3D3, "lightgray" }, - { 0x90EE90, "lightgreen" }, - { 0xD3D3D3, "lightgrey" }, - { 0xFFB6C1, "lightpink" }, - { 0xFFA07A, "lightsalmon" }, - { 0x20B2AA, "lightseagreen" }, - { 0x87CEFA, "lightskyblue" }, - { 0x778899, "lightslategray" }, - { 0x778899, "lightslategrey" }, - { 0xB0C4DE, "lightsteelblue" }, - { 0xFFFFE0, "lightyellow" }, - { 0x00FF00, "lime" }, - { 0x32CD32, "limegreen" }, - { 0xFAF0E6, "linen" }, - { 0xFF00FF, "magenta" }, - { 0x800000, "maroon" }, - { 0x66CDAA, "mediumaquamarine" }, - { 0x0000CD, "mediumblue" }, - { 0xBA55D3, "mediumorchid" }, - { 0x9370DB, "mediumpurple" }, - { 0x3CB371, "mediumseagreen" }, - { 0x7B68EE, "mediumslateblue" }, - { 0x00FA9A, "mediumspringgreen" }, - { 0x48D1CC, "mediumturquoise" }, - { 0xC71585, "mediumvioletred" }, - { 0x191970, "midnightblue" }, - { 0xF5FFFA, "mintcream" }, - { 0xFFE4E1, "mistyrose" }, - { 0xFFE4B5, "moccasin" }, - { 0xFFDEAD, "navajowhite" }, - { 0x000080, "navy" }, - { 0xFDF5E6, "oldlace" }, - { 0x808000, "olive" }, - { 0x6B8E23, "olivedrab" }, - { 0xFFA500, "orange" }, - { 0xFF4500, "orangered" }, - { 0xDA70D6, "orchid" }, - { 0xEEE8AA, "palegoldenrod" }, - { 0x98FB98, "palegreen" }, - { 0xAFEEEE, "paleturquoise" }, - { 0xDB7093, "palevioletred" }, - { 0xFFEFD5, "papayawhip" }, - { 0xFFDAB9, "peachpuff" }, - { 0xCD853F, "peru" }, - { 0xFFC0CB, "pink" }, - { 0xDDA0DD, "plum" }, - { 0xB0E0E6, "powderblue" }, - { 0x800080, "purple" }, - { 0x663399, "rebeccapurple" }, - { 0xFF0000, "red" }, - { 0xBC8F8F, "rosybrown" }, - { 0x4169E1, "royalblue" }, - { 0x8B4513, "saddlebrown" }, - { 0xFA8072, "salmon" }, - { 0xF4A460, "sandybrown" }, - { 0x2E8B57, "seagreen" }, - { 0xFFF5EE, "seashell" }, - { 0xA0522D, "sienna" }, - { 0xC0C0C0, "silver" }, - { 0x87CEEB, "skyblue" }, - { 0x6A5ACD, "slateblue" }, - { 0x708090, "slategray" }, - { 0x708090, "slategrey" }, - { 0xFFFAFA, "snow" }, - { 0x00FF7F, "springgreen" }, - { 0x4682B4, "steelblue" }, - { 0xD2B48C, "tan" }, - { 0x008080, "teal" }, - { 0xD8BFD8, "thistle" }, - { 0xFF6347, "tomato" }, - { 0x40E0D0, "turquoise" }, - { 0xEE82EE, "violet" }, - { 0xF5DEB3, "wheat" }, - { 0xFFFFFF, "white" }, - { 0xF5F5F5, "whitesmoke" }, - { 0xFFFF00, "yellow" }, - { 0x9ACD32, "yellowgreen" } -}; - -static std::map sp_svg_create_color_hash(); - -guint32 sp_svg_read_color(gchar const *str, guint32 const dfl) -{ - return sp_svg_read_color(str, nullptr, dfl); -} - -static guint32 internal_sp_svg_read_color(gchar const *str, gchar const **end_ptr, guint32 def) -{ - static std::map colors; - guint32 val = 0; - - if (str == nullptr) return def; - while ((*str <= ' ') && *str) str++; - if (!*str) return def; - - if (str[0] == '#') { - gint i; - for (i = 1; str[i]; i++) { - int hexval; - if (str[i] >= '0' && str[i] <= '9') - hexval = str[i] - '0'; - else if (str[i] >= 'A' && str[i] <= 'F') - hexval = str[i] - 'A' + 10; - else if (str[i] >= 'a' && str[i] <= 'f') - hexval = str[i] - 'a' + 10; - else - break; - val = (val << 4) + hexval; - } - /* handle #rgb case */ - if (i == 1 + 3) { - val = ((val & 0xf00) << 8) | - ((val & 0x0f0) << 4) | - (val & 0x00f); - val |= val << 4; - } else if (i != 1 + 6) { - /* must be either 3 or 6 digits. */ - return def; - } - if (end_ptr) { - *end_ptr = str + i; - } - } else if (strneq(str, "rgb(", 4)) { - bool hasp, hasd; - gchar *s, *e; - gdouble r, g, b; - - s = (gchar *) str + 4; - hasp = false; - hasd = false; - - r = g_ascii_strtod(s, &e); - if (s == e) return def; - s = e; - if (*s == '%') { - hasp = true; - s += 1; - } else { - hasd = true; - } - while (*s && g_ascii_isspace(*s)) s += 1; - if (*s != ',') return def; - s += 1; - while (*s && g_ascii_isspace(*s)) s += 1; - g = g_ascii_strtod(s, &e); - if (s == e) return def; - s = e; - if (*s == '%') { - hasp = true; - s += 1; - } else { - hasd = true; - } - while (*s && g_ascii_isspace(*s)) s += 1; - if (*s != ',') return def; - s += 1; - while (*s && g_ascii_isspace(*s)) s += 1; - b = g_ascii_strtod(s, &e); - if (s == e) return def; - s = e; - if (*s == '%') { - hasp = true; - s += 1; - } else { - hasd = true; - } - while(*s && g_ascii_isspace(*s)) s += 1; - if (*s != ')') { - return def; - } - ++s; - if (hasp && hasd) return def; - if (hasp) { - val = static_cast(floor(CLAMP(r, 0.0, 100.0) * 2.559999)) << 24; - val |= (static_cast(floor(CLAMP(g, 0.0, 100.0) * 2.559999)) << 16); - val |= (static_cast(floor(CLAMP(b, 0.0, 100.0) * 2.559999)) << 8); - } else { - val = static_cast(CLAMP(r, 0, 255)) << 24; - val |= (static_cast(CLAMP(g, 0, 255)) << 16); - val |= (static_cast(CLAMP(b, 0, 255)) << 8); - } - if (end_ptr) { - *end_ptr = s; - } - return val; - } else if (strneq(str, "hsl(", 4)) { - - gchar *ptr = (gchar *) str + 4; - - gchar *e; // ptr after read - - double h = g_ascii_strtod(ptr, &e); // Read h (0-360) - if (ptr == e) return def; // Read failed - ptr = e; - - while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space - if (*ptr != ',') return def; // Need comma - ptr += 1; - while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space - - double s = g_ascii_strtod(ptr, &e); // Read s (percent) - if (ptr == e) return def; // Read failed - ptr = e; - while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space - if (*ptr != '%') return def; // Need % - ptr += 1; - - while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space - if (*ptr != ',') return def; // Need comma - ptr += 1; - while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space - - double l = g_ascii_strtod(ptr, &e); // Read l (percent) - if (ptr == e) return def; // Read failed - ptr = e; - while (*ptr && g_ascii_isspace(*ptr)) ptr += 1; // Remove any white space - if (*ptr != '%') return def; // Need % - ptr += 1; - - if (end_ptr) { - *end_ptr = ptr; - } - - - // Normalize to 0..1 - h /= 360.0; - s /= 100.0; - l /= 100.0; - - gfloat rgb[3]; - - SPColor::hsl_to_rgb_floatv( rgb, h, s, l ); - - val = static_cast(floor(CLAMP(rgb[0], 0.0, 1.0) * 255.9999)) << 24; - val |= (static_cast(floor(CLAMP(rgb[1], 0.0, 1.0) * 255.9999)) << 16); - val |= (static_cast(floor(CLAMP(rgb[2], 0.0, 1.0) * 255.9999)) << 8); - return val; - - } else { - gint i; - if (colors.empty()) { - colors = sp_svg_create_color_hash(); - } - gchar c[32]; - for (i = 0; i < 31; i++) { - if (str[i] == ';' || g_ascii_isspace(str[i])) { - c[i] = '\0'; - break; - } - c[i] = g_ascii_tolower(str[i]); - if (!str[i]) break; - } - c[31] = '\0'; - - if (colors.count(std::string(c))) { - val = colors[std::string(c)]; - } - else { - return def; - } - if (end_ptr) { - *end_ptr = str + i; - } - } - - return (val << 8); -} - -guint32 sp_svg_read_color(gchar const *str, gchar const **end_ptr, guint32 dfl) -{ - /* I've been rather hurried in editing the above to add support for end_ptr, so I'm adding - * this check wrapper. */ - gchar const *end = str; - guint32 const ret = internal_sp_svg_read_color(str, &end, dfl); - g_assert(((ret == dfl) && (end == str)) - || (((ret & 0xff) == 0) - && (str < end))); - if (str < end) { - gchar *buf = (gchar *) g_malloc(end + 1 - str); - memcpy(buf, str, end - str); - buf[end - str] = '\0'; - gchar const *buf_end = buf; - guint32 const check = internal_sp_svg_read_color(buf, &buf_end, 1); - g_assert(check == ret - && buf_end - buf == end - str); - g_free(buf); - - if ( end_ptr ) { - *end_ptr = end; - } - } - return ret; -} - - -/** - * Converts an RGB colour expressed in form 0x00rrggbb to a CSS/SVG representation of that colour. - * The result is valid even in SVG Tiny or non-SVG CSS. - */ -static void rgb24_to_css(char *const buf, size_t buflen, unsigned const rgb24) -{ - g_assert(rgb24 < (1u << 24)); - - /* SVG 1.1 Full allows additional colour names not supported by SVG Tiny, but we don't bother - * with them: it's good for these colours to be copyable to non-SVG CSS stylesheets and for - * documents to be more viewable in SVG Tiny/Basic viewers; and some of the SVG Full names are - * less meaningful than hex equivalents anyway. And it's easier for a person to map from the - * restricted set because the only component values are {00,80,ff}, other than silver 0xc0c0c0. - */ - - char const *src = nullptr; - switch (rgb24) { - /* Extracted mechanically from the table at - * http://www.w3.org/TR/REC-html40/types.html#h-6.5 .*/ - case 0x000000: src = "black"; break; - case 0xc0c0c0: src = "silver"; break; - case 0x808080: src = "gray"; break; - case 0xffffff: src = "white"; break; - case 0x800000: src = "maroon"; break; - case 0xff0000: src = "red"; break; - case 0x800080: src = "purple"; break; - case 0xff00ff: src = "fuchsia"; break; - case 0x008000: src = "green"; break; - case 0x00ff00: src = "lime"; break; - case 0x808000: src = "olive"; break; - case 0xffff00: src = "yellow"; break; - case 0x000080: src = "navy"; break; - case 0x0000ff: src = "blue"; break; - case 0x008080: src = "teal"; break; - case 0x00ffff: src = "aqua"; break; - - default: { - if ((rgb24 & 0xf0f0f) * 0x11 == rgb24) { - /* Can use the shorter three-digit form #rgb instead of #rrggbb. */ - std::snprintf(buf, buflen, "#%x%x%x", - (rgb24 >> 16) & 0xf, - (rgb24 >> 8) & 0xf, - rgb24 & 0xf); - } else { - std::snprintf(buf, buflen, "#%06x", rgb24); - } - break; - } - } - if (src) { - strcpy(buf, src); - } - - // assert(sp_svg_read_color(buf, 0xff) == (rgb24 << 8)); -} - -/** - * Converts an RGBA32 colour to a CSS/SVG representation of the RGB portion of that colour. The - * result is valid even in SVG Tiny or non-SVG CSS. - * - * \param rgba32 Colour expressed in form 0xrrggbbaa. - * \pre buflen \>= 8. - */ -void sp_svg_write_color(gchar *buf, unsigned const buflen, guint32 const rgba32) -{ - g_assert(8 <= buflen); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - unsigned const rgb24 = rgba32 >> 8; - if ( prefs->getBool("/options/svgoutput/usenamedcolors") && - !prefs->getBool("/options/svgoutput/disable_optimizations" )) { - rgb24_to_css(buf, buflen, rgb24); - } else { - g_snprintf(buf, buflen, "#%06x", rgb24); - } -} - -static std::map -sp_svg_create_color_hash() -{ - std::map colors; - - for (const auto & i : sp_svg_color_named) { - colors[i.name] = i.rgb; - } - return colors; -} - - -void icc_color_to_sRGB(SVGICCColor const* icc, guchar* r, guchar* g, guchar* b) -{ - if (icc) { - g_message("profile name: %s", icc->colorProfile.c_str()); - if (auto prof = SP_ACTIVE_DOCUMENT->getProfileManager().find(icc->colorProfile.c_str())) { - guchar color_out[4] = {0,0,0,0}; - cmsHTRANSFORM trans = prof->getTransfToSRGB8(); - if ( trans ) { - std::vector comps = colorspace::getColorSpaceInfo( prof ); - - size_t count = prof->getChannelCount(); - size_t cap = std::min(count, comps.size()); - guchar color_in[4]; - for (size_t i = 0; i < cap; i++) { - color_in[i] = static_cast((((gdouble)icc->colors[i]) * 256.0) * (gdouble)comps[i].scale); - g_message("input[%d]: %d", (int)i, (int)color_in[i]); - } - - Inkscape::CMSSystem::do_transform( trans, color_in, color_out, 1 ); - g_message("transform to sRGB done"); - } - *r = color_out[0]; - *g = color_out[1]; - *b = color_out[2]; - } - } -} - -/* - * Some discussion at http://markmail.org/message/bhfvdfptt25kgtmj - * Allowed ASCII first characters: ':', 'A'-'Z', '_', 'a'-'z' - * Allowed ASCII remaining chars add: '-', '.', '0'-'9', - */ -bool sp_svg_read_icc_color( gchar const *str, gchar const **end_ptr, SVGICCColor* dest ) -{ - bool good = true; - - if ( end_ptr ) { - *end_ptr = str; - } - if ( dest ) { - dest->colorProfile.clear(); - dest->colors.clear(); - } - - if ( !str ) { - // invalid input - good = false; - } else { - while ( g_ascii_isspace(*str) ) { - str++; - } - - good = strneq( str, "icc-color(", 10 ); - - if ( good ) { - str += 10; - while ( g_ascii_isspace(*str) ) { - str++; - } - - if ( !g_ascii_isalpha(*str) - && ( !(0x080 & *str) ) - && (*str != '_') - && (*str != ':') ) { - // Name must start with a certain type of character - good = false; - } else { - while ( g_ascii_isdigit(*str) || g_ascii_isalpha(*str) - || (*str == '-') || (*str == ':') || (*str == '_') || (*str == '.') ) { - if ( dest ) { - dest->colorProfile += *str; - } - str++; - } - while ( g_ascii_isspace(*str) || *str == ',' ) { - str++; - } - } - } - - if ( good ) { - while ( *str && *str != ')' ) { - if ( g_ascii_isdigit(*str) || *str == '.' || *str == '-' || *str == '+') { - gchar* endPtr = nullptr; - gdouble dbl = g_ascii_strtod( str, &endPtr ); - if ( !errno ) { - if ( dest ) { - dest->colors.push_back( dbl ); - } - str = endPtr; - } else { - good = false; - break; - } - - while ( g_ascii_isspace(*str) || *str == ',' ) { - str++; - } - } else { - break; - } - } - } - - // We need to have ended on a closing parenthesis - if ( good ) { - while ( g_ascii_isspace(*str) ) { - str++; - } - good &= (*str == ')'); - } - } - - if ( good ) { - if ( end_ptr ) { - *end_ptr = str; - } - } else { - if ( dest ) { - dest->colorProfile.clear(); - dest->colors.clear(); - } - } - - return good; -} - - -bool sp_svg_read_icc_color( gchar const *str, SVGICCColor* dest ) -{ - return sp_svg_read_icc_color(str, nullptr, dest); -} - -/** - * Reading inkscape colors, for things like namedviews, guides, etc. - * Non-CSS / SVG specification formatted. Usually just a number. - */ -bool sp_ink_read_opacity(char const *str, guint32 *color, guint32 default_color) -{ - *color = (*color & 0xffffff00) | (default_color & 0xff); - if (!str) return false; - - gchar *check; - gdouble value = g_ascii_strtod(str, &check); - if (!check) return false; - - value = CLAMP(value, 0.0, 1.0); - *color = (*color & 0xffffff00) | (guint32) floor(value * 255.9999); - return true; -} - -/* - 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 : diff --git a/src/svg/svg-color.h b/src/svg/svg-color.h deleted file mode 100644 index b2c2f797375d59dd77f42968cbcb2287ab517b5e..0000000000000000000000000000000000000000 --- a/src/svg/svg-color.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file - * TODO: insert short description here - *//* - * Authors: see git history - * - * Copyright (C) 2014 Authors - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ -#ifndef SVG_SVG_COLOR_H_SEEN -#define SVG_SVG_COLOR_H_SEEN - -typedef unsigned int guint32; -struct SVGICCColor; - -guint32 sp_svg_read_color(char const *str, unsigned int dfl); -guint32 sp_svg_read_color(char const *str, char const **end_ptr, guint32 def); -void sp_svg_write_color(char *buf, unsigned int buflen, unsigned int rgba32); - -bool sp_svg_read_icc_color( char const *str, char const **end_ptr, SVGICCColor* dest ); -bool sp_svg_read_icc_color( char const *str, SVGICCColor* dest ); -void icc_color_to_sRGB(SVGICCColor const *dest, unsigned char *r, unsigned char *g, unsigned char *b); - -bool sp_ink_read_opacity(char const *str, guint32 *color, guint32 default_color); - -#endif /* !SVG_SVG_COLOR_H_SEEN */ diff --git a/src/trace/depixelize/inkscape-depixelize.cpp b/src/trace/depixelize/inkscape-depixelize.cpp index ae3c783a883963d3cd2902af944aef922d082393..8b2daf9cb882364ce8e558123d9b8ec462bce185 100644 --- a/src/trace/depixelize/inkscape-depixelize.cpp +++ b/src/trace/depixelize/inkscape-depixelize.cpp @@ -20,10 +20,9 @@ #include "inkscape-depixelize.h" -#include "color.h" +#include "colors/utils.h" #include "preferences.h" #include "async/progress.h" -#include "svg/svg-color.h" #include "svg/css-ostringstream.h" namespace Inkscape { @@ -65,17 +64,14 @@ TraceResult DepixelizeTracingEngine::trace(Glib::RefPtr const &pixb throttled.report_or_throw((double)i / num_splines); i++; - char b[64]; - sp_svg_write_color(b, sizeof(b), + auto hex = Inkscape::Colors::rgba_to_hex( SP_RGBA32_U_COMPOSE(unsigned(it.rgba[0]), unsigned(it.rgba[1]), unsigned(it.rgba[2]), unsigned(it.rgba[3]))); - Inkscape::CSSOStringStream osalpha; - osalpha << it.rgba[3] / 255.0f; - char *style = g_strdup_printf("fill:%s;fill-opacity:%s;", b, osalpha.str().c_str()); - res.emplace_back(style, std::move(it.pathVector)); - g_free(style); + Inkscape::CSSOStringStream ss; + ss << "fill:" << hex.c_str() << ";fill-opacity:" << (it.rgba[3] / 255.0f) << ";"; + res.emplace_back(ss.str(), std::move(it.pathVector)); } return res; diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index a05fe57eb2849e68f3ad3b1ea82010e495a797bf..6210690b509dfc1ff75cd4c63c6177e5ccac890e 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -14,7 +14,6 @@ set(ui_SRC monitor.cpp pack.cpp popup-menu.cpp - selected-color.cpp shape-editor.cpp shape-editor-knotholders.cpp simple-pref-pusher.cpp @@ -192,7 +191,6 @@ set(ui_SRC widget/canvas-grid.cpp widget/canvas-notice.cpp widget/color-entry.cpp - widget/color-icc-selector.cpp widget/color-notebook.cpp widget/color-palette.cpp widget/color-palette-preview.cpp @@ -296,7 +294,6 @@ set(ui_SRC monitor.h pack.h popup-menu.h - selected-color.h shape-editor.h simple-pref-pusher.h shortcuts.h @@ -482,7 +479,6 @@ set(ui_SRC widget/canvas-notice.h widget/completion-popup.h widget/color-entry.h - widget/color-icc-selector.h widget/color-notebook.h widget/color-palette.h widget/color-palette-preview.h diff --git a/src/ui/builder-utils.cpp b/src/ui/builder-utils.cpp index a0620a9dde2c7f12356f5e784d893a65ae7e907c..21c24a19bdbee347249496372e437d97a8ad5ee8 100644 --- a/src/ui/builder-utils.cpp +++ b/src/ui/builder-utils.cpp @@ -45,6 +45,16 @@ Glib::RefPtr create_builder(const char* filename) { } } +bool hide_widget(const Glib::RefPtr &builder, std::string const &id) +{ + auto widget = builder->get_widget(id); + if (widget) { + widget->set_visible(false); + return true; + } + return false; +} + } } // namespace Inkscape::UI /* diff --git a/src/ui/builder-utils.h b/src/ui/builder-utils.h index 31bcbea411558582ac4942ec1bc92243fd204f06..dfe787543443a4d5c97d5deec5c8c7d7ad23985b 100644 --- a/src/ui/builder-utils.h +++ b/src/ui/builder-utils.h @@ -55,6 +55,8 @@ template Glib::RefPtr get_object(Glib::RefPtr& build return object; } +bool hide_widget(const Glib::RefPtr& builder, std::string const &id); + /** * This version of get_object is needed for Gtk::CellRenderer objects which can not be * put into Glib::RefPtr by the compiler, but are somehow passed to us as RefPtrs anyway. diff --git a/src/ui/clipboard.cpp b/src/ui/clipboard.cpp index 855ff4353e45664606715fac58ffe388c2be7c2c..87a801a3d55187eecaec743b6b5c0301773e0ae6 100644 --- a/src/ui/clipboard.cpp +++ b/src/ui/clipboard.cpp @@ -34,6 +34,7 @@ // TODO: reduce header bloat if possible +#include "colors/manager.h" #include "context-fns.h" #include "desktop-style.h" // for sp_desktop_set_style, used in _pasteStyle #include "desktop.h" @@ -82,7 +83,6 @@ #include "object/sp-textpath.h" #include "object/sp-use.h" #include "svg/css-ostringstream.h" // used in copy -#include "svg/svg-color.h" #include "svg/svg.h" // for sp_svg_transform_write, used in _copySelection #include "text-chemistry.h" #include "ui/tool/control-point-selection.h" @@ -234,7 +234,7 @@ private: Glib::ustring _getBestTarget(SPDesktop *desktop = nullptr); void _registerSerializers(); void _setClipboardTargets(); - void _setClipboardColor(uint32_t); + void _setClipboardColor(Colors::Color const &color); void _userWarn(SPDesktop *, char const *); // private properties @@ -281,7 +281,7 @@ void ClipboardManagerImpl::copy(ObjectSet *set) if (auto const drag = desktop->getTool()->get_drag(); drag && drag->hasSelection()) { - guint32 col = drag->getColor(); + Color col = drag->getColor(); // set the color as clipboard content (text in RRGGBBAA format) _setClipboardColor(col); @@ -294,16 +294,8 @@ void ClipboardManagerImpl::copy(ObjectSet *set) } _text_style = sp_repr_css_attr_new(); // print and set properties - gchar color_str[16]; - g_snprintf(color_str, 16, "#%06x", col >> 8); - sp_repr_css_set_property(_text_style, "fill", color_str); - float opacity = SP_RGBA32_A_F(col); - if (opacity > 1.0) { - opacity = 1.0; // safeguard - } - Inkscape::CSSOStringStream opcss; - opcss << opacity; - sp_repr_css_set_property(_text_style, "opacity", opcss.str().data()); + sp_repr_css_set_property_string(_text_style, "fill", col.toString(false)); + sp_repr_css_set_property_double(_text_style, "opacity", col.getOpacity()); _discardInternalClipboard(); return; @@ -311,7 +303,7 @@ void ClipboardManagerImpl::copy(ObjectSet *set) // Special case for when the color picker ("dropper") is active - copies color under cursor if (auto const dt = dynamic_cast(desktop->getTool())) { - _setClipboardColor(dt->get_color(false, true)); + _setClipboardColor(*dt->get_color(false, true)); _discardInternalClipboard(); return; } @@ -1547,12 +1539,10 @@ bool ClipboardManagerImpl::_pasteText(SPDesktop *desktop) if (clip_text.length() < 30) { // Zero makes it impossible to paste a 100% transparent black, but it's useful. - auto const rgb0 = sp_svg_read_color(clip_text.c_str(), 0x0); - if (rgb0) { + if (auto color = Colors::Color::parse(clip_text)) { auto color_css = sp_repr_css_attr_new(); - sp_repr_css_set_property(color_css, "fill", SPColor(rgb0).toString().c_str()); - // In the future this could parse opacity, but sp_svg_read_color lacks this. - sp_repr_css_set_property(color_css, "fill-opacity", "1.0"); + sp_repr_css_set_property_string(color_css, "fill", color->toString(false)); + sp_repr_css_set_property_double(color_css, "fill-opacity", color->getOpacity()); sp_desktop_set_style(desktop, color_css); sp_repr_css_attr_unref(color_css); return true; @@ -1769,14 +1759,6 @@ void ClipboardManagerImpl::_onGet(char const *mime_type, Glib::RefPtr(area.height() + 0.5); // read from namedview - Inkscape::XML::Node *nv = _clipboardSPDoc->getReprNamedView(); - if (nv && nv->attribute("pagecolor")) { - bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00); - } - if (nv && nv->attribute("inkscape:pageopacity")) { - double opacity = nv->getAttributeDouble("inkscape:pageopacity", 1.0); - bgcolor |= SP_COLOR_F_TO_U(opacity); - } auto const raster_file = get_tmp_filename("inkscape-clipboard-export-raster"); sp_export_png_file(_clipboardSPDoc.get(), raster_file.c_str(), area, width, height, dpi, dpi, bgcolor, nullptr, nullptr, true, {}); (*out)->export_raster(_clipboardSPDoc.get(), raster_file.c_str(), filename.c_str(), true); @@ -2034,11 +2016,9 @@ void ClipboardManagerImpl::_setClipboardTargets() /** * Set the string representation of a 32-bit RGBA color as the clipboard contents. */ -void ClipboardManagerImpl::_setClipboardColor(uint32_t color) +void ClipboardManagerImpl::_setClipboardColor(Colors::Color const &color) { - char colorstr[16]; - g_snprintf(colorstr, 16, "%08x", color); - _clipboard->set_text(colorstr); + _clipboard->set_text(color.toString()); } /** diff --git a/src/ui/cursor-utils.cpp b/src/ui/cursor-utils.cpp index c2b65d0b567d6cc967415320de4c11363ecd7540..c09d0baad0f164fbee5bcab5fb6d7bbf2bd542af 100644 --- a/src/ui/cursor-utils.cpp +++ b/src/ui/cursor-utils.cpp @@ -44,7 +44,7 @@ using Inkscape::IO::Resource::ICONS; namespace Inkscape { // SVG cursor unique ID/key -typedef std::tuple Key; +typedef std::tuple Key; struct KeyHasher { std::size_t operator () (const Key& k) const { return boost::hash_value(k); } @@ -58,16 +58,16 @@ struct KeyHasher { Glib::RefPtr load_svg_cursor(Gtk::Widget &widget, std::string const &file_name, - std::uint32_t const fill, - std::uint32_t const stroke, - double fill_opacity, - double stroke_opacity) + std::optional maybe_fill, + std::optional maybe_stroke) { // GTK puts cursors in a "cursors" subdirectory of icon themes. We'll do the same... but // note that we cannot use the normal GTK method for loading cursors as GTK knows nothing // about scalable SVG cursors. We must locate and load the files ourselves. (Even if // GTK could handle scalable cursors, we would need to load the files ourselves inorder // to modify CSS 'fill' and 'stroke' properties.) + Colors::Color fill = maybe_fill.value_or(Colors::Color(0xffffffff)); + Colors::Color stroke = maybe_stroke.value_or(Colors::Color(0x000000ff)); Glib::RefPtr cursor; @@ -89,8 +89,8 @@ load_svg_cursor(Gtk::Widget &widget, theme_names.emplace_back("hicolor"); // quantize opacity to limit number of cursor variations we generate - fill_opacity = std::floor(std::clamp(fill_opacity, 0.0, 1.0) * 100) / 100; - stroke_opacity = std::floor(std::clamp(stroke_opacity, 0.0, 1.0) * 100) / 100; + fill.setOpacity(std::floor(std::clamp(fill.getOpacity(), 0.0, 1.0) * 100) / 100); + stroke.setOpacity(std::floor(std::clamp(stroke.getOpacity(), 0.0, 1.0) * 100) / 100); const auto enable_drop_shadow = prefs->getBool("/options/cursor-drop-shadow", true); @@ -109,7 +109,7 @@ load_svg_cursor(Gtk::Widget &widget, if (cache_enabled) { // construct a key cursor_key = std::tuple{theme_names[0], theme_names[1], file_name, - fill, stroke, fill_opacity, stroke_opacity, + fill.toRGBA(), stroke.toRGBA(), enable_drop_shadow, scale}; if (auto const it = cursor_cache.find(cursor_key); it != cursor_cache.end()) { return it->second; @@ -155,20 +155,10 @@ load_svg_cursor(Gtk::Widget &widget, // Set the CSS 'fill' and 'stroke' properties on the SVG element (for cascading). SPCSSAttr *css = sp_repr_css_attr(root->getRepr(), "style"); - - std::stringstream fill_stream; - fill_stream << "#" - << std::setfill ('0') << std::setw(6) - << std::hex << (fill >> 8); - std::stringstream stroke_stream; - stroke_stream << "#" - << std::setfill ('0') << std::setw(6) - << std::hex << (stroke >> 8); - - sp_repr_css_set_property(css, "fill", fill_stream.str().c_str()); - sp_repr_css_set_property(css, "stroke", stroke_stream.str().c_str()); - sp_repr_css_set_property_double(css, "fill-opacity", fill_opacity); - sp_repr_css_set_property_double(css, "stroke-opacity", stroke_opacity); + sp_repr_css_set_property_string(css, "fill", fill.toString(false)); + sp_repr_css_set_property_string(css, "stroke", stroke.toString(false)); + sp_repr_css_set_property_double(css, "fill-opacity", fill.getOpacity()); + sp_repr_css_set_property_double(css, "stroke-opacity", stroke.getOpacity()); root->changeCSS(css, "style"); sp_repr_css_attr_unref(css); @@ -235,12 +225,10 @@ load_svg_cursor(Gtk::Widget &widget, void set_svg_cursor(Gtk::Widget &widget, std::string const &file_name, - std::uint32_t const fill, - std::uint32_t const stroke, - double fill_opacity, - double stroke_opacity) + std::optional fill, + std::optional stroke) { - auto cursor = load_svg_cursor(widget, file_name, fill, stroke, fill_opacity, stroke_opacity); + auto cursor = load_svg_cursor(widget, file_name, fill, stroke); widget.set_cursor(std::move(cursor)); } diff --git a/src/ui/cursor-utils.h b/src/ui/cursor-utils.h index 98a535c74f0940c01a49643aad8a05047a362232..603cb0db1655f097b0f9e79819ce0f64c2051cf1 100644 --- a/src/ui/cursor-utils.h +++ b/src/ui/cursor-utils.h @@ -14,6 +14,9 @@ #include #include #include +#include + +#include "colors/color.h" namespace Gdk { class Cursor; @@ -27,17 +30,13 @@ namespace Inkscape { Glib::RefPtr load_svg_cursor(Gtk::Widget &widget, std::string const &file_name, - std::uint32_t fill = 0xffffffff, - std::uint32_t stroke = 0x000000ff, - double fill_opacity = 1.0, - double stroke_opacity = 1.0); + std::optional fill = {}, + std::optional stroke = {}); void set_svg_cursor(Gtk::Widget &widget, std::string const &file_name, - std::uint32_t fill = 0xffffffff, - std::uint32_t stroke = 0x000000ff, - double fill_opacity = 1.0, - double stroke_opacity = 1.0); + std::optional fill = {}, + std::optional stroke = {}); } // namespace Inkscape diff --git a/src/ui/dialog/about.cpp b/src/ui/dialog/about.cpp index 134f7ebce3dd10207f399842460309577bfd9a1a..76ba3927d2270813946b0d65f87302601fd7f595 100644 --- a/src/ui/dialog/about.cpp +++ b/src/ui/dialog/about.cpp @@ -42,7 +42,6 @@ #include "desktop.h" #include "display/cairo-utils.h" #include "helper/auto-connection.h" -#include "hsluv.h" #include "inkscape-version-info.h" #include "inkscape.h" #include "inkscape-window.h" @@ -147,24 +146,14 @@ private: auto ctx = Cairo::Context::create(surface); ctx->set_source(image, 0, -y); ctx->paint(); - double r,g,b,a; - ink_cairo_surface_average_color_premul(surface->cobj(), r, g, b, a); // calculate footer color: light/dark depending on a theme bool dark = INKSCAPE.themecontext->isCurrentThemeDark(this); - // color of the image strip to HSL, so we can manipulate its lightness - auto [h, s, l] = Hsluv::rgb_to_hsluv(r, g, b); - // for a dark theme come up with a darker shade, for a light time - with a lighter one - l = dark ? l * 0.7 : l + (100 - l) * 0.5; - // clip them to remove extremes - l = dark ? std::min(l, 30.0) : std::max(l, 80.0); - // limit saturation to improve contrast with some artwork - s = dark ? std::min(s, 80.0) : s; - auto rgb = Hsluv::hsluv_to_rgb(h, s, l); + auto foot = Colors::make_theme_color(ink_cairo_surface_average_color_premul(surface->cobj()), dark); + auto style_context = _footer->get_style_context(); _footer_style = Gtk::CssProvider::create(); - auto color_str = rgba_to_css_color(rgb[0], rgb[1], rgb[2]); - _footer_style->load_from_data("box {background-color:" + color_str + ";}"); + _footer_style->load_from_data("box {background-color:" + foot.toString() + ";}"); if (_footer_style) style_context->remove_provider(_footer_style); style_context->add_provider(_footer_style, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); } diff --git a/src/ui/dialog/clonetiler.cpp b/src/ui/dialog/clonetiler.cpp index d84cc6dd28ebc1a87f1621ec1af79f6fd7de757c..2aa1bda374d8f0fc42349612b921f678c8e2fc7b 100644 --- a/src/ui/dialog/clonetiler.cpp +++ b/src/ui/dialog/clonetiler.cpp @@ -44,7 +44,6 @@ #include "object/sp-namedview.h" #include "object/sp-root.h" #include "object/sp-use.h" -#include "svg/svg-color.h" #include "svg/svg.h" #include "ui/icon-loader.h" #include "ui/icon-names.h" @@ -671,10 +670,10 @@ CloneTiler::CloneTiler() auto const l = Gtk::make_managed(_("Initial color: ")); UI::pack_start(*hb, *l, false, false); - guint32 rgba = 0x000000ff | sp_svg_read_color (prefs->getString(prefs_path + "initial_color").data(), 0x000000ff); + auto color = prefs->getColor(prefs_path + "initial_color", "#000000ff"); color_picker = Gtk::make_managed(_("Initial color of tiled clones"), _("Initial color for clones (works only if the original has unset fill or stroke or on spray tool in copy mode)"), - rgba, false); + color, false); color_changed_connection = color_picker->connectChanged(sigc::mem_fun(*this, &CloneTiler::on_picker_color_changed)); UI::pack_start(*hb, *color_picker, false, false); @@ -1218,19 +1217,15 @@ CloneTiler::~CloneTiler () color_changed_connection.disconnect(); } -void CloneTiler::on_picker_color_changed(guint rgba) +void CloneTiler::on_picker_color_changed(Colors::Color const &color) { static bool is_updating = false; if (is_updating || !SP_ACTIVE_DESKTOP) return; is_updating = true; - - gchar c[32]; - sp_svg_write_color(c, sizeof(c), rgba); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setString(prefs_path + "initial_color", c); - + prefs->setColor(prefs_path + "initial_color", color); is_updating = false; } @@ -1947,11 +1942,9 @@ guint32 CloneTiler::trace_pick(Geom::Rect box) Inkscape::DrawingContext dc(s, ibox.min()); /* Render */ trace_drawing->render(dc, ibox); - double R = 0, G = 0, B = 0, A = 0; - ink_cairo_surface_average_color(s, R, G, B, A); + auto color = ink_cairo_surface_average_color(s); cairo_surface_destroy(s); - - return SP_RGBA32_F_COMPOSE (R, G, B, A); + return color.toRGBA(); } void CloneTiler::trace_finish() @@ -2166,7 +2159,7 @@ void CloneTiler::apply() bool opacity_alternatej = prefs->getBool(prefs_path + "opacity_alternatej"); double opacity_rand = 0.01 * prefs->getDoubleLimited(prefs_path + "opacity_rand", 0, 0, 100); - Glib::ustring initial_color = prefs->getString(prefs_path + "initial_color"); + auto initial_color = prefs->getColor(prefs_path + "initial_color"); double hue_per_j = 0.01 * prefs->getDoubleLimited(prefs_path + "hue_per_j", 0, -100, 100); double hue_per_i = 0.01 * prefs->getDoubleLimited(prefs_path + "hue_per_i", 0, -100, 100); double hue_rand = 0.01 * prefs->getDoubleLimited(prefs_path + "hue_rand", 0, 0, 100); @@ -2298,29 +2291,20 @@ void CloneTiler::apply() } } - gchar color_string[32]; *color_string = 0; + std::string color_string; // Color tab - if (!initial_color.empty()) { - guint32 rgba = sp_svg_read_color (initial_color.data(), 0x000000ff); - float hsl[3]; - SPColor::rgb_to_hsl_floatv (hsl, SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba)); + double eff_i = (color_alternatei? (i%2) : (i)); + double eff_j = (color_alternatej? (j%2) : (j)); - double eff_i = (color_alternatei? (i%2) : (i)); - double eff_j = (color_alternatej? (j%2) : (j)); + auto hsl = *initial_color.converted(Colors::Space::Type::HSL); + hsl.set(0, hsl[0] + hue_per_i * eff_i + hue_per_j * eff_j + hue_rand * g_random_double_range (-1, 1)); + hsl.set(1, hsl[1] + saturation_per_i * eff_i + saturation_per_j * eff_j + saturation_rand * g_random_double_range (-1, 1)); + hsl.set(2, hsl[2] + lightness_per_i * eff_i + lightness_per_j * eff_j + lightness_rand * g_random_double_range (-1, 1)); + hsl.normalize(); - hsl[0] += hue_per_i * eff_i + hue_per_j * eff_j + hue_rand * g_random_double_range (-1, 1); - double notused; - hsl[0] = modf( hsl[0], ¬used ); // Restrict to 0-1 - hsl[1] += saturation_per_i * eff_i + saturation_per_j * eff_j + saturation_rand * g_random_double_range (-1, 1); - hsl[1] = CLAMP (hsl[1], 0, 1); - hsl[2] += lightness_per_i * eff_i + lightness_per_j * eff_j + lightness_rand * g_random_double_range (-1, 1); - hsl[2] = CLAMP (hsl[2], 0, 1); - - float rgb[3]; - SPColor::hsl_to_rgb_floatv (rgb, hsl[0], hsl[1], hsl[2]); - sp_svg_write_color(color_string, sizeof(color_string), SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 1.0)); - } + // We could convert to RGB here, but we shouldn't actually need to. + color_string = hsl.toString(); // Blur double blur = 0.0; @@ -2344,14 +2328,8 @@ void CloneTiler::apply() if (dotrace) { Geom::Rect bbox_t = transform_rect (bbox_original, t*Geom::Scale(1.0/scale_units)); - guint32 rgba = trace_pick (bbox_t); - float r = SP_RGBA32_R_F(rgba); - float g = SP_RGBA32_G_F(rgba); - float b = SP_RGBA32_B_F(rgba); - float a = SP_RGBA32_A_F(rgba); - - float hsl[3]; - SPColor::rgb_to_hsl_floatv (hsl, r, g, b); + auto rgba = Colors::Color(trace_pick(bbox_t)); + auto hsl = *rgba.converted(Colors::Space::Type::HSL); gdouble val = 0; switch (pick) { @@ -2359,16 +2337,16 @@ void CloneTiler::apply() val = 1 - hsl[2]; // inverse lightness; to match other picks where black = max break; case PICK_OPACITY: - val = a; + val = rgba.getOpacity(); break; case PICK_R: - val = r; + val = rgba[0]; break; case PICK_G: - val = g; + val = rgba[1]; break; case PICK_B: - val = b; + val = rgba[2]; break; case PICK_H: val = hsl[0]; @@ -2385,9 +2363,8 @@ void CloneTiler::apply() if (rand_picked > 0) { val = randomize01 (val, rand_picked); - r = randomize01 (r, rand_picked); - g = randomize01 (g, rand_picked); - b = randomize01 (b, rand_picked); + for (auto i = 0; i < 3; i++) + rgba.set(i, randomize01(rgba[i], rand_picked)); } if (gamma_picked != 0) { @@ -2398,25 +2375,17 @@ void CloneTiler::apply() power = 1 + fabs(gamma_picked); val = pow (val, power); - r = pow (r, power); - g = pow (g, power); - b = pow (b, power); + for (auto i = 0; i < 3; i++) + rgba.set(i, pow(rgba[i], power)); } if (invert_picked) { val = 1 - val; - r = 1 - r; - g = 1 - g; - b = 1 - b; + rgba.invert(); } val = CLAMP (val, 0, 1); - r = CLAMP (r, 0, 1); - g = CLAMP (g, 0, 1); - b = CLAMP (b, 0, 1); - - // recompose tweaked color - rgba = SP_RGBA32_F_COMPOSE(r, g, b, a); + rgba.normalize(); if (pick_to_presence) { if (g_random_double_range (0, 1) > val) { @@ -2432,7 +2401,7 @@ void CloneTiler::apply() opacity *= val; } if (pick_to_color) { - sp_svg_write_color(color_string, sizeof(color_string), rgba); + color_string = rgba.toString(); } } @@ -2464,7 +2433,7 @@ void CloneTiler::apply() clone->setAttributeCssDouble("opacity", opacity); } - if (*color_string) { + if (!color_string.empty()) { clone->setAttribute("fill", color_string); clone->setAttribute("stroke", color_string); } diff --git a/src/ui/dialog/clonetiler.h b/src/ui/dialog/clonetiler.h index bfbbfccd47be1ec6836aad28d1b03ef089322a78..4417efa4727232ba6c9915ecda6dec9705103443 100644 --- a/src/ui/dialog/clonetiler.h +++ b/src/ui/dialog/clonetiler.h @@ -90,7 +90,7 @@ protected: void table_attach(Gtk::Grid *table, Gtk::Widget *widget, float align, int row, int col); void symgroup_changed(Gtk::ComboBox *cb); - void on_picker_color_changed(guint rgba); + void on_picker_color_changed(Colors::Color const &rgba); void trace_hide_tiled_clones_recursively(SPObject *from); guint number_of_clones(SPObject *obj); void trace_setup(SPDocument *doc, gdouble zoom, SPItem *original); diff --git a/src/ui/dialog/color-item.cpp b/src/ui/dialog/color-item.cpp index 42de70f29c5e00f04378a736fafece6a5241aed5..834d32c68f6d589b18a5b6d042c5169ccf98be4f 100644 --- a/src/ui/dialog/color-item.cpp +++ b/src/ui/dialog/color-item.cpp @@ -35,10 +35,10 @@ #include #include +#include "colors/dragndrop.h" #include "desktop-style.h" #include "document.h" #include "document-undo.h" -#include "hsluv.h" #include "message-context.h" #include "preferences.h" #include "selection.h" @@ -48,7 +48,6 @@ #include "io/resource.h" #include "object/sp-gradient.h" #include "object/tags.h" -#include "svg/svg-color.h" #include "ui/containerize.h" #include "ui/controller.h" #include "ui/dialog/dialog-base.h" @@ -82,21 +81,23 @@ Glib::RefPtr get_removecolor() } // namespace -ColorItem::ColorItem(PaintDef const &paintdef, DialogBase *dialog) +ColorItem::ColorItem(DialogBase *dialog) : dialog(dialog) { - if (paintdef.get_type() == PaintDef::RGB) { - pinned_default = false; - data = RGBData{paintdef.get_rgb()}; - } else { - pinned_default = true; - data = PaintNone{}; - add_css_class("paint-none"); - } - description = paintdef.get_description(); - color_id = paintdef.get_color_id(); - tooltip = paintdef.get_tooltip(); + data = PaintNone(); + pinned_default = true; + add_css_class("paint-none"); + description = C_("Paint", "None"); + color_id = "none"; + common_setup(); +} +ColorItem::ColorItem(Colors::Color color, DialogBase *dialog) + : dialog(dialog) +{ + description = color.getName(); + color_id = color_to_id(color); + data = std::move(color); common_setup(); } @@ -202,9 +203,8 @@ void ColorItem::draw_color(Cairo::RefPtr const &cr, int w, int h cr->paint(); cr->restore(); } - } else if (auto const rgbdata = std::get_if(&data)) { - auto [r, g, b] = rgbdata->rgb; - cr->set_source_rgb(r / 255.0, g / 255.0, b / 255.0); + } else if (auto const color = std::get_if(&data)) { + ink_cairo_set_source_color(cr, *color); cr->paint(); // there's no way to query background color to check if color item stands out, // so we apply faint outline to let users make out color shapes blending with background @@ -256,8 +256,8 @@ void ColorItem::draw_func(Cairo::RefPtr const &cr, int const w, // Draw fill/stroke indicators. if (is_fill || is_stroke) { - double const lightness = Hsluv::rgb_to_perceptual_lightness(average_color()); - auto [gray, alpha] = Hsluv::get_contrasting_color(lightness); + double const lightness = Colors::get_perceptual_lightness(getColor()); + auto [gray, alpha] = Colors::get_contrasting_color(lightness); cr->set_source_rgba(gray, gray, gray, alpha); // Scale so that the square -1...1 is the biggest possible square centred in the widget. @@ -351,12 +351,8 @@ void ColorItem::on_click(bool stroke) if (is_paint_none()) { sp_repr_css_set_property(css.get(), attr_name, "none"); descr = stroke ? _("Set stroke color to none") : _("Set fill color to none"); - } else if (auto const rgbdata = std::get_if(&data)) { - auto [r, g, b] = rgbdata->rgb; - std::uint32_t rgba = (r << 24) | (g << 16) | (b << 8) | 0xff; - char buf[64]; - sp_svg_write_color(buf, sizeof(buf), rgba); - sp_repr_css_set_property(css.get(), attr_name, buf); + } else if (auto const color = std::get_if(&data)) { + sp_repr_css_set_property_string(css.get(), attr_name, color->toString()); descr = stroke ? _("Set stroke color from swatch") : _("Set fill color from swatch"); } else if (auto const graddata = std::get_if(&data)) { auto grad = graddata->gradient; @@ -511,30 +507,13 @@ void ColorItem::action_convert(Glib::ustring const &name) DocumentUndo::done(doc, _("Add gradient stop"), INKSCAPE_ICON("color-gradient")); } -PaintDef ColorItem::to_paintdef() const -{ - if (is_paint_none()) { - return PaintDef(); - } else if (auto const rgbdata = std::get_if(&data)) { - return PaintDef(rgbdata->rgb, description, ""); - } else if (auto const graddata = std::get_if(&data)) { - auto const grad = graddata->gradient; - assert(grad != nullptr); - return PaintDef({0, 0, 0}, grad->getId(), ""); - } - - // unreachable - assert(false); - return {}; -} - Glib::RefPtr ColorItem::on_drag_prepare(Gtk::DragSource const &, double, double) { if (!dialog) return {}; - Glib::Value value; + Glib::Value> value; value.init(value.value_type()); - value.set(to_paintdef()); + value.set(getColor()); return Gdk::ContentProvider::create(value); } @@ -572,13 +551,16 @@ bool ColorItem::is_pinned() const } } -std::array ColorItem::average_color() const +/** + * Return the average color for this color item. If none, returns whit + * but if a gradient an average of the gradient in RGB is returned. + */ +Colors::Color ColorItem::getColor() const { if (is_paint_none()) { - return {1.0, 1.0, 1.0}; - } else if (auto const rgbdata = std::get_if(&data)) { - auto [r, g, b] = rgbdata->rgb; - return {r / 255.0, g / 255.0, b / 255.0}; + return Colors::Color(0xffffffff); + } else if (auto const color = std::get_if(&data)) { + return *color; } else if (auto const graddata = std::get_if(&data)) { auto grad = graddata->gradient; auto pat = Cairo::RefPtr(new Cairo::Pattern(grad->create_preview_pattern(1), true)); @@ -589,12 +571,12 @@ std::array ColorItem::average_color() const cr->set_source(pat); cr->paint(); auto rgb = img->get_data(); - return {rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0}; + return Colors::Color(Colors::Space::Type::RGB, {rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0}); } // unreachable assert(false); - return {1.0, 1.0, 1.0}; + return Colors::Color(0xffffffff); } bool ColorItem::is_paint_none() const { diff --git a/src/ui/dialog/color-item.h b/src/ui/dialog/color-item.h index 61d1078c544d414cef6b4ee932167c08d11c8b6e..77086040d9e98d3cd379817e79b15c8cf3e69177 100644 --- a/src/ui/dialog/color-item.h +++ b/src/ui/dialog/color-item.h @@ -20,7 +20,7 @@ #include #include // Gtk::EventSequenceState -#include "widgets/paintdef.h" +#include "colors/color.h" namespace Cairo { class Context; @@ -51,8 +51,10 @@ class DialogBase; class ColorItem : public Gtk::DrawingArea { public: - /// Create a static color from a paintdef. - ColorItem(PaintDef const&, DialogBase*); + // No fill option + ColorItem(DialogBase*); + /// Create a static color + ColorItem(Colors::Color, DialogBase*); /// Add new group or filler element. ColorItem(Glib::ustring name); ~ColorItem() override; @@ -118,11 +120,8 @@ private: // Draw the color only (i.e. no indicators) to a Cairo context. Used for drawing both the widget and the drag/drop icon. void draw_color(Cairo::RefPtr const &cr, int w, int h) const; - // Construct an equivalent paintdef for use during drag/drop. - PaintDef to_paintdef() const; - // Return the color (or average if a gradient), for choosing the color of the fill/stroke indicators. - std::array average_color() const; + Colors::Color getColor() const; // Description of the color, shown in help text. Glib::ustring description; @@ -136,9 +135,8 @@ private: // The color. struct Undefined {}; struct PaintNone {}; - struct RGBData { std::array rgb; }; struct GradientData { SPGradient *gradient; }; - std::variant data; + std::variant data; // The dialog this widget belongs to. Used for determining what desktop to take action on. DialogBase *dialog = nullptr; diff --git a/src/ui/dialog/document-properties.cpp b/src/ui/dialog/document-properties.cpp index 638962f1d11df80d556e50f11b75890407d0e60a..c482b18555ab1fdb2e5deb29ef5e21e543557060 100644 --- a/src/ui/dialog/document-properties.cpp +++ b/src/ui/dialog/document-properties.cpp @@ -57,7 +57,9 @@ #include "rdf.h" #include "page-manager.h" #include "selection.h" -#include "color/cms-system.h" + +#include "colors/cms/profile.h" +#include "colors/document-cms.h" #include "helper/auto-connection.h" #include "io/sys.h" #include "object/color-profile.h" @@ -65,7 +67,6 @@ #include "object/sp-root.h" #include "object/sp-script.h" #include "streq.h" -#include "svg/svg-color.h" #include "ui/dialog/filedialog.h" #include "ui/icon-loader.h" #include "ui/icon-names.h" @@ -295,11 +296,10 @@ void set_namedview_bool(SPDesktop* desktop, const Glib::ustring& operation, SPAt DocumentUndo::done(desktop->getDocument(), operation, ""); } -void set_color(SPDesktop* desktop, Glib::ustring operation, unsigned int rgba, SPAttr color_key, SPAttr opacity_key = SPAttr::INVALID) { +void set_color(SPDesktop* desktop, Glib::ustring operation, SPAttr color_key, SPAttr opacity_key, Colors::Color const &color) { if (!desktop || !desktop->getDocument()) return; - desktop->getNamedView()->change_color(rgba, color_key, opacity_key); - + desktop->getNamedView()->change_color(color_key, opacity_key, color); desktop->getDocument()->setModifiedSinceSave(); DocumentUndo::maybeDone(desktop->getDocument(), ("document-color-" + operation).c_str(), operation, ""); } @@ -476,19 +476,19 @@ void DocumentProperties::build_page() _page = Gtk::manage(PageProperties::create()); _page_page->table().attach(*_page, 0, 0); - _page->signal_color_changed().connect([this](unsigned const color, PageProperties::Color const element){ + _page->signal_color_changed().connect([this](Colors::Color const &color, PageProperties::Color const element){ if (_wr.isUpdating() || !_wr.desktop()) return; _wr.setUpdating(true); switch (element) { case PageProperties::Color::Desk: - set_color(_wr.desktop(), _("Desk color"), color, SPAttr::INKSCAPE_DESK_COLOR); + set_color(_wr.desktop(), _("Desk color"), SPAttr::INKSCAPE_DESK_COLOR, SPAttr::INKSCAPE_DESK_OPACITY, color); break; case PageProperties::Color::Background: - set_color(_wr.desktop(), _("Background color"), color, SPAttr::PAGECOLOR); + set_color(_wr.desktop(), _("Background color"), SPAttr::PAGECOLOR, SPAttr::INKSCAPE_PAGEOPACITY, color); break; case PageProperties::Color::Border: - set_color(_wr.desktop(), _("Border color"), color, SPAttr::BORDERCOLOR, SPAttr::BORDEROPACITY); + set_color(_wr.desktop(), _("Border color"), SPAttr::BORDERCOLOR, SPAttr::BORDEROPACITY, color); break; } _wr.setUpdating(false); @@ -621,24 +621,24 @@ void DocumentProperties::populate_available_profiles(){ // Iterate through the list of profiles and add the name to the combo box. bool home = true; // initial value doesn't matter, it's just to avoid a compiler warning bool first = true; - auto cms_system = Inkscape::CMSSystem::get(); - for (auto const &info: cms_system->get_system_profile_infos()) { + auto &cms_system = Inkscape::Colors::CMS::System::get(); + for (auto const &profile: cms_system.getProfiles()) { Gtk::TreeModel::Row row; // add a separator between profiles from the user's home directory and system profiles - if (!first && info.in_home() != home) + if (!first && profile->inHome() != home) { row = *(_AvailableProfilesListStore->append()); row[_AvailableProfilesListColumns.fileColumn] = ""; row[_AvailableProfilesListColumns.nameColumn] = ""; row[_AvailableProfilesListColumns.separatorColumn] = true; } - home = info.in_home(); + home = profile->inHome(); first = false; row = *(_AvailableProfilesListStore->append()); - row[_AvailableProfilesListColumns.fileColumn] = info.get_path(); - row[_AvailableProfilesListColumns.nameColumn] = info.get_name(); + row[_AvailableProfilesListColumns.fileColumn] = profile->getPath(); + row[_AvailableProfilesListColumns.nameColumn] = profile->getName(); row[_AvailableProfilesListColumns.separatorColumn] = false; } } @@ -694,33 +694,8 @@ void DocumentProperties::linkSelectedProfile() Glib::ustring file = (*iter)[_AvailableProfilesListColumns.fileColumn]; Glib::ustring name = (*iter)[_AvailableProfilesListColumns.nameColumn]; - std::vector current = document->getResourceList( "iccprofile" ); - for (auto obj : current) { - Inkscape::ColorProfile* prof = reinterpret_cast(obj); - if (!strcmp(prof->href, file.c_str())) - return; - } - Inkscape::XML::Document *xml_doc = document->getReprDoc(); - Inkscape::XML::Node *cprofRepr = xml_doc->createElement("svg:color-profile"); - std::string nameStr = name.empty() ? "profile" : name; // TODO add some auto-numbering to avoid collisions - sanitizeName(nameStr); - cprofRepr->setAttribute("name", nameStr); - cprofRepr->setAttribute("xlink:href", Glib::filename_to_uri(Glib::filename_from_utf8(file))); - cprofRepr->setAttribute("id", file); - - // Checks whether there is a defs element. Creates it when needed - Inkscape::XML::Node *defsRepr = sp_repr_lookup_name(xml_doc, "svg:defs"); - if (!defsRepr) { - defsRepr = xml_doc->createElement("svg:defs"); - xml_doc->root()->addChild(defsRepr, nullptr); - } - - g_assert(document->getDefs()); - defsRepr->addChild(cprofRepr, nullptr); - - // TODO check if this next line was sometimes needed. It being there caused an assertion. - //Inkscape::GC::release(defsRepr); - + auto filename = Glib::filename_to_uri(Glib::filename_from_utf8(file)); + document->getDocumentCMS().attachProfileToDoc(filename, ColorProfileStorage::HREF_FILE, Colors::RenderingIntent::AUTO, name); // inform the document, so we can undo DocumentUndo::done(document, _("Link Color Profile"), ""); @@ -728,20 +703,6 @@ void DocumentProperties::linkSelectedProfile() } } -struct _cmp { - bool operator()(const SPObject * const & a, const SPObject * const & b) - { - const Inkscape::ColorProfile &a_prof = reinterpret_cast(*a); - const Inkscape::ColorProfile &b_prof = reinterpret_cast(*b); - auto const a_name_casefold = g_utf8_casefold(a_prof.name, -1); - auto const b_name_casefold = g_utf8_casefold(b_prof.name, -1); - int result = g_strcmp0(a_name_casefold, b_name_casefold); - g_free(a_name_casefold); - g_free(b_name_casefold); - return result < 0; - } -}; - template struct static_caster { To * operator () (From * value) const { return static_cast(value); } }; @@ -750,9 +711,6 @@ void DocumentProperties::populate_linked_profiles_box() _LinkedProfilesListStore->clear(); if (auto document = getDocument()) { std::vector current = document->getResourceList( "iccprofile" ); - if (! current.empty()) { - _emb_profiles_observer.set((*(current.begin()))->parent); - } std::set _current; std::transform(current.begin(), @@ -762,7 +720,7 @@ void DocumentProperties::populate_linked_profiles_box() for (auto const &profile: _current) { Gtk::TreeModel::Row row = *(_LinkedProfilesListStore->append()); - row[_LinkedProfilesListColumns.nameColumn] = profile->name; + row[_LinkedProfilesListColumns.nameColumn] = profile->getName(); } } } @@ -787,14 +745,9 @@ void DocumentProperties::removeSelectedProfile(){ } } if (auto document = getDocument()) { - std::vector current = document->getResourceList( "iccprofile" ); - for (auto obj : current) { - Inkscape::ColorProfile* prof = reinterpret_cast(obj); - if (!name.compare(prof->name)){ - prof->deleteObject(true, false); - DocumentUndo::done(document, _("Remove linked color profile"), ""); - break; // removing the color profile likely invalidates part of the traversed list, stop traversing here. - } + if (auto colorprofile = document->getDocumentCMS().getColorProfileForSpace(name)) { + colorprofile->deleteObject(true, false); + DocumentUndo::done(document, _("Remove linked color profile"), ""); } } @@ -885,15 +838,6 @@ void DocumentProperties::build_cms() _LinkedProfilesList.get_selection()->signal_changed().connect( sigc::mem_fun(*this, &DocumentProperties::onColorProfileSelectRow) ); connect_remove_popup_menu(_LinkedProfilesList, _popoverbin, sigc::mem_fun(*this, &DocumentProperties::removeSelectedProfile)); - - if (auto document = getDocument()) { - std::vector current = document->getResourceList( "defs" ); - if (!current.empty()) { - _emb_profiles_observer.set((*(current.begin()))->parent); - } - _emb_profiles_observer.signal_changed().connect(sigc::mem_fun(*this, &DocumentProperties::populate_linked_profiles_box)); - onColorProfileSelectRow(); - } } void DocumentProperties::build_scripting() @@ -1574,11 +1518,11 @@ void DocumentProperties::update_widgets() _page->set_unit(PageProperties::Units::Display, nv->display_units->abbr); } _page->set_check(PageProperties::Check::Checkerboard, nv->desk_checkerboard); - _page->set_color(PageProperties::Color::Desk, nv->desk_color); - _page->set_color(PageProperties::Color::Background, page_manager.background_color); + _page->set_color(PageProperties::Color::Desk, nv->getDeskColor()); + _page->set_color(PageProperties::Color::Background, page_manager.getBackgroundColor()); _page->set_check(PageProperties::Check::Border, page_manager.border_show); _page->set_check(PageProperties::Check::BorderOnTop, page_manager.border_on_top); - _page->set_color(PageProperties::Color::Border, page_manager.border_color); + _page->set_color(PageProperties::Color::Border, page_manager.getBorderColor()); _page->set_check(PageProperties::Check::Shadow, page_manager.shadow_show); _page->set_check(PageProperties::Check::PageLabelStyle, page_manager.label_style != "default"); _page->set_check(PageProperties::Check::AntiAlias, nv->antialias_rendering); @@ -1588,12 +1532,8 @@ void DocumentProperties::update_widgets() _rcb_sgui.setActive (nv->getShowGuides()); _rcb_lgui.setActive (nv->getLockGuides()); - _rcp_gui.setRgba32 (nv->guidecolor); - _rcp_hgui.setRgba32 (nv->guidehicolor); - - //------------------------------------------------Color Management page - - populate_linked_profiles_box(); + _rcp_gui.setColor(nv->getGuideColor()); + _rcp_hgui.setColor(nv->getGuideHiColor()); //-----------------------------------------------------------meta pages // update the RDF entities; note that this may modify document, maybe doc-undo should be called? @@ -1676,12 +1616,14 @@ void DocumentProperties::documentReplaced() { _root_connection.disconnect(); _namedview_connection.disconnect(); + _cms_connection.disconnect(); if (auto desktop = getDesktop()) { _wr.setDesktop(desktop); _namedview_connection.connect(desktop->getNamedView()->getRepr()); if (auto document = desktop->getDocument()) { _root_connection.connect(document->getRoot()->getRepr()); + _cms_connection = document->getDocumentCMS().connectChanged(sigc::mem_fun(*this, &DocumentProperties::populate_linked_profiles_box)); } populate_linked_profiles_box(); update_widgets(); @@ -1899,17 +1841,15 @@ GridWidget::GridWidget(SPGrid *grid) "", _("Grid color"), _("Color of the grid lines"), "empcolor", "empopacity", _wr, repr, doc); - _grid_color->setCustomSetter([](Inkscape::XML::Node* node, std::uint32_t rgba) { - char buf[32]; + _grid_color->setCustomSetter([](Inkscape::XML::Node* node, Colors::Color color) { // major color - sp_svg_write_color(buf, sizeof(buf), rgba); - node->setAttribute("empcolor", buf); - node->setAttributeCssDouble("empopacity", (rgba & 0xff) / 255.0); + node->setAttribute("empcolor", color.toString(false)); + node->setAttributeSvgDouble("empopacity", color.getOpacity()); + // minor color at half opacity - rgba = (rgba & ~0xff) | ((rgba & 0xff) / 2); - sp_svg_write_color(buf, sizeof(buf), rgba); - node->setAttribute("color", buf); - node->setAttributeCssDouble("opacity", (rgba & 0xff) / 255.0); + color.addOpacity(0.5); + node->setAttribute("color", color.toString(false)); + node->setAttributeCssDouble("opacity", color.getOpacity()); }); _grid_color->set_spacing(0); _no_of_lines = Gtk::make_managed( @@ -2119,7 +2059,7 @@ void GridWidget::update() _margin_y->setValueKeepUnit(margin.y(), "px"); } - _grid_color->setRgba32 (_grid->getMajorColor()); + _grid_color->setColor(_grid->getMajorColor()); show(_no_of_lines, !modular); _no_of_lines->setValue(_grid->getMajorLineInterval()); diff --git a/src/ui/dialog/document-properties.h b/src/ui/dialog/document-properties.h index ed51a3e430100c756e0e0b9170ccd78fe58c9aea..dc9c0a9d9f52082dcb045c903fdd374c6b058464 100644 --- a/src/ui/dialog/document-properties.h +++ b/src/ui/dialog/document-properties.h @@ -127,7 +127,7 @@ private: void set_viewbox_pos(SPDesktop* desktop, double x, double y); void set_viewbox_size(SPDesktop* desktop, double width, double height); - Inkscape::XML::SignalObserver _emb_profiles_observer, _scripts_observer; + Inkscape::XML::SignalObserver _scripts_observer; UI::Widget::PopoverBin _popoverbin; Gtk::Notebook _notebook; @@ -265,6 +265,8 @@ private: // nodes connected to listeners WatchConnection _namedview_connection; WatchConnection _root_connection; + + auto_connection _cms_connection; }; } // namespace Dialog diff --git a/src/ui/dialog/document-resources.cpp b/src/ui/dialog/document-resources.cpp index 0b9cf3ebcdd9dfe783e52c695cf5fd93ce912a6c..698e57f4810d174ccc0268cdd38f286388205417 100644 --- a/src/ui/dialog/document-resources.cpp +++ b/src/ui/dialog/document-resources.cpp @@ -60,7 +60,8 @@ #include #include -#include "color.h" +#include "colors/color.h" +#include "colors/color-set.h" #include "desktop.h" #include "document-undo.h" #include "helper/choose-file.h" @@ -629,27 +630,24 @@ Cairo::RefPtr render_color(uint32_t rgb, double size, double rad return add_background_to_image(nul, rgb, size / 2, radius, device_scale, 0x7f7f7f00); } -void collect_object_colors(SPObject& obj, std::map& colors) { +void collect_object_colors(SPObject& obj, Colors::ColorSet &colors) { auto style = obj.style; - if (style->stroke.set && style->stroke.colorSet) { - const auto& c = style->stroke.value.color; - colors[c.toString()] = c; - } + auto add = [&colors](Colors::Color const &c) { + colors.set(c.toString(), c); + }; + if (style->stroke.set && style->stroke.isColor()) { + add(style->stroke.getColor()); + } if (style->color.set) { - const auto& c = style->color.value.color; - colors[c.toString()] = c; + add(style->color.getColor()); } - if (style->fill.set) { - const auto& c = style->fill.value.color; - colors[c.toString()] = c; + add(style->fill.getColor()); } - if (style->solid_color.set) { - const auto& c = style->solid_color.value.color; - colors[c.toString()] = c; + add(style->solid_color.getColor()); } } @@ -666,12 +664,10 @@ void apply_visitor(SPObject& object, V&& visitor) { } } -std::map collect_colors(SPObject* object) { - std::map colors; +void collect_colors(SPObject* object, Colors::ColorSet &colors) { if (object) { apply_visitor(*object, [&](SPObject& obj){ collect_object_colors(obj, colors); }); } - return colors; } void collect_used_fonts(SPObject& object, std::set& fonts) { @@ -758,7 +754,7 @@ details::Statistics collect_statistics(SPObject* root) { return stats; } - std::map colors; + Colors::ColorSet colors; std::set fonts; apply_visitor(*root, [&](SPObject& obj){ @@ -929,12 +925,12 @@ void DocumentResources::selectionModified(Inkscape::Selection* selection, unsign auto get_id = [](const SPObject* object) { auto id = object->getId(); return id ? id : ""; }; auto label_fmt = [](const char* label, const Glib::ustring& id) { return label && *label ? label : '#' + id; }; -void add_colors(Glib::RefPtr& item_store, const std::map& colors, int device_scale) { +void add_colors(Glib::RefPtr& item_store, Colors::ColorSet const &colors, int device_scale) { for (auto&& it : colors) { const auto& color = it.second; Glib::ustring name = color.toString(); - auto rgba32 = color.toRGBA32(0xff); + auto rgba32 = color.toRGBA(0xff); auto rgb24 = rgba32 >> 8; int size = 20; @@ -1124,12 +1120,15 @@ void DocumentResources::refresh_page(const Glib::ustring& id) { switch (rsrc) { case Colors: - add_colors(_item_store, collect_colors(root), device_scale); - item_width = 70; - items_selectable = false; // to do: make selectable? - can_extract = true; - break; - + { + Colors::ColorSet colors; + collect_colors(root, colors); + add_colors(_item_store, colors, device_scale); + item_width = 70; + items_selectable = false; // to do: make selectable? + can_extract = true; + break; + } case Symbols: { auto opt = object_renderer::options(); diff --git a/src/ui/dialog/export-batch.cpp b/src/ui/dialog/export-batch.cpp index 915a060f11ba1027f8d4f2bd3d6993e380f3b2d1..6c49046471d25f1b449261633c67e42bc4f63000 100644 --- a/src/ui/dialog/export-batch.cpp +++ b/src/ui/dialog/export-batch.cpp @@ -268,6 +268,7 @@ BatchExport::BatchExport(BaseObjectType * const cobject, Glib::RefPtr (builder, "b_progress")) , _prog_batch (get_widget (builder, "b_progress_batch")) , export_list (get_derived_widget(builder, "b_export_list")) + , _background_color(get_derived_widget(builder, "b_backgnd", _("Background color"), true)) { prefs = Inkscape::Preferences::get(); @@ -279,10 +280,6 @@ BatchExport::BatchExport(BaseObjectType * const cobject, Glib::RefPtr(builder, "b_s_layers"); selection_buttons[SELECTION_PAGE] = &get_widget(builder, "b_s_pages"); - auto &button = get_widget(builder, "b_backgnd"); - _bgnd_color_picker = std::make_unique( - _("Background color"), _("Color used to fill the image background"), 0xffffff00, true, &button); - path_chooser.signal_clicked().connect([this] { pickBatchPath(); }); setup(); @@ -363,7 +360,7 @@ void BatchExport::setup() export_conn = export_btn.signal_clicked().connect(sigc::mem_fun(*this, &BatchExport::onExport)); cancel_conn = cancel_btn.signal_clicked().connect(sigc::mem_fun(*this, &BatchExport::onCancel)); hide_all.signal_toggled().connect(sigc::mem_fun(*this, &BatchExport::refreshPreview)); - _bgnd_color_picker->connectChanged([=, this](guint32 color){ + _background_color.connectChanged([=, this](Colors::Color const &color){ if (_desktop) { Inkscape::UI::Dialog::set_export_bg_color(_desktop->getNamedView(), color); } @@ -501,7 +498,7 @@ void BatchExport::refreshPreview() _preview_drawing->set_shown_items(std::move(selected)); for (auto &[key, val] : current_items) { - val->refresh(!preview, _bgnd_color_picker->get_current_color()); + val->refresh(!preview, _background_color.get_current_color().toRGBA()); } } } @@ -772,7 +769,7 @@ void BatchExport::onExport() unsigned long int height = (int)(area.height() * dpi / DPI_BASE + 0.5); Export::exportRaster( - area, width, height, dpi, _bgnd_color_picker->get_current_color(), + area, width, height, dpi, _background_color.get_current_color().toRGBA(), item_filename, true, onProgressCallback, this, ext, hide ? &show_only : nullptr); } else { auto copy_doc = _document->copy(); @@ -866,9 +863,7 @@ void BatchExport::setDocument(SPDocument *document) if (document) { // when the page selected is changed, update the export area _pages_changed_connection = document->getPageManager().connectPagesChanged([this]() { pagesChanged(); }); - - auto bg_color = get_export_bg_color(document->getNamedView(), 0xffffff00); - _bgnd_color_picker->setRgba32(bg_color); + _background_color.setColor(get_export_bg_color(document->getNamedView(), Colors::Color(0xffffff00))); _preview_drawing = std::make_shared(document); } else { _preview_drawing.reset(); diff --git a/src/ui/dialog/export-batch.h b/src/ui/dialog/export-batch.h index 41ea57db0371dbffce9e2bbdb558e78da4e1d10d..b4d6a5d989fa3157e53c41fcd82a17ddaf1de8c8 100644 --- a/src/ui/dialog/export-batch.h +++ b/src/ui/dialog/export-batch.h @@ -141,6 +141,7 @@ private: Gtk::ProgressBar &_prog_batch; ExportList &export_list; Gtk::Box &progress_box; + Inkscape::UI::Widget::ColorPicker &_background_color; // Store all items to be displayed in flowbox std::map> current_items; @@ -183,8 +184,6 @@ private: auto_connection refresh_items_conn; // SVG Signals auto_connection _pages_changed_connection; - - std::unique_ptr _bgnd_color_picker; }; } // namespace Dialog diff --git a/src/ui/dialog/export-single.cpp b/src/ui/dialog/export-single.cpp index 0720840e3f75abcda5f0e821b244d250af4b92b5..2d81d1fc1b2946677942d707a7157d10dc5b93b9 100644 --- a/src/ui/dialog/export-single.cpp +++ b/src/ui/dialog/export-single.cpp @@ -80,6 +80,7 @@ SingleExport::SingleExport(BaseObjectType *cobject, const Glib::RefPtr (builder, "si_progress")) , cancel_button (get_widget (builder, "si_cancel")) , progress_box (get_widget (builder, "si_inprogress")) + , _background_color (get_derived_widget(builder, "si_backgnd", _("Background color"), true)) { prefs = Inkscape::Preferences::get(); @@ -113,10 +114,6 @@ SingleExport::SingleExport(BaseObjectType *cobject, const Glib::RefPtr(builder, "si_prefs"); pref_button_box.append(*si_extension_cb.getPrefButton()); - auto &button = get_widget(builder, "si_backgnd"); - _bgnd_color_picker = std::make_unique( - _("Background color"), _("Color used to fill background"), 0xffffff00, true, &button); - setup(); } @@ -206,7 +203,7 @@ void SingleExport::setup() si_filename_entry.signal_activate().connect(sigc::mem_fun(*this, &SingleExport::onExport)); si_show_preview.signal_toggled().connect(sigc::mem_fun(*this, &SingleExport::refreshPreview)); si_hide_all.signal_toggled().connect(sigc::mem_fun(*this, &SingleExport::refreshPreview)); - _bgnd_color_picker->connectChanged([=, this](guint32 color){ + _background_color.connectChanged([=, this](Colors::Color const &color){ if (_desktop) { Inkscape::UI::Dialog::set_export_bg_color(_desktop->getNamedView(), color); } @@ -647,7 +644,7 @@ void SingleExport::onExport() std::vector selected(selection->items().begin(), selection->items().end()); exportSuccessful = Export::exportRaster( - area, width, height, dpi, _bgnd_color_picker->get_current_color(), + area, width, height, dpi, _background_color.get_current_color().toRGBA(), filename, false, onProgressCallback, this, omod, selected_only ? &selected : nullptr); @@ -1006,7 +1003,7 @@ void SingleExport::refreshPreview() bool have_pages = false; for (auto const child : UI::get_children(pages_list)) { if (auto bi = dynamic_cast(child)) { - bi->refresh(!show, _bgnd_color_picker->get_current_color()); + bi->refresh(!show, _background_color.get_current_color().toRGBA()); have_pages = true; } } @@ -1023,7 +1020,7 @@ void SingleExport::refreshPreview() float y0 = unit->convert(spin_buttons[SPIN_Y0]->get_value(), "px"); float y1 = unit->convert(spin_buttons[SPIN_Y1]->get_value(), "px"); preview.setBox(Geom::Rect(x0, y0, x1, y1) * _document->dt2doc()); - preview.setBackgroundColor(_bgnd_color_picker->get_current_color()); + preview.setBackgroundColor(_background_color.get_current_color().toRGBA()); preview.queueRefresh(); } @@ -1051,8 +1048,7 @@ void SingleExport::setDocument(SPDocument *document) _page_selected_connection = pm.connectPageSelected(sigc::mem_fun(*this, &SingleExport::onPagesSelected)); _page_modified_connection = pm.connectPageModified(sigc::mem_fun(*this, &SingleExport::onPagesModified)); _page_changed_connection = pm.connectPagesChanged(sigc::mem_fun(*this, &SingleExport::onPagesChanged)); - auto bg_color = get_export_bg_color(document->getNamedView(), 0xffffff00); - _bgnd_color_picker->setRgba32(bg_color); + _background_color.setColor(get_export_bg_color(document->getNamedView(), Colors::Color(0xffffff00))); _preview_drawing = std::make_shared(document); preview.setDrawing(_preview_drawing); diff --git a/src/ui/dialog/export-single.h b/src/ui/dialog/export-single.h index 180f2eda44ef49df5d1ec763a03e4a7fc585263c..ef0e2fe60191466005641744d541b5906e2cdc43 100644 --- a/src/ui/dialog/export-single.h +++ b/src/ui/dialog/export-single.h @@ -132,6 +132,7 @@ private: Gtk::ProgressBar &progress_bar; Gtk::Widget &progress_box; Gtk::Button &cancel_button; + UI::Widget::ColorPicker &_background_color; bool filename_modified = false; Glib::ustring original_name; @@ -210,8 +211,6 @@ private: auto_connection _page_selected_connection; auto_connection _page_modified_connection; auto_connection _page_changed_connection; - - std::unique_ptr _bgnd_color_picker; }; } // namespace Dialog diff --git a/src/ui/dialog/export.cpp b/src/ui/dialog/export.cpp index beb9ac2bafd0ca11197c8e30ff5231f66dba5965..6f811d338441854e1967180511fae7ef1c806a25 100644 --- a/src/ui/dialog/export.cpp +++ b/src/ui/dialog/export.cpp @@ -34,7 +34,10 @@ #include "message.h" // for MessageType #include "message-stack.h" -#include "color/color-conv.h" +#include "colors/utils.h" +#include "colors/color.h" +#include "colors/manager.h" + #include "extension/output.h" #include "helper/png-write.h" #include "io/resource.h" @@ -479,16 +482,16 @@ std::string Export::defaultFilename(SPDocument *doc, std::string &filename_entry return filename; } -void set_export_bg_color(SPObject* object, guint32 color) { +void set_export_bg_color(SPObject* object, Inkscape::Colors::Color const &color) { if (object) { - object->setAttribute("inkscape:export-bgcolor", Inkscape::Util::rgba_color_to_string(color).c_str()); + object->setAttribute("inkscape:export-bgcolor", color.toString()); } } -guint32 get_export_bg_color(SPObject* object, guint32 default_color) { +Inkscape::Colors::Color get_export_bg_color(SPObject* object, Inkscape::Colors::Color const &default_color) { if (object) { - if (auto color = Inkscape::Util::string_to_rgba_color(object->getAttribute("inkscape:export-bgcolor"))) { - return *color; + if (auto c = Inkscape::Colors::Color::parse(object->getAttribute("inkscape:export-bgcolor"))) { + return *c; } } return default_color; diff --git a/src/ui/dialog/export.h b/src/ui/dialog/export.h index ae6a31d7aa57182a4711053521dd7d992ec43e5b..815f8a3e15e5799956f7515b2843ec5ae9f5e4d8 100644 --- a/src/ui/dialog/export.h +++ b/src/ui/dialog/export.h @@ -35,6 +35,9 @@ class SPPage; namespace Inkscape { class Preferences; +namespace Colors { +class Color; +} namespace Extension { class Output; @@ -51,8 +54,8 @@ enum notebook_page BATCH_EXPORT }; -void set_export_bg_color(SPObject* object, guint32 color); -guint32 get_export_bg_color(SPObject* object, guint32 default_color); +void set_export_bg_color(SPObject* object, Inkscape::Colors::Color const &color); +Inkscape::Colors::Color get_export_bg_color(SPObject* object, Inkscape::Colors::Color const &default_color); class Export final : public DialogBase { diff --git a/src/ui/dialog/filter-effects-dialog.cpp b/src/ui/dialog/filter-effects-dialog.cpp index 0129b5c5f5250a2b7948828bdd7417d3e3912978..d80be8a4a17292dcb03beb2d23239a4ae3b37506 100644 --- a/src/ui/dialog/filter-effects-dialog.cpp +++ b/src/ui/dialog/filter-effects-dialog.cpp @@ -74,7 +74,6 @@ #include "object/filters/pointlight.h" #include "object/filters/spotlight.h" #include "selection-chemistry.h" -#include "svg/svg-color.h" #include "ui/builder-utils.h" #include "ui/column-menu-builder.h" #include "ui/controller.h" @@ -312,42 +311,29 @@ class ColorButton : public Widget::ColorPicker, public AttrWidget { public: ColorButton(unsigned int def, const SPAttr a, char* tip_text) - : ColorPicker(_("Select color"), tip_text ? tip_text : "", 0x000000ff, false), + : ColorPicker(_("Select color"), tip_text ? tip_text : "", Colors::Color(0x000000ff), false, false), AttrWidget(a, def) { - use_transparency(false); - connectChanged([this](unsigned const rgba){ signal_attr_changed().emit(); }); + connectChanged([this](Colors::Color const &color){ signal_attr_changed().emit(); }); if (tip_text) { set_tooltip_text(tip_text); } - setRgba32(0xffffffff); + setColor(Colors::Color(0xffffffff)); } - // Returns the color in 'rgb(r,g,b)' form. Glib::ustring get_as_attribute() const override { - // no doubles here, so we can use the standard string stream. - std::ostringstream os; - - const auto c = get_current_color(); - int r = SP_RGBA32_R_U(c); - int g = SP_RGBA32_G_U(c); - int b = SP_RGBA32_B_U(c); - os << "rgb(" << r << "," << g << "," << b << ")"; - return os.str(); + return get_current_color().toString(false); } - void set_from_attribute(SPObject* o) override { const gchar* val = attribute_value(o); - guint32 i = 0; - if(val) { - i = sp_svg_read_color(val, 0xFFFFFFFF); + if (auto color = Colors::Color::parse(val)) { + setColor(*color); } else { - i = (guint32) get_default()->as_uint(); + setColor(Colors::Color(get_default()->as_uint())); } - setRgba32(i); } }; diff --git a/src/ui/dialog/global-palettes.cpp b/src/ui/dialog/global-palettes.cpp index 7e68a363bb4a99614e7a443c0e0724de7a909c09..11725239e52131ff31f7c576fa47f36e3b9888b0 100644 --- a/src/ui/dialog/global-palettes.cpp +++ b/src/ui/dialog/global-palettes.cpp @@ -33,14 +33,17 @@ #include #include -#include "color/cmyk-conv.h" +#include "colors/manager.h" +#include "colors/spaces/enum.h" + #include "helper/choose-file.h" -#include "hsluv.h" +#include "colors/spaces/lab.h" #include "io/resource.h" #include "io/sys.h" #include "util/delete-with.h" -using Inkscape::Util::delete_with; #include "util-string/ustring-format.h" +using Inkscape::Util::delete_with; +using namespace Inkscape::Colors; namespace { @@ -143,29 +146,6 @@ void load_acb_palette(PaletteFileData& palette, std::string const &fname) { palette.columns = read_value(stream); palette.page_offset = read_value(stream); auto cs = read_value(stream); - int components = 0; - PaletteFileData::ColorSpace color_space; - - switch (cs) { - case 0: // RGB - components = 3; - color_space = PaletteFileData::Rgb255; - break; - case 2: // CMYK - components = 4; - color_space = PaletteFileData::Cmyk100; - break; - case 7: // LAB - components = 3; - color_space = PaletteFileData::Lab100; - break; - case 8: // Grayscale - components = 1; - color_space = PaletteFileData::Rgb255; // using RGB for grayscale - break; - default: - throw std::runtime_error(_("ACB file color space not supported.")); - } auto ext = get_extension(ttl); if (ext == ".acb") { @@ -183,73 +163,46 @@ void load_acb_palette(PaletteFileData& palette, std::string const &fname) { } palette.colors.reserve(color_count); - // simple CMYK converter here; original palette colors are kept for later use - Inkscape::CmykConverter convert; + + auto &cm = Manager::get(); + std::shared_ptr space; + + switch (cs) { + case 0: // RGB + space = cm.find(Space::Type::RGB); + break; + case 2: // CMYK + space = cm.find(Space::Type::CMYK); + break; + case 7: // LAB + space = cm.find(Space::Type::LAB); + break; + case 8: // Grayscale + space = cm.find(Space::Type::Gray); + break; + default: + throw std::runtime_error(_("ACB file color space not supported.")); + } for (int index = 0; index < color_count; ++index) { auto name = read_pstring(stream); if (name.substr(0, 3) == "$$$") name = extract(name); auto code = read_string(stream, 6); - auto channels = read_data(stream, components); - PaletteFileData::Color color; std::ostringstream ost; ost.precision(3); - switch (color_space) { - case PaletteFileData::Lab100: { - auto l = std::floor(channels[0] / 2.55f + 0.5f); - auto a = channels[1] - 128.0f; - auto b = channels[2] - 128.0f; - auto rgb = Hsluv::lab_to_rgb(l, a, b); - unsigned ur = rgb[0] * 255; - unsigned ug = rgb[1] * 255; - unsigned ub = rgb[2] * 255; - color = {{l, a, b, 0}, color_space, {ur, ug, ub}}; - ost << "L: " << l << " a: " << a << " b: " << b; - } - break; - - case PaletteFileData::Cmyk100: { - // 0% - 100% - auto c = std::floor((255 - channels[0]) / 2.55f + 0.5f); - auto m = std::floor((255 - channels[1]) / 2.55f + 0.5f); - auto y = std::floor((255 - channels[2]) / 2.55f + 0.5f); - auto k = std::floor((255 - channels[3]) / 2.55f + 0.5f); - auto [r, g, b] = convert.cmyk_to_rgb(c, m, y, k); - color = {{c, m, y, k}, color_space, {r, g, b}}; - ost << "C: " << c << "% M: " << m << "% Y: " << y << "% K: " << k << '%'; - } - break; - - case PaletteFileData::Rgb255: { - float r = channels[0]; - float g = cs == 1 ? r : channels[1]; - float b = cs == 1 ? r : channels[2]; - unsigned ur = r; - unsigned ug = g; - unsigned ub = b; - color = {{r, g, b, 0}, color_space, {ur, ug, ub}}; - if (cs == 1) { - // grayscale - ost << "R: " << ur << " G: " << ug << " B: " << ub; - } - else { - ost << "R: " << ur << " G: " << ug << " B: " << ub; - } - } - break; - - default: - throw std::runtime_error(_("Palette color space unexpected.")); + std::vector data; + for (auto channel : read_data(stream, space->getComponentCount())) { + data.emplace_back(channel / 255.0); } + auto color = Color(space, data); if (name.empty()) { palette.colors.emplace_back(PaletteFileData::SpacerItem()); } else { - color.name = prefix + name + suffix; - color.definition = ost.str(); + color.setName(prefix + name + suffix); palette.colors.emplace_back(std::move(color)); } } @@ -269,13 +222,13 @@ void load_ase_swatches(PaletteFileData& palette, std::string const &fname) { } auto block_count = read_value(stream); - Inkscape::CmykConverter convert; - auto to_mode = [](int type){ - switch (type) { - case 0: return PaletteFileData::Global; - case 1: return PaletteFileData::Spot; - default: return PaletteFileData::Normal; - } + auto &cm = Manager::get(); + + static std::map name_map = { + {"RGB ", Space::Type::RGB}, + {"LAB ", Space::Type::LAB}, + {"CMYK", Space::Type::CMYK}, + {"GRAY", Space::Type::Gray} }; for (uint32_t block = 0; block < block_count; ++block) { @@ -289,60 +242,26 @@ void load_ase_swatches(PaletteFileData& palette, std::string const &fname) { } else if (block_type == 0x0001) { // color entry auto color_name = read_pstring(stream, true); - auto mode = read_string(stream, 4); - if (mode == "CMYK") { - auto c = read_float(stream) * 100; - auto m = read_float(stream) * 100; - auto y = read_float(stream) * 100; - auto k = read_float(stream) * 100; - auto type = read_value(stream); - auto mode = to_mode(type); - auto [r, g, b] = convert.cmyk_to_rgb(c, m, y, k); - ost << "C: " << c << "% M: " << m << "% Y: " << y << "% K: " << k << '%'; - palette.colors.emplace_back( - PaletteFileData::Color {{c, m, y, k}, PaletteFileData::Cmyk100, {r, g, b}, color_name, ost.str(), mode} - ); - } - else if (mode == "RGB ") { - auto r = read_float(stream) * 255; - auto g = read_float(stream) * 255; - auto b = read_float(stream) * 255; - auto type = read_value(stream); - auto mode = to_mode(type); - palette.colors.emplace_back( - PaletteFileData::Color {{r, g, b, 0}, PaletteFileData::Rgb255, {(unsigned)r, (unsigned)g, (unsigned)b}, color_name, "", mode} - ); - } - else if (mode == "LAB ") { - //TODO - verify scale - auto l = read_float(stream) * 100; - auto a = read_float(stream) * 100; - auto b = read_float(stream) * 100; - auto type = read_value(stream); - auto mode = to_mode(type); - auto rgb = Hsluv::lab_to_rgb(l, a, b); - unsigned ur = rgb[0] * 255; - unsigned ug = rgb[1] * 255; - unsigned ub = rgb[2] * 255; - ost << "L: " << l << " a: " << a << " b: " << b; - palette.colors.emplace_back( - PaletteFileData::Color {{l, a, b, 0}, PaletteFileData::Lab100, {ur, ug, ub}, color_name, ost.str(), mode} - ); - } - else if (mode == "Gray") { - auto g = read_float(stream) * 255; - auto type = read_value(stream); - auto mode = to_mode(type); - palette.colors.emplace_back( - PaletteFileData::Color {{g, g, g, 0}, PaletteFileData::Rgb255, {(unsigned)g, (unsigned)g, (unsigned)g}, color_name, "", mode} - ); - } - else { + auto space_name = read_string(stream, 4); + + auto it = name_map.find(space_name); + if (it == name_map.end()) { std::ostringstream ost; - ost << _("ASE color mode not recognized:") << " '" << mode << "'."; + ost << _("ASE color mode not recognized:") << " '" << space_name << "'."; throw std::runtime_error(ost.str()); } - + auto space = cm.find(it->second); + std::vector data; + for (unsigned i = 0; i < space->getComponentCount(); i++) { + data.emplace_back(read_float(stream)); + } + auto color = Color(space, data); + + read_value(stream); // type uint16, ignored for now + // auto mode = to_mode(type); Is this used? 0, Global, 1, Spot, else Normal + + color.setName(color_name); + palette.colors.emplace_back(color); } else if (block_type == 0xc002) { // group end } @@ -375,30 +294,25 @@ void load_gimp_palette(PaletteFileData& palette, std::string const &path) static auto const regex_cols = Glib::Regex::create("\\s*Columns:\\s*(.*\\S)", Glib::Regex::CompileFlags::OPTIMIZE | Glib::Regex::CompileFlags::ANCHORED); static auto const regex_blank = Glib::Regex::create("\\s*(?:$|#)", Glib::Regex::CompileFlags::OPTIMIZE | Glib::Regex::CompileFlags::ANCHORED); + auto &cm = Manager::get(); + auto space = cm.find(Space::Type::RGB); + while (std::fgets(buf, sizeof(buf), f.get())) { auto line = Glib::ustring(buf); // Unnecessary copy required until using a glibmm with support for string views. TODO: Fix when possible. Glib::MatchInfo match; if (regex_rgb->match(line, match)) { // ::regex_match(line, match, boost::regex(), boost::regex_constants::match_continuous)) { // RGB color, followed by an optional name. - PaletteFileData::Color color; - for (int i = 0; i < 3; i++) { - color.rgb[i] = std::clamp(std::stoi(match.fetch(i + 1)), 0, 255); - color.channels[i] = color.rgb[i]; + + std::vector data; + for (unsigned i = 0; i < space->getComponentCount(); i++) { + data.emplace_back(std::stoi(match.fetch(i+1)) / 255.0); } - color.channels[3] = 0; - color.space = PaletteFileData::Rgb255; - color.name = match.fetch(4); + auto color = Color(space, data); + color.setName(match.fetch(4)); - if (!color.name.empty()) { + if (!color.getName().empty()) { // Translate the name if present. - color.name = g_dpgettext2(nullptr, "Palette", color.name.c_str()); - } else { - // Otherwise, set the name to be the hex value. - color.name = Glib::ustring::compose("#%1%2%3", - Inkscape::ustring::format_classic(std::hex, std::setw(2), std::setfill('0'), color.rgb[0]), - Inkscape::ustring::format_classic(std::hex, std::setw(2), std::setfill('0'), color.rgb[1]), - Inkscape::ustring::format_classic(std::hex, std::setw(2), std::setfill('0'), color.rgb[2]) - ).uppercase(); + color.setName(g_dpgettext2(nullptr, "Palette", color.getName().c_str())); } palette.colors.emplace_back(std::move(color)); diff --git a/src/ui/dialog/global-palettes.h b/src/ui/dialog/global-palettes.h index 65387cc78595135e89f3ec33d876a6c7a42ae590..017961ff6c89999484db660f532e7b6eadc2dd1b 100644 --- a/src/ui/dialog/global-palettes.h +++ b/src/ui/dialog/global-palettes.h @@ -19,6 +19,8 @@ #include #include +#include "colors/color.h" + namespace Gtk { class Window; } // namespace Gtk @@ -42,56 +44,6 @@ struct PaletteFileData /// We can use this info to organize colors in columns in multiples of this value. int columns; - /// Color space of all colors in this palette. Original definitions are kept in "Color.channels" - /// for use with ICC profiles. Preview sRGB colors are inside "Color.rgb" - enum ColorSpace { - Undefined, // not a valid color definition - Rgb255, // RGB 0..255 - Lab100, // Cie*Lab, L 0..100, a, b -128..127 - Cmyk100 // CMYK 0%..100% - }; - - enum ColorMode: uint8_t { - Normal, - Global, - Spot, - }; - - struct Color - { - /// Original color definition (Lab, Cmyk, Rgb); unused channels 0.0 - std::array channels; - - /// Color space of this color. - ColorSpace space = Undefined; - - /// RGB color. - std::array rgb; - - /// Name of the color, either specified in the file or generated from the rgb. - Glib::ustring name; - - /// Color as defined in a palette, for informational purposes. - Glib::ustring definition; - - /// Mode (not used currently, for informational purposes only) - ColorMode mode = Normal; - -#ifdef false // not currently used - bool operator < (const Color& c) const { - for (int i = 0; i < rgb.size(); ++i) { - if (rgb[i] < c.rgb[i]) return true; - if (rgb[i] > c.rgb[i]) return false; - } - for (int i = 0; i < channels.size(); ++i) { - if (channels[i] < c.channels[i]) return true; - if (channels[i] > c.channels[i]) return false; - } - return name < c.name; - } -#endif - }; - // dummy item used for aligning color tiles in a palette enum SpacerItem {}; @@ -100,7 +52,7 @@ struct PaletteFileData Glib::ustring name; }; - using ColorItem = std::variant; + using ColorItem = std::variant; /// The list of colors in the palette. std::vector colors; diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index 91dc437692e99b6afce41662d17179551c8c705c..6b7afe05f85f565e9254e2d008394e333163e154 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -82,13 +82,15 @@ #include "selection.h" #include "style.h" -#include "color/cms-system.h" +#include "colors/cms/system.h" +#include "colors/cms/profile.h" +#include "colors/manager.h" +#include "colors/spaces/base.h" #include "display/control/canvas-item-grid.h" #include "display/nr-filter-gaussian.h" #include "extension/internal/gdkpixbuf-input.h" #include "helper/auto-connection.h" #include "io/resource.h" -#include "svg/svg-color.h" #include "ui/builder-utils.h" #include "ui/controller.h" #include "ui/desktop/menubar.h" @@ -118,7 +120,6 @@ using Inkscape::UI::Widget::PrefItem; using Inkscape::UI::Widget::PrefRadioButtons; using Inkscape::UI::Widget::PrefSpinButton; using Inkscape::UI::Widget::StyleSwatch; -using Inkscape::CMSSystem; using Inkscape::IO::Resource::get_filename; using Inkscape::IO::Resource::UIS; @@ -985,7 +986,7 @@ void InkscapePreferences::initPageTools() AddLayerChangeCheckbox(_page_node, "/tools/nodes", false); _page_node.add_group_header( _("Path outline")); - _t_node_pathoutline_color.init(_("Path outline color"), "/tools/nodes/highlight_color", 0xff0000ff); + _t_node_pathoutline_color.init(_("Path outline color"), "/tools/nodes/highlight_color", "#ff0000ff"); _page_node.add_line( false, "", _t_node_pathoutline_color, "", _("Selects the color used for showing the path outline"), false); _t_node_show_outline.init(_("Always show outline"), "/tools/nodes/show_outline", false); _page_node.add_line( true, "", _t_node_show_outline, "", _("Show outlines for all paths, not only invisible paths")); @@ -1172,9 +1173,7 @@ void InkscapePreferences::get_highlight_colors(guint32 &colorsetbase, guint32 &c size_t endposin = result.find(";"); result = result.substr(startposin + 5, endposin - (startposin + 5)); Util::trim(result); - Gdk::RGBA base_color = Gdk::RGBA(result); - SPColor base_color_sp(base_color.get_red(), base_color.get_green(), base_color.get_blue()); - colorsetbase = base_color_sp.toRGBA32(base_color.get_alpha()); + colorsetbase = to_guint32(Gdk::RGBA(result)); } content.erase(0, endpos + 1); startpos = content.find(prefix + ".success"); @@ -1185,9 +1184,7 @@ void InkscapePreferences::get_highlight_colors(guint32 &colorsetbase, guint32 &c size_t endposin = result.find(";"); result = result.substr(startposin + 5, endposin - (startposin + 5)); Util::trim(result); - Gdk::RGBA success_color = Gdk::RGBA(result); - SPColor success_color_sp(success_color.get_red(), success_color.get_green(), success_color.get_blue()); - colorsetsuccess = success_color_sp.toRGBA32(success_color.get_alpha()); + colorsetsuccess = to_guint32(Gdk::RGBA(result)); } content.erase(0, endpos + 1); startpos = content.find(prefix + ".warning"); @@ -1198,9 +1195,7 @@ void InkscapePreferences::get_highlight_colors(guint32 &colorsetbase, guint32 &c size_t endposin = result.find(";"); result = result.substr(startposin + 5, endposin - (startposin + 5)); Util::trim(result); - Gdk::RGBA warning_color = Gdk::RGBA(result); - SPColor warning_color_sp(warning_color.get_red(), warning_color.get_green(), warning_color.get_blue()); - colorsetwarning = warning_color_sp.toRGBA32(warning_color.get_alpha()); + colorsetwarning = to_guint32(Gdk::RGBA(result)); } content.erase(0, endpos + 1); startpos = content.find(prefix + ".error"); @@ -1211,9 +1206,7 @@ void InkscapePreferences::get_highlight_colors(guint32 &colorsetbase, guint32 &c size_t endposin = result.find(";"); result = result.substr(startposin + 5, endposin - (startposin + 5)); Util::trim(result); - Gdk::RGBA error_color = Gdk::RGBA(result); - SPColor error_color_sp(error_color.get_red(), error_color.get_green(), error_color.get_blue()); - colorseterror = error_color_sp.toRGBA32(error_color.get_alpha()); + colorseterror = to_guint32(Gdk::RGBA(result)); } } } @@ -1227,10 +1220,10 @@ void InkscapePreferences::resetIconsColors(bool themechange) if (!prefs->getBool("/theme/symbolicIcons", false)) { _symbolic_base_colors.set_sensitive(false); _symbolic_highlight_colors.set_sensitive(false); - _symbolic_base_color.setSensitive(false); - _symbolic_success_color.setSensitive(false); - _symbolic_warning_color.setSensitive(false); - _symbolic_error_color.setSensitive(false); + _symbolic_base_color.set_sensitive(false); + _symbolic_success_color.set_sensitive(false); + _symbolic_warning_color.set_sensitive(false); + _symbolic_error_color.set_sensitive(false); return; } @@ -1246,22 +1239,20 @@ void InkscapePreferences::resetIconsColors(bool themechange) // This is a hack to fix a problematic style which isn't updated fast enough on // change from dark to bright themes if (themechange) { - base_color = to_rgba(_symbolic_base_color.get_current_color()); + base_color = to_rgba(_symbolic_base_color.get_current_color().toRGBA()); } - // This colors are set on style.css of inkscape - SPColor base_color_sp(base_color.get_red(), base_color.get_green(), base_color.get_blue()); - //we copy highlight to not use - guint32 colorsetbase = base_color_sp.toRGBA32(base_color.get_alpha()); + // This colors are set on style.css of inkscape, we copy highlight to not use + guint32 colorsetbase = to_guint32(base_color); guint32 colorsetsuccess = colorsetbase; guint32 colorsetwarning = colorsetbase; guint32 colorseterror = colorsetbase; get_highlight_colors(colorsetbase, colorsetsuccess, colorsetwarning, colorseterror); - _symbolic_base_color.setRgba32(colorsetbase); + _symbolic_base_color.setColor(Colors::Color(colorsetbase)); prefs->setUInt("/theme/" + themeiconname + "/symbolicBaseColor", colorsetbase); - _symbolic_base_color.setSensitive(false); + _symbolic_base_color.set_sensitive(false); doChangeIconsColors = true; } else { - _symbolic_base_color.setSensitive(true); + _symbolic_base_color.set_sensitive(true); } if (prefs->getBool("/theme/symbolicDefaultHighColors", true)) { @@ -1272,29 +1263,26 @@ void InkscapePreferences::resetIconsColors(bool themechange) auto const success_color = _symbolic_success_color.get_color(); auto const warning_color = _symbolic_warning_color.get_color(); auto const error_color = _symbolic_error_color .get_color(); - SPColor success_color_sp(success_color.get_red(), success_color.get_green(), success_color.get_blue()); - SPColor warning_color_sp(warning_color.get_red(), warning_color.get_green(), warning_color.get_blue()); - SPColor error_color_sp(error_color.get_red(), error_color.get_green(), error_color.get_blue()); //we copy base to not use - guint32 colorsetbase = success_color_sp.toRGBA32(success_color.get_alpha()); - guint32 colorsetsuccess = success_color_sp.toRGBA32(success_color.get_alpha()); - guint32 colorsetwarning = warning_color_sp.toRGBA32(warning_color.get_alpha()); - guint32 colorseterror = error_color_sp.toRGBA32(error_color.get_alpha()); + guint32 colorsetbase = to_guint32(success_color); + guint32 colorsetsuccess = to_guint32(success_color); + guint32 colorsetwarning = to_guint32(warning_color); + guint32 colorseterror = to_guint32(error_color); get_highlight_colors(colorsetbase, colorsetsuccess, colorsetwarning, colorseterror); - _symbolic_success_color.setRgba32(colorsetsuccess); - _symbolic_warning_color.setRgba32(colorsetwarning); - _symbolic_error_color.setRgba32(colorseterror); + _symbolic_success_color.setColor(Colors::Color(colorsetsuccess)); + _symbolic_warning_color.setColor(Colors::Color(colorsetwarning)); + _symbolic_error_color.setColor(Colors::Color(colorseterror)); prefs->setUInt("/theme/" + themeiconname + "/symbolicSuccessColor", colorsetsuccess); prefs->setUInt("/theme/" + themeiconname + "/symbolicWarningColor", colorsetwarning); prefs->setUInt("/theme/" + themeiconname + "/symbolicErrorColor", colorseterror); - _symbolic_success_color.setSensitive(false); - _symbolic_warning_color.setSensitive(false); - _symbolic_error_color.setSensitive(false); + _symbolic_success_color.set_sensitive(false); + _symbolic_warning_color.set_sensitive(false); + _symbolic_error_color.set_sensitive(false); doChangeIconsColors = true; } else { - _symbolic_success_color.setSensitive(true); - _symbolic_warning_color.setSensitive(true); - _symbolic_error_color.setSensitive(true); + _symbolic_success_color.set_sensitive(true); + _symbolic_warning_color.set_sensitive(true); + _symbolic_error_color.set_sensitive(true); } if (doChangeIconsColors) { @@ -1312,10 +1300,10 @@ void InkscapePreferences::changeIconsColors() guint32 colorsetsuccess = prefs->getUInt("/theme/" + themeiconname + "/symbolicSuccessColor", 0x4AD589ff); guint32 colorsetwarning = prefs->getUInt("/theme/" + themeiconname + "/symbolicWarningColor", 0xF57900ff); guint32 colorseterror = prefs->getUInt("/theme/" + themeiconname + "/symbolicErrorColor", 0xCC0000ff); - _symbolic_base_color.setRgba32(colorsetbase); - _symbolic_success_color.setRgba32(colorsetsuccess); - _symbolic_warning_color.setRgba32(colorsetwarning); - _symbolic_error_color.setRgba32(colorseterror); + _symbolic_base_color.setColor(Colors::Color(colorsetbase)); + _symbolic_success_color.setColor(Colors::Color(colorsetsuccess)); + _symbolic_warning_color.setColor(Colors::Color(colorsetwarning)); + _symbolic_error_color.setColor(Colors::Color(colorseterror)); auto const &colorize_provider = INKSCAPE.themecontext->getColorizeProvider(); if (!colorize_provider) return; @@ -1507,18 +1495,18 @@ void InkscapePreferences::symbolicThemeCheck() } else { changeIconsColors(); } - guint32 colorsetbase = prefs->getUInt("/theme/" + themeiconname + "/symbolicBaseColor", 0x2E3436ff); - guint32 colorsetsuccess = prefs->getUInt("/theme/" + themeiconname + "/symbolicSuccessColor", 0x4AD589ff); - guint32 colorsetwarning = prefs->getUInt("/theme/" + themeiconname + "/symbolicWarningColor", 0xF57900ff); - guint32 colorseterror = prefs->getUInt("/theme/" + themeiconname + "/symbolicErrorColor", 0xCC0000ff); + auto colorsetbase = prefs->getColor("/theme/" + themeiconname + "/symbolicBaseColor", "#2E3436ff"); + auto colorsetsuccess = prefs->getColor("/theme/" + themeiconname + "/symbolicSuccessColor", "#4AD589ff"); + auto colorsetwarning = prefs->getColor("/theme/" + themeiconname + "/symbolicWarningColor", "#F57900ff"); + auto colorseterror = prefs->getColor("/theme/" + themeiconname + "/symbolicErrorColor", "#CC0000ff"); _symbolic_base_color.init(_("Color for symbolic icons:"), "/theme/" + themeiconname + "/symbolicBaseColor", - colorsetbase); + colorsetbase.toString()); _symbolic_success_color.init(_("Color for symbolic success icons:"), - "/theme/" + themeiconname + "/symbolicSuccessColor", colorsetsuccess); + "/theme/" + themeiconname + "/symbolicSuccessColor", colorsetsuccess.toString()); _symbolic_warning_color.init(_("Color for symbolic warning icons:"), - "/theme/" + themeiconname + "/symbolicWarningColor", colorsetwarning); + "/theme/" + themeiconname + "/symbolicWarningColor", colorsetwarning.toString()); _symbolic_error_color.init(_("Color for symbolic error icons:"), - "/theme/" + themeiconname + "/symbolicErrorColor", colorseterror); + "/theme/" + themeiconname + "/symbolicErrorColor", colorseterror.toString()); } } @@ -1846,13 +1834,13 @@ void InkscapePreferences::initPageUI() _symbolic_highlight_colors.signal_toggled().connect(sigc::mem_fun(*this, &InkscapePreferences::resetIconsColorsWrapper)); _page_theme.add_line(true, "", _symbolic_highlight_colors, "", "", true); _symbolic_base_color.init(_("Color for symbolic icons:"), "/theme/" + themeiconname + "/symbolicBaseColor", - 0x2E3436ff); + "#2E3436ff"); _symbolic_success_color.init(_("Color for symbolic success icons:"), - "/theme/" + themeiconname + "/symbolicSuccessColor", 0x4AD589ff); + "/theme/" + themeiconname + "/symbolicSuccessColor", "#4AD589ff"); _symbolic_warning_color.init(_("Color for symbolic warning icons:"), - "/theme/" + themeiconname + "/symbolicWarningColor", 0xF57900ff); + "/theme/" + themeiconname + "/symbolicWarningColor", "#F57900ff"); _symbolic_error_color.init(_("Color for symbolic error icons:"), "/theme/" + themeiconname + "/symbolicErrorColor", - 0xCC0000ff); + "#CC0000ff"); _symbolic_base_color.add_css_class("system_base_color"); _symbolic_success_color.add_css_class("system_success_color"); _symbolic_warning_color.add_css_class("system_warning_color"); @@ -2110,6 +2098,11 @@ void InkscapePreferences::initPageUI() this->AddPage(_page_windows, _("Windows"), iter_ui, PREFS_PAGE_UI_WINDOWS); + // default colors in RGBA + static auto GRID_DEFAULT_MAJOR_COLOR = "#0099e54d"; + static auto GRID_DEFAULT_MINOR_COLOR = "#0099e526"; + static auto GRID_DEFAULT_BLOCK_COLOR = "#0047cb4d"; + // Color pickers _compact_colorselector.init(_("Use compact color selector mode switch"), "/colorselector/switcher", true); _page_color_pickers.add_line(false, "", _compact_colorselector, "", _("Use compact combo box for selecting color modes"), false); @@ -2275,10 +2268,9 @@ static void profileComboChanged( Gtk::ComboBoxText* combo ) } else { Glib::ustring active = combo->get_active_text(); - auto cms_system = Inkscape::CMSSystem::get(); - Glib::ustring path = cms_system->get_path_for_profile(active); - if ( !path.empty() ) { - prefs->setString("/options/displayprofile/uri", path); + auto &cms_system = Inkscape::Colors::CMS::System::get(); + if (auto profile = cms_system.getProfile(active)) { + prefs->setString("/options/displayprofile/uri", profile->getPath()); } } } @@ -2286,12 +2278,10 @@ static void profileComboChanged( Gtk::ComboBoxText* combo ) static void proofComboChanged( Gtk::ComboBoxText* combo ) { Glib::ustring active = combo->get_active_text(); - auto cms_system = Inkscape::CMSSystem::get(); - Glib::ustring path = cms_system->get_path_for_profile(active); - - if ( !path.empty() ) { + auto &cms_system = Inkscape::Colors::CMS::System::get(); + if (auto profile = cms_system.getProfile(active)) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setString("/options/softproof/uri", path); + prefs->setString("/options/softproof/uri", profile->getPath()); } } @@ -2442,7 +2432,7 @@ void InkscapePreferences::initPageIO() _page_cms.add_group_header( _("Display adjustment")); Glib::ustring tmpStr; - for (auto const &path : Inkscape::CMSSystem::get_directory_paths()) { + for (auto const &path : Inkscape::Colors::CMS::System::get().getDirectoryPaths()) { tmpStr += "\n"; tmpStr += path.first; } @@ -2491,16 +2481,14 @@ void InkscapePreferences::initPageIO() _("Enables black point compensation"), false); { - auto cms_system = Inkscape::CMSSystem::get(); - std::vector names = cms_system->get_monitor_profile_names(); - Glib::ustring current = prefs->getString( "/options/displayprofile/uri" ); + auto &cms_system = Inkscape::Colors::CMS::System::get(); + std::string current = prefs->getString( "/options/displayprofile/uri" ); gint index = 0; _cms_display_profile.append(_("")); index++; - for (auto const &name : names) { - _cms_display_profile.append( name ); - Glib::ustring path = cms_system->get_path_for_profile(name); - if ( !path.empty() && path == current ) { + for (auto profile : cms_system.getDisplayProfiles()) { + _cms_display_profile.append(profile->getName()); + if (profile->getPath() == current) { _cms_display_profile.set_active(index); } index++; @@ -2509,13 +2497,11 @@ void InkscapePreferences::initPageIO() _cms_display_profile.set_active(0); } - names = cms_system->get_softproof_profile_names(); current = prefs->getString("/options/softproof/uri"); index = 0; - for (auto const &name : names) { - _cms_proof_profile.append( name ); - Glib::ustring path = cms_system->get_path_for_profile(name); - if ( !path.empty() && path == current ) { + for (auto profile : cms_system.getOutputProfiles()) { + _cms_proof_profile.append(profile->getName()); + if (profile->getPath() == current) { _cms_proof_profile.set_active(index); } index++; diff --git a/src/ui/dialog/layer-properties.cpp b/src/ui/dialog/layer-properties.cpp index 8f2b6069c13fa8e66586a50216946d9776ea3610..b838cfbb31d369ceec622a9827c042e1c54c3136 100644 --- a/src/ui/dialog/layer-properties.cpp +++ b/src/ui/dialog/layer-properties.cpp @@ -183,8 +183,7 @@ void LayerPropertiesDialog::_doRename() SPGroup *activeLayer = layman.currentLayer(); if (activeLayer && !activeLayer->isHighlightSet()) { - guint32 color = activeLayer->highlight_color(); - activeLayer->setHighlight(color); + activeLayer->setHighlight(activeLayer->highlight_color()); } layman.renameLayer(layman.currentLayer(), name.c_str(), false); diff --git a/src/ui/dialog/livepatheffect-editor.cpp b/src/ui/dialog/livepatheffect-editor.cpp index 5d7678a174e4c7734262c98a27b418b2254781c8..d4feb6a703a1b1d4c513afb1a2c7701286f9cf39 100644 --- a/src/ui/dialog/livepatheffect-editor.cpp +++ b/src/ui/dialog/livepatheffect-editor.cpp @@ -481,6 +481,7 @@ LivePathEffectEditor::selection_info() SPItem * selected = nullptr; _LPESelectionInfo.set_visible(false); if (selection && (selected = selection->singleItem()) ) { + auto highlight = selected->highlight_color().toRGBA(); if (is(selected) || is(selected)) { _LPESelectionInfo.set_text(_("Text objects do not support Live Path Effects")); _LPESelectionInfo.set_visible(true); @@ -489,7 +490,7 @@ LivePathEffectEditor::selection_info() auto const selectbutton = Gtk::make_managed(); auto const boxc = Gtk::make_managed(); auto const lbl = Gtk::make_managed(labeltext); - auto const type = get_shape_image("group", selected->highlight_color()); + auto const type = get_shape_image("group", highlight); UI::pack_start(*boxc, *type, false, false); UI::pack_start(*boxc, *lbl, false, false); type->set_margin_start(4); @@ -504,7 +505,7 @@ LivePathEffectEditor::selection_info() auto const selectbutton2 = Gtk::make_managed(); auto const boxc2 = Gtk::make_managed(); auto const lbl2 = Gtk::make_managed(labeltext2); - auto const type2 = get_shape_image("clone", selected->highlight_color()); + auto const type2 = get_shape_image("clone", highlight); UI::pack_start(*boxc2, *type2, false, false); UI::pack_start(*boxc2, *lbl2, false, false); type2->set_margin_start(4); @@ -523,7 +524,7 @@ LivePathEffectEditor::selection_info() auto const boxc = Gtk::make_managed(); auto const lbl = Gtk::make_managed(labeltext); lbl->set_ellipsize(Pango::EllipsizeMode::END); - auto const type = get_shape_image(selected->typeName(), selected->highlight_color()); + auto const type = get_shape_image(selected->typeName(), highlight); UI::pack_start(*boxc, *type, false, false); UI::pack_start(*boxc, *lbl, false, false); _LPECurrentItem.append(*boxc); @@ -546,7 +547,7 @@ LivePathEffectEditor::selection_info() auto const selectbutton = Gtk::make_managed(); auto const boxc = Gtk::make_managed(); auto const lbl = Gtk::make_managed(labeltext); - auto const type = get_shape_image(selected->typeName(), selected->highlight_color()); + auto const type = get_shape_image(selected->typeName(), highlight); UI::pack_start(*boxc, *type, false, false); UI::pack_start(*boxc, *lbl, false, false); type->set_margin_start(4); diff --git a/src/ui/dialog/object-properties.cpp b/src/ui/dialog/object-properties.cpp index b1ab365afb1b921a9fabbf95b91188bc840d05b7..f70af2ce65131e94a61b5cb811ed137e870677f6 100644 --- a/src/ui/dialog/object-properties.cpp +++ b/src/ui/dialog/object-properties.cpp @@ -65,7 +65,7 @@ ObjectProperties::ObjectProperties() , _label_title(_("_Title:"), true) , _label_dpi(_("_DPI SVG:"), true) , _label_color(_("Highlight Color:"), true) - , _highlight_color(_("Highlight Color"), "", 0xff0000ff, true) + , _highlight_color(_("Highlight Color"), "", Colors::Color(0xff0000ff), true) , _cb_hide(_("_Hide"), true) , _cb_lock(_("L_ock"), true) , _cb_aspect_ratio(_("Preserve Ratio"), true) @@ -334,7 +334,7 @@ void ObjectProperties::update_entries() _cb_aspect_ratio.set_active(item && g_strcmp0(item->getAttribute("preserveAspectRatio"), "none") != 0); _cb_lock.set_active(item && item->isLocked()); /* Sensitive */ _cb_hide.set_active(item && item->isExplicitlyHidden()); /* Hidden */ - _highlight_color.setRgba32(item ? item->highlight_color() : 0x0); + _highlight_color.setColor(item ? item->highlight_color() : Colors::Color(0x000000ff)); _highlight_color.closeWindow(); // hide aspect ratio checkbox for an image element, images have their own panel in object attributes; // apart from only , , and support this attribute, but we don't handle them @@ -471,13 +471,13 @@ void ObjectProperties::_labelChanged() _blocked = false; } -void ObjectProperties::_highlightChanged(guint rgba) +void ObjectProperties::_highlightChanged(Colors::Color const &color) { if (_blocked) return; if (auto item = getSelection()->singleItem()) { - item->setHighlight(rgba); + item->setHighlight(color); DocumentUndo::done(getDocument(), _("Set item highlight color"), INKSCAPE_ICON("dialog-object-properties")); } } diff --git a/src/ui/dialog/object-properties.h b/src/ui/dialog/object-properties.h index 82b9b3933e3715a4ae3d62052606d82534a96df9..830394972ff428fc36fcafdf3b9f8ed780db7978 100644 --- a/src/ui/dialog/object-properties.h +++ b/src/ui/dialog/object-properties.h @@ -105,7 +105,7 @@ private: void _labelChanged(); // Callback for highlight color - void _highlightChanged(guint rgba); + void _highlightChanged(Colors::Color const &color); /// Callback for checkbox Lock. void _sensitivityToggled(); diff --git a/src/ui/dialog/objects.cpp b/src/ui/dialog/objects.cpp index 93df690002fcfbedb6cd5b8f1c35c3a6394afc55..72800e5713d61a40c3001b732773632e317268f5 100644 --- a/src/ui/dialog/objects.cpp +++ b/src/ui/dialog/objects.cpp @@ -353,7 +353,7 @@ void ObjectWatcher::updateRowHighlight() { if (auto item = cast(panel->getObject(node))) { auto row = *panel->_store->get_iter(row_ref.get_path()); - auto new_color = item->highlight_color(); + auto new_color = item->highlight_color().toRGBA(); if (new_color != row[panel->_model->_colIconColor]) { row[panel->_model->_colIconColor] = new_color; updateRowBg(new_color); @@ -695,7 +695,7 @@ ObjectsPanel::ObjectsPanel() , _layer(nullptr) , _is_editing(false) , _page(Gtk::Orientation::VERTICAL) - , _color_picker(_("Highlight color"), "", 0, true) + , _color_picker(_("Highlight color"), "", Colors::Color(0x000000ff), true) , _builder(create_builder("dialog-objects.glade")) , _settings_menu(get_widget(_builder, "settings-menu")) , _object_menu(get_widget(_builder, "object-menu")) @@ -905,14 +905,14 @@ ObjectsPanel::ObjectsPanel() _clicked_item_row = *_store->get_iter(path); if (auto item = getItem(_clicked_item_row)) { // find object's color - _color_picker.setRgba32(item->highlight_color()); + _color_picker.setColor(item->highlight_color()); _color_picker.open(); } }); - _color_picker.connectChanged([this](guint rgba) { + _color_picker.connectChanged([this](Colors::Color const &color) { if (auto item = getItem(_clicked_item_row)) { - item->setHighlight(rgba); + item->setHighlight(color); DocumentUndo::maybeDone(getDocument(), "highlight-color", _("Set item highlight color"), INKSCAPE_ICON("dialog-object-properties")); } }); @@ -1706,8 +1706,7 @@ void ObjectsPanel::_handleEdited(const Glib::ustring& path, const Glib::ustring& if (!new_text.empty() && (!item->label() || new_text != item->label())) { auto obj = cast(item); if (obj && obj->layerMode() == SPGroup::LAYER && !obj->isHighlightSet()) { - guint32 color = obj->highlight_color(); - obj->setHighlight(color); + obj->setHighlight(obj->highlight_color()); } item->setLabel(new_text.c_str()); DocumentUndo::done(getDocument(), _("Rename object"), ""); diff --git a/src/ui/dialog/print.cpp b/src/ui/dialog/print.cpp index 084b218d3240e47859a84404c60968331fd6340a..d8322c649f9aacda05af5a630ec898917566bcc6 100644 --- a/src/ui/dialog/print.cpp +++ b/src/ui/dialog/print.cpp @@ -26,10 +26,11 @@ #include "document.h" #include "object/sp-page.h" +#include "colors/manager.h" +#include "colors/color.h" #include "util/units.h" #include "helper/png-write.h" #include "page-manager.h" -#include "svg/svg-color.h" #include @@ -181,7 +182,10 @@ void Print::draw_page(const Glib::RefPtr& context, int page_n guint32 bgcolor = 0x00000000; Inkscape::XML::Node *nv = _workaround._doc->getReprNamedView(); if (nv && nv->attribute("pagecolor")){ - bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00); + if (auto c = Colors::Color::parse(nv->attribute("pagecolor"))) { + // TODO allow page color to be any color space, not just RGB + bgcolor = c->toRGBA(); + } } if (nv && nv->attribute("inkscape:pageopacity")){ double opacity = nv->getAttributeDouble("inkscape:pageopacity", 1.0); diff --git a/src/ui/dialog/startup.cpp b/src/ui/dialog/startup.cpp index d62993b34683b4b312499331edd7ec2a3d81228b..26e7899ae69d514781ce61034e49c3f2305ad231 100644 --- a/src/ui/dialog/startup.cpp +++ b/src/ui/dialog/startup.cpp @@ -34,8 +34,6 @@ #include #include -#include "color.h" -#include "color-rgba.h" #include "inkscape-application.h" #include "inkscape.h" #include "inkscape-version.h" @@ -136,19 +134,6 @@ class ThemeCols: public Gtk::TreeModel::ColumnRecord { Gtk::TreeModelColumn enabled; }; -/** - * Color is store as a string in the form #RRGGBBAA, '0' means "unset" - * - * @param color - The string color from glade. - */ -unsigned int get_color_value(const Glib::ustring color) -{ - Gdk::RGBA gdk_color = Gdk::RGBA(color); - ColorRGBA sp_color(gdk_color.get_red(), gdk_color.get_green(), - gdk_color.get_blue(), gdk_color.get_alpha()); - return sp_color.getIntValue(); -} - using Inkscape::UI::Widget::TemplateList; StartScreen::StartScreen() @@ -622,21 +607,18 @@ StartScreen::canvas_changed() prefs->setString("/options/boot/canvas", row[cols.id]); Gdk::RGBA gdk_color = Gdk::RGBA(row[cols.pagecolor]); - SPColor sp_color(gdk_color.get_red(), gdk_color.get_green(), gdk_color.get_blue()); - prefs->setString("/template/base/pagecolor", sp_color.toString()); + prefs->setString("/template/base/pagecolor", gdk_to_css_color(gdk_color)); prefs->setDouble("/template/base/pageopacity", gdk_color.get_alpha()); Gdk::RGBA gdk_border = Gdk::RGBA(row[cols.bordercolor]); - SPColor sp_border(gdk_border.get_red(), gdk_border.get_green(), gdk_border.get_blue()); - prefs->setString("/template/base/bordercolor", sp_border.toString()); + prefs->setString("/template/base/bordercolor", gdk_to_css_color(gdk_border)); prefs->setDouble("/template/base/borderopacity", gdk_border.get_alpha()); prefs->setBool("/template/base/pagecheckerboard", row[cols.checkered]); prefs->setInt("/template/base/pageshadow", row[cols.shadow] ? 2 : 0); Gdk::RGBA gdk_desk = Gdk::RGBA(row[cols.deskcolor]); - SPColor sp_desk(gdk_desk.get_red(), gdk_desk.get_green(), gdk_desk.get_blue()); - prefs->setString("/template/base/deskcolor", sp_desk.toString()); + prefs->setString("/template/base/deskcolor", gdk_to_css_color(gdk_desk)); } catch(int e) { g_warning("Couldn't find canvas value."); } diff --git a/src/ui/dialog/styledialog.cpp b/src/ui/dialog/styledialog.cpp index 0bd9d72b57bc3bb6f96384beb0c90e1395803f06..821056927e0bc3330a754af1281f43694d5d7809 100644 --- a/src/ui/dialog/styledialog.cpp +++ b/src/ui/dialog/styledialog.cpp @@ -42,6 +42,8 @@ #include #include +#include "colors/manager.h" +#include "colors/color.h" #include "attribute-rel-svg.h" #include "attributes.h" #include "document.h" @@ -54,7 +56,6 @@ #include "io/resource.h" #include "object/sp-object.h" -#include "svg/svg-color.h" #include "ui/builder-utils.h" #include "ui/controller.h" #include "ui/icon-loader.h" @@ -735,11 +736,10 @@ void StyleDialog::readStyleElement() break; } } - guint32 r1 = 0; // if there's no color, return black - r1 = sp_svg_read_color(value.c_str(), r1); - guint32 r2 = 0; // if there's no color, return black - r2 = sp_svg_read_color(val.c_str(), r2); - if ((r1 == 0 || r1 != r2) && value != val || attr_prop.count(name)) { + + auto r1 = Colors::Color::parse(value); + auto r2 = Colors::Color::parse(val); + if (((!r1 || *r1 != *r2) && value != val) || attr_prop.count(name)) { row[_mColumns._colStrike] = true; } else { row[_mColumns._colOwner] = _("Current value"); diff --git a/src/ui/dialog/swatches.cpp b/src/ui/dialog/swatches.cpp index a8cd719c3856f5d82703f374a8d91ff2871dae37..1aec3aa09cf5729fb95b595aaa2bc8c54ec1992a 100644 --- a/src/ui/dialog/swatches.cpp +++ b/src/ui/dialog/swatches.cpp @@ -57,7 +57,14 @@ #include "ui/widget/color-palette-preview.h" #include "ui/widget/popover-menu-item.h" #include "util/variant-visitor.h" -#include "widgets/paintdef.h" + +namespace Inkscape::Colors { + std::size_t hash_value(Color const& b) + { + boost::hash hasher; + return hasher(b.toRGBA()); + } +} namespace Inkscape::UI::Dialog { @@ -228,7 +235,7 @@ void SwatchesPanel::set_palette(const Glib::ustring& id) { select_palette(id); } -const PaletteFileData* SwatchesPanel::get_palette(const Glib::ustring& id) { +const PaletteFileData *SwatchesPanel::get_palette(const Glib::ustring& id) { if (auto p = GlobalPalettes::get().find_palette(id)) return p; if (_loaded_palette.id == id) return &_loaded_palette; @@ -375,19 +382,6 @@ bool SwatchesPanel::update_isswatch() return modified; } -static auto spcolor_to_rgb(SPColor const &color) -{ - float rgbf[3]; - color.get_rgb_floatv(rgbf); - - std::array rgb; - for (int i = 0; i < 3; i++) { - rgb[i] = SP_COLOR_F_TO_U(rgbf[i]); - }; - - return rgb; -} - void SwatchesPanel::update_fillstroke_indicators() { auto doc = getDocument(); @@ -413,7 +407,7 @@ void SwatchesPanel::update_fillstroke_indicators() if (attr->isNone()) { return std::monostate{}; } else if (attr->isColor()) { - return spcolor_to_rgb(attr->value.color); + return attr->getColor(); } else if (attr->isPaintserver()) { if (auto grad = cast(fill ? style.getFillPaintServer() : style.getStrokePaintServer())) { if (grad->isSwatch()) { @@ -460,9 +454,9 @@ void SwatchesPanel::update_fillstroke_indicators() palette.id = p.id; for (auto const &c : p.colors) { std::visit(VariantVisitor { - [&](const PaletteFileData::Color& c) { - auto [r, g, b] = c.rgb; - palette.colors.push_back({r / 255.0, g / 255.0, b / 255.0}); + [&](const Colors::Color& c) { + auto rgb = *c.converted(Colors::Space::Type::RGB); + palette.colors.push_back({rgb[0], rgb[1], rgb[2]}); }, [](const PaletteFileData::SpacerItem&) {}, [](const PaletteFileData::GroupStart&) {} @@ -510,7 +504,7 @@ void SwatchesPanel::rebuild() current_stroke.clear(); // Add the "remove-color" color. - auto const w = Gtk::make_managed(PaintDef(), this); + auto const w = Gtk::make_managed(this); w->set_pinned_pref(_prefs_path); palette.emplace_back(w); widgetmap.emplace(std::monostate{}, w); @@ -527,10 +521,10 @@ void SwatchesPanel::rebuild() [](const PaletteFileData::GroupStart& g) { return Gtk::make_managed(g.name); }, - [=, this](const PaletteFileData::Color& c) { - auto w = Gtk::make_managed(PaintDef(c.rgb, c.name, c.definition), dialog); + [=, this](const Colors::Color& c) { + auto w = Gtk::make_managed(c, dialog); w->set_pinned_pref(_prefs_path); - widgetmap.emplace(c.rgb, w); + widgetmap.emplace(c, w); return w; }, }, c); diff --git a/src/ui/dialog/swatches.h b/src/ui/dialog/swatches.h index dba066f837df3915bcde5418868280586ee5b2a1..8f81da54d8b3f5fc4458565077710ba7a1afe11d 100644 --- a/src/ui/dialog/swatches.h +++ b/src/ui/dialog/swatches.h @@ -23,6 +23,7 @@ #include #include +#include "colors/color.h" #include "preferences.h" // PrefObserver #include "ui/dialog/dialog-base.h" #include "ui/dialog/global-palettes.h" @@ -38,6 +39,11 @@ class ToggleButton; class SPGradient; +// Allow boost to map with colors +namespace Inkscape::Colors { + std::size_t hash_value(Color const& b); +} // namespace Inkscape::Colors + namespace Inkscape::UI { namespace Widget { @@ -90,7 +96,7 @@ private: Glib::ustring _current_palette_id; void set_palette(const Glib::ustring& id); void select_palette(const Glib::ustring& id); - const PaletteFileData* get_palette(const Glib::ustring& id); + const PaletteFileData *get_palette(const Glib::ustring& id); // Asynchronous update mechanism. sigc::connection conn_gradients; @@ -108,7 +114,7 @@ private: // A map from colors to their respective widgets. Used to quickly find the widgets corresponding // to the current fill/stroke color, in order to update their fill/stroke indicators. - using ColorKey = std::variant, SPGradient *>; + using ColorKey = std::variant; boost::unordered_multimap widgetmap; // need boost for array hash std::vector current_fill; std::vector current_stroke; diff --git a/src/ui/drag-and-drop.cpp b/src/ui/drag-and-drop.cpp index 0ded741b5715ef667587bc084aaa5777d5b30909..149973df93545629b14d422457be56d10f59604d 100644 --- a/src/ui/drag-and-drop.cpp +++ b/src/ui/drag-and-drop.cpp @@ -13,6 +13,7 @@ #include "drag-and-drop.h" +#include #include #include #include @@ -29,6 +30,7 @@ #include "style.h" #include "layer-manager.h" +#include "colors/dragndrop.h" #include "extension/find_extension_by_mime.h" #include "object/sp-shape.h" @@ -37,16 +39,12 @@ #include "path/path-util.h" -#include "svg/svg-color.h" // write color - #include "ui/clipboard.h" #include "ui/interface.h" #include "ui/tools/tool-base.h" #include "ui/widget/canvas.h" // Target, canvas to world transform. #include "ui/widget/desktop-widget.h" -#include "widgets/paintdef.h" - using Inkscape::DocumentUndo; namespace { @@ -196,19 +194,19 @@ GValue from_bytes(Glib::RefPtr &&bytes, char const *) } template <> -GValue from_bytes(Glib::RefPtr &&bytes, char const *mime_type) +GValue from_bytes(Glib::RefPtr &&bytes, char const *mime_type) { - PaintDef result; - if (!result.fromMIMEData(mime_type, get_span(bytes))) { - throw Glib::Error(G_FILE_ERROR, 0, "Failed to parse colour"); + try { + return make_value(Colors::fromMIMEData(get_span(bytes), mime_type)); + } catch (Colors::ColorError const &c) { + throw Glib::Error(G_FILE_ERROR, 0, c.what()); } - return make_value(std::move(result)); } template <> -Glib::RefPtr to_bytes(PaintDef const &paintdef, char const *mime_type) +Glib::RefPtr to_bytes(Colors::Paint const &paint, char const *mime_type) { - return make_bytes(paintdef.getMIMEData(mime_type)); + return make_bytes(getMIMEData(paint, mime_type)); } template <> @@ -224,18 +222,18 @@ std::vector const &get_drop_types() register_deserializer(mime_type); } - for (auto mime_type : {mimeOSWB_COLOR, mimeX_COLOR}) { - register_deserializer(mime_type); + for (auto mime_type : {Colors::mimeOSWB_COLOR, Colors::mimeX_COLOR}) { + register_deserializer(mime_type); } - for (auto mime_type : {mimeOSWB_COLOR, mimeX_COLOR, mimeTEXT}) { - register_serializer(mime_type); + for (auto mime_type : {Colors::mimeOSWB_COLOR, Colors::mimeX_COLOR, Colors::mimeTEXT}) { + register_serializer(mime_type); } register_serializer("text/plain"); return { - Glib::Value::value_type(), + Glib::Value::value_type(), Glib::Value::value_type(), GDK_TYPE_FILE_LIST, Glib::Value::value_type(), @@ -257,16 +255,17 @@ bool on_drop(Glib::ValueBase const &value, double x, double y, SPDesktopWidget * auto const world_pos = canvas->canvas_to_world(canvas_pos); auto const dt_pos = desktop->w2d(world_pos); - if (auto const paintdef = get(value)) { + if (auto const ptr = get(value)) { + auto paint = *ptr; auto const item = desktop->getItemAtPoint(world_pos, true); if (!item) { return false; } - auto find_gradient = [&] () -> SPGradient * { + auto find_gradient = [&] (Colors::Color const &color) -> SPGradient * { for (auto obj : doc->getResourceList("gradient")) { auto const grad = cast_unsafe(obj); - if (grad->hasStops() && grad->getId() == paintdef->get_description()) { + if (grad->hasStops() && grad->getId() == color.getName()) { return grad; } } @@ -274,16 +273,14 @@ bool on_drop(Glib::ValueBase const &value, double x, double y, SPDesktopWidget * }; std::string colorspec; - if (paintdef->get_type() == PaintDef::NONE) { + if (std::holds_alternative(paint)) { colorspec = "none"; } else { - if (auto const grad = find_gradient()) { + auto &color = std::get(paint); + if (auto const grad = find_gradient(color)) { colorspec = std::string{"url(#"} + grad->getId() + ")"; } else { - auto const [r, g, b] = paintdef->get_rgb(); - colorspec.resize(63); - sp_svg_write_color(colorspec.data(), colorspec.size() + 1, SP_RGBA32_U_COMPOSE(r, g, b, 0xff)); - colorspec.resize(std::strlen(colorspec.c_str())); + colorspec.resize(std::strlen(color.toString().c_str())); } } @@ -324,7 +321,7 @@ bool on_drop(Glib::ValueBase const &value, double x, double y, SPDesktopWidget * } auto const css = sp_repr_css_attr_new(); - sp_repr_css_set_property(css, fillnotstroke ? "fill" : "stroke", colorspec.c_str()); + sp_repr_css_set_property_string(css, fillnotstroke ? "fill" : "stroke", colorspec); sp_desktop_apply_css_recursive(item, css, true); sp_repr_css_attr_unref(css); diff --git a/src/ui/icon-loader.cpp b/src/ui/icon-loader.cpp index 1a327af8acfcc1e806f50a05413d2dfaf36e7bd2..5bd73400f79928846c9516d44bbac18a63c4073e 100644 --- a/src/ui/icon-loader.cpp +++ b/src/ui/icon-loader.cpp @@ -21,6 +21,7 @@ #include #include +#include "colors/color.h" #include "desktop.h" #include "inkscape.h" @@ -57,18 +58,19 @@ namespace Inkscape::UI { auto &color_class = color_classes[rgba_color]; if (!color_class.empty()) return color_class; + auto color = Colors::Color(rgba_color); + // The CSS class is .icon-color-RRGGBBAA - std::string hex(9, '\0'); - std::snprintf(hex.data(), 9, "%08X", rgba_color); - color_class = Glib::ustring::compose("icon-color-%1", hex); - // GTK CSS does not support #RRGGBBAA, so we split it - hex.resize(6); - auto const opacity = (rgba_color & 0xFF) / 255.0; + auto rgba_str = color.toString(); + rgba_str.erase(0, 1); + color_class = Glib::ustring::compose("icon-color-%1", rgba_str); + // Add a persistent CSS provider for that class+color auto const css_provider = Gtk::CssProvider::create(); auto const data = Glib::ustring::compose( - ".symbolic .%1, .regular .%1 { -gtk-icon-style: symbolic; color: #%2; opacity: %3; }", - color_class, hex, opacity); + ".symbolic .%1, .regular .%1 { -gtk-icon-style: symbolic; color: %2; opacity: %3; }", + color_class, color.toString(false), color.getOpacity()); + css_provider->load_from_data(data); // Add it with the needed priority = higher than themes.cpp _colorizeprovider static constexpr auto priority = GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 1; diff --git a/src/ui/selected-color.cpp b/src/ui/selected-color.cpp deleted file mode 100644 index b43976f0cccf68f132f60b604442cd8f0c31e661..0000000000000000000000000000000000000000 --- a/src/ui/selected-color.cpp +++ /dev/null @@ -1,155 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file - * Color selected in color selector widget. - * This file was created during the refactoring of SPColorSelector - *//* - * Authors: - * bulia byak - * Jon A. Cruz - * Tomasz Boczkowski - * - * Copyright (C) 2014 Authors - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ - -#include "selected-color.h" - -#include - -#include - -namespace Inkscape { -namespace UI { - -double const SelectedColor::_EPSILON = 1e-4; - -SelectedColor::SelectedColor() - : _color(0) - , _alpha(1.0) - , _held(false) - , _virgin(true) - , _updating(false) -{ - -} - -SelectedColor::~SelectedColor() = default; - -void SelectedColor::setColor(SPColor const &color) -{ - setColorAlpha( color, _alpha); -} - -SPColor SelectedColor::color() const -{ - return _color; -} - -void SelectedColor::setAlpha(gfloat alpha) -{ - g_return_if_fail( ( 0.0 <= alpha ) && ( alpha <= 1.0 ) ); - setColorAlpha( _color, alpha); -} - -gfloat SelectedColor::alpha() const -{ - return _alpha; -} - -void SelectedColor::setValue(guint32 value) -{ - SPColor color(value); - gfloat alpha = SP_RGBA32_A_F(value); - setColorAlpha(color, alpha); -} - -guint32 SelectedColor::value() const -{ - return color().toRGBA32(_alpha); -} - -void SelectedColor::setColorAlpha(SPColor const &color, gfloat alpha, bool emit_signal) -{ -#ifdef DUMP_CHANGE_INFO - g_message("SelectedColor::setColorAlpha( this=%p, %f, %f, %f, %s, %f, %s)", this, color.v.c[0], color.v.c[1], color.v.c[2], (color.icc?color.icc->colorProfile.c_str():""), alpha, (emit_signal?"YES":"no")); -#endif - g_return_if_fail( ( 0.0 <= alpha ) && ( alpha <= 1.0 ) ); - - if (_updating) { - return; - } - -#ifdef DUMP_CHANGE_INFO - g_message("---- SelectedColor::setColorAlpha virgin:%s !close:%s alpha is:%s", - (_virgin?"YES":"no"), - (!color.isClose( _color, _EPSILON )?"YES":"no"), - ((fabs((_alpha) - (alpha)) >= _EPSILON )?"YES":"no") - ); -#endif - - if ( _virgin || !color.isClose( _color, _EPSILON ) || - (fabs((_alpha) - (alpha)) >= _EPSILON )) { - - _virgin = false; - - _color = color; - _alpha = alpha; - - if (emit_signal) - { - _updating = true; - if (_held) { - signal_dragged.emit(); - } else { - signal_changed.emit(); - } - _updating = false; - } - -#ifdef DUMP_CHANGE_INFO - } else { - g_message("++++ SelectedColor::setColorAlpha color:%08x ==> _color:%08X isClose:%s", color.toRGBA32(alpha), _color.toRGBA32(_alpha), - (color.isClose( _color, _EPSILON )?"YES":"no")); -#endif - } -} - -void SelectedColor::colorAlpha(SPColor &color, gfloat &alpha) const { - color = _color; - alpha = _alpha; -} - -void SelectedColor::setHeld(bool held) { - if (_updating) { - return; - } - bool grabbed = held && !_held; - bool released = !held && _held; - - _held = held; - - _updating = true; - if (grabbed) { - signal_grabbed.emit(); - } - - if (released) { - signal_released.emit(); - // signal_changed.emit(); // TODO: signal_changed isn't emitted after dragging! - } - _updating = false; -} - -} -} - -/* - 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 : diff --git a/src/ui/selected-color.h b/src/ui/selected-color.h deleted file mode 100644 index 6010b760458b625d32b3ff37b384a990bc57c3e0..0000000000000000000000000000000000000000 --- a/src/ui/selected-color.h +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file - * Color selected in color selector widget. - * This file was created during the refactoring of SPColorSelector - *//* - * Authors: - * bulia byak - * Jon A. Cruz - * Tomasz Boczkowski - * - * Copyright (C) 2014 Authors - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ -#ifndef SEEN_SELECTED_COLOR -#define SEEN_SELECTED_COLOR - -#include -#include -#include - -#include "color.h" - -namespace Gtk -{ - class Widget; -} - -namespace Inkscape { -namespace UI { - -class SelectedColor { -public: - SelectedColor(); - virtual ~SelectedColor(); - - // By default, disallow copy constructor and assignment operator - SelectedColor(SelectedColor const &obj) = delete; - SelectedColor& operator=(SelectedColor const &obj) = delete; - - void setColor(SPColor const &color); - SPColor color() const; - - void setAlpha(gfloat alpha); - gfloat alpha() const; - - void setValue(guint32 value); - guint32 value() const; - - void setColorAlpha(SPColor const &color, gfloat alpha, bool emit_signal = true); - void colorAlpha(SPColor &color, gfloat &alpha) const; - void emitIccChanged() { signal_icc_changed.emit(); } - - void setHeld(bool held); - - sigc::signal signal_grabbed; - sigc::signal signal_dragged; - sigc::signal signal_released; - sigc::signal signal_changed; - sigc::signal signal_icc_changed; - -private: - SPColor _color; - /** - * Color alpha value guaranteed to be in [0, 1]. - */ - gfloat _alpha; - - bool _held; - /** - * This flag is true if no color is set yet - */ - bool _virgin; - - bool _updating; - - static double const _EPSILON; -}; - -class ColorSelectorFactory { -public: - virtual ~ColorSelectorFactory() = default; - - virtual Gtk::Widget* createWidget(SelectedColor &color, bool no_alpha) const = 0; - virtual Glib::ustring modeName() const = 0; -}; - -} -} - -#endif -/* - 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 : diff --git a/src/ui/svg-renderer.cpp b/src/ui/svg-renderer.cpp index d851f7a4de12d7420816f502743f308c1123251f..a62acb832c8f19b878654015c601913a63f87e58 100644 --- a/src/ui/svg-renderer.cpp +++ b/src/ui/svg-renderer.cpp @@ -33,33 +33,6 @@ namespace Inkscape { -Glib::ustring rgba_to_css_color(double r, double g, double b) { - char buffer[16]; - safeprintf(buffer, "#%02x%02x%02x", - static_cast(r * 0xff + 0.5), - static_cast(g * 0xff + 0.5), - static_cast(b * 0xff + 0.5) - ); - return Glib::ustring(buffer); -} - -Glib::ustring rgba_to_css_color(const Gdk::RGBA& color) { - return rgba_to_css_color(color.get_red(), color.get_green(), color.get_blue()); -} - -Glib::ustring rgba_to_css_color(const SPColor& color) { - float rgb[3]; - color.get_rgb_floatv(rgb); - return rgba_to_css_color(rgb[0], rgb[1], rgb[2]); -} - -Glib::ustring double_to_css_value(double value) { - char buffer[32]; - // arbitrarily chosen precision - safeprintf(buffer, "%.4f", value); - return Glib::ustring(buffer); -} - template static auto ensure_nonnull(T &&t, char const *message) { diff --git a/src/ui/svg-renderer.h b/src/ui/svg-renderer.h index f7c0e352ea9597cc794931f22b41f81a0a8892d4..3a2d391099c28653d08d4835f0aa71e7383ee88e 100644 --- a/src/ui/svg-renderer.h +++ b/src/ui/svg-renderer.h @@ -10,7 +10,7 @@ #include #include -#include "color.h" +#include "colors/color.h" class SPDocument; class SPRoot; @@ -25,19 +25,10 @@ class ustring; namespace Gdk { class Pixbuf; -class RGBA; } // namespace Gdk namespace Inkscape { -// utilities for set_style: -// Gdk color to CSS rgb (no alpha) -Glib::ustring rgba_to_css_color(const Gdk::RGBA& color); -Glib::ustring rgba_to_css_color(const SPColor& color); -Glib::ustring rgba_to_css_color(double r, double g, double b); -// double to low precision string -Glib::ustring double_to_css_value(double value); - class Pixbuf; class svg_renderer diff --git a/src/ui/syntax.cpp b/src/ui/syntax.cpp index b38f902ad227baff3530c8cf2abf1eb4ca2c72a6..205c830d8a33ec553844e72b5b0edeaa2ebb48be 100644 --- a/src/ui/syntax.cpp +++ b/src/ui/syntax.cpp @@ -18,7 +18,6 @@ #include #include -#include "color.h" #include "config.h" #include "io/resource.h" #include "object/sp-factory.h" diff --git a/src/ui/syntax.h b/src/ui/syntax.h index 35917351e1bb9962085d4cefe77cbcb727b3ceac..c9cd280be13eda3ed2e820af80bf68721dafd7f8 100644 --- a/src/ui/syntax.h +++ b/src/ui/syntax.h @@ -19,8 +19,6 @@ #include #include -#include "color.h" - namespace Inkscape::UI::Syntax { /** The style of a single element in a (Pango markup)-enabled widget. */ diff --git a/src/ui/themes.cpp b/src/ui/themes.cpp index d1182910d2a1279b16f71f645c17840deb3f5802..1a3dc0681cdb8bc900e520036c1725e6613b3235 100644 --- a/src/ui/themes.cpp +++ b/src/ui/themes.cpp @@ -36,10 +36,10 @@ #include "desktop.h" #include "inkscape.h" #include "preferences.h" +#include "colors/utils.h" #include "io/resource.h" #include "object/sp-item-group.h" // set_default_highlight_colors #include "svg/css-ostringstream.h" -#include "svg/svg-color.h" #include "ui/dialog/dialog-manager.h" #include "ui/dialog/dialog-window.h" #include "ui/util.h" @@ -149,11 +149,6 @@ Glib::ustring ThemeContext::get_symbolic_colors() { Glib::ustring css_str; - gchar colornamed[64]; - gchar colornamedsuccess[64]; - gchar colornamedwarning[64]; - gchar colornamederror[64]; - gchar colornamed_inverse[64]; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Glib::ustring themeiconname = prefs->getString("/theme/iconTheme", prefs->getString("/theme/defaultIconTheme", "")); guint32 colorsetbase = 0x2E3436ff; @@ -165,15 +160,10 @@ ThemeContext::get_symbolic_colors() colorsetsuccess = prefs->getUInt("/theme/" + themeiconname + "/symbolicSuccessColor", colorsetsuccess); colorsetwarning = prefs->getUInt("/theme/" + themeiconname + "/symbolicWarningColor", colorsetwarning); colorseterror = prefs->getUInt("/theme/" + themeiconname + "/symbolicErrorColor", colorseterror); - sp_svg_write_color(colornamed, sizeof(colornamed), colorsetbase); - sp_svg_write_color(colornamedsuccess, sizeof(colornamedsuccess), colorsetsuccess); - sp_svg_write_color(colornamedwarning, sizeof(colornamedwarning), colorsetwarning); - sp_svg_write_color(colornamederror, sizeof(colornamederror), colorseterror); colorsetbase_inverse = colorsetbase ^ 0xffffff00; - sp_svg_write_color(colornamed_inverse, sizeof(colornamed_inverse), colorsetbase_inverse); - css_str += "@define-color warning_color " + Glib::ustring(colornamedwarning) + ";\n"; - css_str += "@define-color error_color " + Glib::ustring(colornamederror) + ";\n"; - css_str += "@define-color success_color " + Glib::ustring(colornamedsuccess) + ";\n"; + css_str += "@define-color warning_color " + Inkscape::Colors::rgba_to_hex(colorsetwarning) + ";\n"; + css_str += "@define-color error_color " + Inkscape::Colors::rgba_to_hex(colorseterror) + ";\n"; + css_str += "@define-color success_color " + Inkscape::Colors::rgba_to_hex(colorsetsuccess) + ";\n"; /* ":not(.rawstyle) > image" works only on images in first level of widget container if in the future we use a complex widget with more levels and we dont want to tweak the color here, retaining default we can add more lines like ":not(.rawstyle) > > image" @@ -184,7 +174,7 @@ ThemeContext::get_symbolic_colors() css_str += ":not(.rawstyle) > image:not(.arrow),"; css_str += ":not(.rawstyle) treeview.image"; css_str += "{color:"; - css_str += colornamed; + css_str += Inkscape::Colors::rgba_to_hex(colorsetbase); css_str += ";}"; } css_str += ".dark .forcebright :not(.rawstyle) > image,"; @@ -199,7 +189,7 @@ ThemeContext::get_symbolic_colors() css_str += ".inverse image:not(.rawstyle)"; css_str += "{color:"; if (overridebasecolor) { - css_str += colornamed_inverse; + css_str += Inkscape::Colors::rgba_to_hex(colorsetbase_inverse); } else { // we override base color in this special cases using inverse color css_str += "@theme_bg_color"; diff --git a/src/ui/tool/multi-path-manipulator.cpp b/src/ui/tool/multi-path-manipulator.cpp index c4d7938f092c9e352d64fa1d23f4e93d7ebaa571..edb83e7d3731132b28664cbc218d56785a81d4b1 100644 --- a/src/ui/tool/multi-path-manipulator.cpp +++ b/src/ui/tool/multi-path-manipulator.cpp @@ -182,7 +182,7 @@ void MultiPathManipulator::setItems(std::set const &s) auto lpobj = cast(r.object); if (!is(r.object) && !lpobj) continue; std::shared_ptr newpm(new PathManipulator(*this, (SPPath*) r.object, - r.edit_transform, _getOutlineColor(r.role, r.object), r.lpe_key)); + r.edit_transform, _getOutlineColor(r.role, r.object).toRGBA(), r.lpe_key)); newpm->showHandles(_show_handles); // always show outlines for clips and masks newpm->showOutline(_show_outline || r.role != SHAPE_ROLE_NORMAL); @@ -889,19 +889,19 @@ void MultiPathManipulator::_doneWithCleanup(gchar const *reason, bool alert_LPE) } /** Get an outline color based on the shape's role (normal, mask, LPE parameter, etc.). */ -guint32 MultiPathManipulator::_getOutlineColor(ShapeRole role, SPObject *object) +Colors::Color MultiPathManipulator::_getOutlineColor(ShapeRole role, SPObject *object) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); switch(role) { case SHAPE_ROLE_CLIPPING_PATH: - return prefs->getColor("/tools/nodes/clipping_path_color", 0x00ff00ff); + return prefs->getColor("/tools/nodes/clipping_path_color", "#00ff00ff"); case SHAPE_ROLE_MASK: - return prefs->getColor("/tools/nodes/mask_color", 0x0000ffff); + return prefs->getColor("/tools/nodes/mask_color", "#0000ffff"); case SHAPE_ROLE_LPE_PARAM: - return prefs->getColor("/tools/nodes/lpe_param_color", 0x009000ff); + return prefs->getColor("/tools/nodes/lpe_param_color", "#009000ff"); case SHAPE_ROLE_NORMAL: default: - return prefs->getColor("/tools/nodes/highlight_color", 0xff0000ff);; + return prefs->getColor("/tools/nodes/highlight_color", "#ff0000ff");; } } diff --git a/src/ui/tool/multi-path-manipulator.h b/src/ui/tool/multi-path-manipulator.h index d48234e696b18d4f95e14d6732e13b004fbd6dea..615999268cd7d67c45f15dc52739b03b1254e54b 100644 --- a/src/ui/tool/multi-path-manipulator.h +++ b/src/ui/tool/multi-path-manipulator.h @@ -125,7 +125,7 @@ private: void _commit(CommitEvent cps); void _done(gchar const *reason, bool alert_LPE = true); void _doneWithCleanup(gchar const *reason, bool alert_LPE = false); - guint32 _getOutlineColor(ShapeRole role, SPObject *object); + Colors::Color _getOutlineColor(ShapeRole role, SPObject *object); MapType _mmap; diff --git a/src/ui/toolbar/text-toolbar.cpp b/src/ui/toolbar/text-toolbar.cpp index d26c15b8beee62f60a8bb71d5e228825cb1bccf8..2563431cbfbe3ee27ad84b3c3ab727ffef85365d 100644 --- a/src/ui/toolbar/text-toolbar.cpp +++ b/src/ui/toolbar/text-toolbar.cpp @@ -1568,12 +1568,10 @@ void TextToolbar::selection_changed(Inkscape::Selection *selection) // don't bot std::vector qactive{ this->_sub_active_item }; auto parent = cast(this->_sub_active_item->parent); std::vector qparent{ parent }; - result_numbers = - sp_desktop_query_style_from_list(qactive, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS); - result_numbers_fallback = - sp_desktop_query_style_from_list(qparent, &query_fallback, QUERY_STYLE_PROPERTY_FONTNUMBERS); + result_numbers = objects_query_fontnumbers(qactive, &query); + result_numbers_fallback = objects_query_fontnumbers(qparent, &query_fallback); } else if (_outer) { - result_numbers = sp_desktop_query_style_from_list(to_work, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS); + result_numbers = objects_query_fontnumbers(to_work, &query); } else { result_numbers = sp_desktop_query_style(desktop, &query, QUERY_STYLE_PROPERTY_FONTNUMBERS); } diff --git a/src/ui/tools/booleans-builder.cpp b/src/ui/tools/booleans-builder.cpp index e06bc5d524490a3e17bfe9d8511bc4b5e5489343..dada329649ba42885a66d2b8eacbd1c7a1ce94e2 100644 --- a/src/ui/tools/booleans-builder.cpp +++ b/src/ui/tools/booleans-builder.cpp @@ -89,8 +89,7 @@ void BooleanBuilder::redraw_items() return; } - auto nv = _set->desktop()->getNamedView(); - _dark = SP_RGBA32_LUMINANCE(nv->desk_color) < 100; + _dark = Colors::get_perceptual_lightness(_set->desktop()->getNamedView()->getDeskColor()) < 0.5; _screen_items.clear(); diff --git a/src/ui/tools/calligraphic-tool.cpp b/src/ui/tools/calligraphic-tool.cpp index e1733611f268e97bbf473b1f100794b41791fda4..8e20d405b4794d71f2db4bfc34c922192bb5b1a9 100644 --- a/src/ui/tools/calligraphic-tool.cpp +++ b/src/ui/tools/calligraphic-tool.cpp @@ -1042,13 +1042,13 @@ void CalligraphicTool::fit_and_split(bool release) if (!release) { g_assert(!currentcurve.is_empty()); - guint32 fillColor = sp_desktop_get_color_tool(_desktop, "/tools/calligraphic", true); + auto fillColor = sp_desktop_get_color_tool(_desktop, "/tools/calligraphic", true); double opacity = sp_desktop_get_master_opacity_tool(_desktop, "/tools/calligraphic"); double fillOpacity = sp_desktop_get_opacity_tool(_desktop, "/tools/calligraphic", true); - guint fill = (fillColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*fillOpacity); + // TODO: This removes color space information. auto cbp = new Inkscape::CanvasItemBpath(_desktop->getCanvasSketch(), currentcurve.get_pathvector(), true); - cbp->set_fill(fill, SP_WIND_RULE_EVENODD); + cbp->set_fill(fillColor ? fillColor->toRGBA(opacity * fillOpacity) : 0x0, SP_WIND_RULE_EVENODD); cbp->set_stroke(0x0); /* fixme: Cannot we cascade it to root more clearly? */ diff --git a/src/ui/tools/dropper-tool.cpp b/src/ui/tools/dropper-tool.cpp index a78bc8adf05535866c2e4ed956f11b02df690324..b08623177514027b0215dee2181559e5c4990620 100644 --- a/src/ui/tools/dropper-tool.cpp +++ b/src/ui/tools/dropper-tool.cpp @@ -34,8 +34,6 @@ #include "object/sp-namedview.h" -#include "svg/svg-color.h" - #include "ui/cursor-utils.h" #include "ui/icon-names.h" #include "ui/tools/dropper-tool.h" @@ -89,7 +87,7 @@ DropperTool::~DropperTool() * @param invert If true, invert the rgb value * @param non_dropping If true, use color from canvas, even in dropping mode. */ -uint32_t DropperTool::get_color(bool invert, bool non_dropping) const +std::optional DropperTool::get_color(bool invert, bool non_dropping) const { auto prefs = Preferences::get(); @@ -98,16 +96,15 @@ uint32_t DropperTool::get_color(bool invert, bool non_dropping) const // non_dropping ignores dropping mode and always uses color from canvas. // Used by the clipboard - double r = non_dropping ? non_dropping_R : R; - double g = non_dropping ? non_dropping_G : G; - double b = non_dropping ? non_dropping_B : B; - double a = non_dropping ? non_dropping_A : alpha; - - return SP_RGBA32_F_COMPOSE( - std::abs(invert - r), - std::abs(invert - g), - std::abs(invert - b), - (pick == PICK_ACTUAL && setalpha) ? a : 1.0); + auto color = non_dropping ? non_dropping_color : stored_color; + + if (color && invert) + color->invert(); + + if (color && (pick != PICK_ACTUAL || !setalpha)) + color->enableOpacity(false); + + return color; } bool DropperTool::root_handler(CanvasEvent const &event) @@ -128,29 +125,21 @@ bool DropperTool::root_handler(CanvasEvent const &event) auto selection = _desktop->getSelection(); g_assert(selection); - std::optional apply_color; + std::optional apply_color; for (auto const &obj: selection->objects()) { if (obj->style) { - double opacity = 1.0; if (!stroke && obj->style->fill.set) { - if (obj->style->fill_opacity.set) { - opacity = SP_SCALE24_TO_FLOAT(obj->style->fill_opacity.value); - } - apply_color = obj->style->fill.value.color.toRGBA32(opacity); + apply_color = obj->style->fill.getColor(); + apply_color->addOpacity(obj->style->fill_opacity); } else if (stroke && obj->style->stroke.set) { - if (obj->style->stroke_opacity.set) { - opacity = SP_SCALE24_TO_FLOAT(obj->style->stroke_opacity.value); - } - apply_color = obj->style->stroke.value.color.toRGBA32(opacity); + apply_color = obj->style->stroke.getColor(); + apply_color->addOpacity(obj->style->stroke_opacity); } } } if (apply_color) { - R = SP_RGBA32_R_F(*apply_color); - G = SP_RGBA32_G_F(*apply_color); - B = SP_RGBA32_B_F(*apply_color); - alpha = SP_RGBA32_A_F(*apply_color); + stored_color = apply_color; } else { // This means that having no selection or some other error // we will default back to normal dropper mode. @@ -223,7 +212,7 @@ bool DropperTool::root_handler(CanvasEvent const &event) auto canvas_item_drawing = _desktop->getCanvasDrawing(); auto drawing = canvas_item_drawing->get_drawing(); - // Get average color + // Get average color of on screen pixels (sRGB) double R2, G2, B2, A2; drawing->averageColor(pick_area, R2, G2, B2, A2); @@ -249,19 +238,12 @@ bool DropperTool::root_handler(CanvasEvent const &event) // Remember color if (!dropping) { - R = R2; - G = G2; - B = B2; - alpha = A2; + stored_color = Colors::Color(SP_RGBA32_F_COMPOSE(R2, G2, B2, A2)); } // Remember color from canvas, even in dropping mode. // These values are used by the clipboard. - non_dropping_R = R2; - non_dropping_G = G2; - non_dropping_B = B2; - non_dropping_A = A2; - + non_dropping_color = Colors::Color(SP_RGBA32_F_COMPOSE(R2, G2, B2, A2)); ret = true; }, @@ -291,11 +273,11 @@ bool DropperTool::root_handler(CanvasEvent const &event) } } - auto picked_color = ColorRGBA(get_color(invert)); + auto picked_color = get_color(invert); // One time pick has active signal, call them all and clear. if (!onetimepick_signal.empty()) { - onetimepick_signal.emit(&picked_color); + onetimepick_signal.emit(*picked_color); onetimepick_signal.clear(); // Do this last as it destroys the picker tool. sp_toggle_dropper(_desktop); @@ -304,7 +286,7 @@ bool DropperTool::root_handler(CanvasEvent const &event) } // do the actual color setting - sp_desktop_set_color(_desktop, picked_color, false, !stroke); + sp_desktop_set_color(_desktop, *picked_color, false, !stroke); // REJON: set aux. toolbar input to hex color! if (!_desktop->getSelection()->isEmpty()) { @@ -345,31 +327,32 @@ bool DropperTool::root_handler(CanvasEvent const &event) } // set the status message to the right text. - char c[64]; - sp_svg_write_color(c, sizeof(c), get_color(invert)); - - // alpha of color under cursor, to show in the statusbar - // locale-sensitive printf is OK, since this goes to the UI, not into SVG - auto alphastr = g_strdup_printf(_(" alpha %.3g"), alpha); - // where the color is picked, to show in the statusbar - auto where = dragging ? g_strdup_printf(_(", averaged with radius %d"), (int)radius) : g_strdup_printf("%s", _(" under cursor")); - // message, to show in the statusbar - auto message = dragging ? _("Release mouse to set color.") : _("Click to set fill, Shift+click to set stroke; drag to average color in area; with Alt to pick inverse color; Ctrl+C to copy the color under mouse to clipboard"); - - defaultMessageContext()->setF( - Inkscape::NORMAL_MESSAGE, - "%s%s%s. %s", c, - pick == PICK_VISIBLE ? "" : alphastr, where, message); - - g_free(where); - g_free(alphastr); + auto color = get_color(invert); + + if (color) { + // alpha of color under cursor, to show in the statusbar + // locale-sensitive printf is OK, since this goes to the UI, not into SVG + auto alphastr = g_strdup_printf(_(" alpha %.3g"), color->getOpacity()); + // where the color is picked, to show in the statusbar + auto where = dragging ? g_strdup_printf(_(", averaged with radius %d"), (int)radius) : g_strdup_printf("%s", _(" under cursor")); + // message, to show in the statusbar + auto message = dragging ? _("Release mouse to set color.") : _("Click to set fill, Shift+click to set stroke; drag to average color in area; with Alt to pick inverse color; Ctrl+C to copy the color under mouse to clipboard"); + + defaultMessageContext()->setF( + Inkscape::NORMAL_MESSAGE, + "%s%s%s. %s", color->toString(false).c_str(), + pick == PICK_VISIBLE ? "" : alphastr, where, message); + + g_free(where); + g_free(alphastr); + } // Set the right cursor for the mode and apply the special Fill color _cursor_filename = dropping ? (stroke ? "dropper-drop-stroke.svg" : "dropper-drop-fill.svg") : (stroke ? "dropper-pick-stroke.svg" : "dropper-pick-fill.svg"); // We do this ourselves to get color correct. - set_svg_cursor(*_desktop->getCanvas(), _cursor_filename, get_color(invert)); + set_svg_cursor(*_desktop->getCanvas(), _cursor_filename, color); if (!ret) { ret = ToolBase::root_handler(event); diff --git a/src/ui/tools/dropper-tool.h b/src/ui/tools/dropper-tool.h index ef6848b748baead1b28a561ecc64fe23c124c378..70b17c637397efafa750e344698d72ae4a5e22ef 100644 --- a/src/ui/tools/dropper-tool.h +++ b/src/ui/tools/dropper-tool.h @@ -15,7 +15,7 @@ #include <2geom/point.h> -#include "color-rgba.h" +#include "colors/color.h" #include "display/control/canvas-item-ptr.h" #include "ui/tools/tool-base.h" @@ -28,26 +28,19 @@ public: DropperTool(SPDesktop *desktop); ~DropperTool() override; - uint32_t get_color(bool invert = false, bool non_dropping = false) const; - - sigc::signal onetimepick_signal; + std::optional get_color(bool invert = false, bool non_dropping = false) const; + sigc::signal onetimepick_signal; protected: bool root_handler(CanvasEvent const &event) override; private: // Stored color. - double R = 0.0; - double G = 0.0; - double B = 0.0; - double alpha = 0.0; + std::optional stored_color; // Stored color taken from canvas. Used by clipboard. - // Identical to R, G, B, alpha if dropping disabled. - double non_dropping_R = 0.0; - double non_dropping_G = 0.0; - double non_dropping_B = 0.0; - double non_dropping_A = 0.0; + // Identical to stored_color if dropping disabled. + std::optional non_dropping_color; bool invert = false; ///< Set color to inverse rgb value bool stroke = false; ///< Set to stroke color. In dropping mode, set from stroke color diff --git a/src/ui/tools/eraser-tool.cpp b/src/ui/tools/eraser-tool.cpp index b604c9f544c173f365edc741babe6844f28331d7..08525054027924af0bac2a139ebe53e62a2fdf4a 100644 --- a/src/ui/tools/eraser-tool.cpp +++ b/src/ui/tools/eraser-tool.cpp @@ -1334,14 +1334,13 @@ void EraserTool::_fitDrawLastPoint() { g_assert(!currentcurve.is_empty()); - guint32 fillColor = sp_desktop_get_color_tool(_desktop, "/tools/eraser", true); + auto fillColor = sp_desktop_get_color_tool(_desktop, "/tools/eraser", true); double opacity = sp_desktop_get_master_opacity_tool(_desktop, "/tools/eraser"); double fillOpacity = sp_desktop_get_opacity_tool(_desktop, "/tools/eraser", true); - guint fill = (fillColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity * fillOpacity); - + // TODO This removes color space information from the color auto cbp = new Inkscape::CanvasItemBpath(_desktop->getCanvasSketch(), currentcurve.get_pathvector(), true); - cbp->set_fill(fill, trace_wind_rule); + cbp->set_fill(fillColor ? fillColor->toRGBA(opacity * fillOpacity) : 0x0, trace_wind_rule); cbp->set_stroke(0x0); /* fixme: Cannot we cascade it to root more clearly? */ diff --git a/src/ui/tools/flood-tool.cpp b/src/ui/tools/flood-tool.cpp index 8cc4c6f4a528373e23ef937becd4788ddbc4655c..ba51af714c1b6d59339459596c2b0e7ee30fee3b 100644 --- a/src/ui/tools/flood-tool.cpp +++ b/src/ui/tools/flood-tool.cpp @@ -30,7 +30,6 @@ #include <2geom/pathvector.h> #include "async/progress.h" -#include "color.h" #include "context-fns.h" #include "desktop-style.h" #include "desktop.h" @@ -70,6 +69,8 @@ using Inkscape::Display::ExtractARGB32; using Inkscape::Display::ExtractRGB32; using Inkscape::Display::AssembleARGB32; +using namespace Inkscape::Colors; + namespace Inkscape::UI::Tools { // Must match PaintBucketChannels enum @@ -192,8 +193,6 @@ static bool compare_uint32(uint32_t a, uint32_t b, uint32_t d) */ static bool compare_pixels(uint32_t check, uint32_t orig, uint32_t merged_orig_pixel, uint32_t dtc, int threshold, PaintBucketChannels method) { - float hsl_check[3] = {0,0,0}, hsl_orig[3] = {0,0,0}; - uint32_t ac = 0, rc = 0, gc = 0, bc = 0; ExtractARGB32(check, ac, rc, gc, bc); @@ -206,13 +205,16 @@ static bool compare_pixels(uint32_t check, uint32_t orig, uint32_t merged_orig_p uint32_t amop = 0, rmop = 0, gmop = 0, bmop = 0; ExtractARGB32(merged_orig_pixel, amop, rmop, gmop, bmop); + auto hsl_orig = Color(Space::Type::HSL, {0, 0, 0}); + auto hsl_check = hsl_orig; + if ((method == FLOOD_CHANNELS_H) || (method == FLOOD_CHANNELS_S) || (method == FLOOD_CHANNELS_L)) { - double dac = ac; double dao = ao; - SPColor::rgb_to_hsl_floatv(hsl_check, rc / dac, gc / dac, bc / dac); - SPColor::rgb_to_hsl_floatv(hsl_orig, ro / dao, go / dao, bo / dao); + double dac = ac; + hsl_orig.set(Color(Space::Type::RGB, {ro / dao, go / dao, bo / dao}), true); + hsl_check.set(Color(Space::Type::RGB, {rc / dac, gc / dac, bc / dac}), true); } switch (method) { @@ -720,7 +722,7 @@ static void sp_flood_do_flood_fill(SPDesktop *desktop, Geom::Point const &cursor auto const stride = Cairo::ImageSurface::format_stride_for_width(Cairo::Surface::Format::ARGB32, width); // TODO: C++20: *once* Apple+AppImage support it: Use std::make_unique_for_overwrite() auto const px = std::make_unique(stride * height); - uint32_t bgcolor, dtc; + uint32_t dtc; // Draw image into data block px { // this block limits the lifetime of Drawing and DrawingContext @@ -738,12 +740,11 @@ static void sp_flood_do_flood_fill(SPDesktop *desktop, Geom::Point const &cursor auto dc = DrawingContext(surf->cobj(), Geom::Point()); // cairo_translate not necessary here - surface origin is at 0,0 - bgcolor = document->getPageManager().background_color; - bgcolor &= 0xffffff00; // make color transparent for 'alpha' flood mode to work - // bgcolor is 0xrrggbbaa, we need 0xaarrggbb - dtc = bgcolor >> 8; // keep color transparent; page color doesn't support transparency anymore + // make color transparent for 'alpha' flood mode to work + auto bgcolor = document->getPageManager().getBackgroundColor(); + bgcolor.setOpacity(0.0); - dc.setSource(bgcolor); + dc.setSource(bgcolor.toARGB()); dc.setOperator(CAIRO_OPERATOR_SOURCE); dc.paint(); dc.setOperator(CAIRO_OPERATOR_OVER); diff --git a/src/ui/tools/freehand-base.cpp b/src/ui/tools/freehand-base.cpp index 6ca80a1375b92bb49166cf22443be289d6ab91de..c7312589170077b6d2af2ff835d62d44a9f90f5c 100644 --- a/src/ui/tools/freehand-base.cpp +++ b/src/ui/tools/freehand-base.cpp @@ -36,7 +36,6 @@ #include "object/sp-rect.h" #include "object/sp-use.h" -#include "svg/svg-color.h" #include "svg/svg.h" #include "ui/clipboard.h" @@ -169,11 +168,9 @@ void spdc_apply_style(SPObject *obj) sp_repr_css_set_property(css, "fill", str.c_str()); } } else if (obj->style->stroke.isColor()) { - gchar c[64]; - sp_svg_write_color( - c, sizeof(c), - obj->style->stroke.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(obj->style->stroke_opacity.value))); - sp_repr_css_set_property(css, "fill", c); + auto color = obj->style->stroke.getColor(); + color.addOpacity(obj->style->stroke_opacity); + sp_repr_css_set_property_string(css, "fill", color.toString()); } else { sp_repr_css_set_property(css, "fill", "none"); } @@ -899,11 +896,15 @@ void spdc_create_single_dot(ToolBase *tool, Geom::Point const &pt, char const *p } // unset stroke and set fill color to former stroke color - auto str = strcmp(path, "/tools/calligraphic") - ? g_strdup_printf("fill:#%06x;stroke:none;", sp_desktop_get_color_tool(desktop, path, false) >> 8) - : g_strdup_printf("fill:#%06x;stroke:#%06x;", sp_desktop_get_color_tool(desktop, path, true) >> 8, sp_desktop_get_color_tool(desktop, path, false) >> 8); - repr->setAttribute("style", str); - g_free(str); + bool cali = strcmp(path, "/tools/calligraphic"); + auto fill = sp_desktop_get_color_tool(desktop, path, cali); + auto stroke = sp_desktop_get_color_tool(desktop, path, false); + + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property_string(css, "fill", fill ? fill->toString() : "none"); + sp_repr_css_set_property_string(css, "stroke", !cali && stroke ? stroke->toString() : "none"); + sp_repr_css_set(repr, css, "style"); + sp_repr_css_attr_unref(css); // put the circle where the mouse click occurred and set the diameter to the // current stroke width, multiplied by the amount specified in the preferences diff --git a/src/ui/tools/gradient-tool.cpp b/src/ui/tools/gradient-tool.cpp index da3cdc20f9712b53e632d5d407f5ba807082082c..7db42a4bce268b0e8dc2556d7786c7eb96beba73 100644 --- a/src/ui/tools/gradient-tool.cpp +++ b/src/ui/tools/gradient-tool.cpp @@ -346,18 +346,9 @@ void GradientTool::simplify(double tolerance) } // compare color of stop1 to the average color of stop0 and stop2 - uint32_t const c0 = stop0->get_rgba32(); - uint32_t const c2 = stop2->get_rgba32(); - uint32_t const c1r = stop1->get_rgba32(); - uint32_t const c1 = average_color(c0, c2, (stop1->offset - stop0->offset) / (stop2->offset - stop0->offset)); - - double diff = - Geom::sqr(SP_RGBA32_R_F(c1) - SP_RGBA32_R_F(c1r)) + - Geom::sqr(SP_RGBA32_G_F(c1) - SP_RGBA32_G_F(c1r)) + - Geom::sqr(SP_RGBA32_B_F(c1) - SP_RGBA32_B_F(c1r)) + - Geom::sqr(SP_RGBA32_A_F(c1) - SP_RGBA32_A_F(c1r)); - - if (diff < tolerance) { + auto coord = (stop1->offset - stop0->offset) / (stop2->offset - stop0->offset); + auto avg = stop0->getColor().averaged(stop2->getColor(), coord); + if (avg.difference(stop1->getColor()) < tolerance) { todel.emplace(stop1); } } diff --git a/src/ui/tools/measure-tool.cpp b/src/ui/tools/measure-tool.cpp index da47557393bb03a061623218fe16714643675e10..7af6215f75fdf15f5543d6bbbd0ddc05b45259ff 100644 --- a/src/ui/tools/measure-tool.cpp +++ b/src/ui/tools/measure-tool.cpp @@ -22,6 +22,7 @@ #include <2geom/line.h> #include <2geom/path-intersection.h> +#include "colors/utils.h" #include "desktop-style.h" #include "desktop.h" #include "document-undo.h" @@ -44,7 +45,6 @@ #include "object/sp-shape.h" #include "object/sp-text.h" -#include "svg/svg-color.h" #include "svg/svg.h" #include "ui/dialog/knot-properties.h" @@ -854,10 +854,8 @@ void MeasureTool::setLabelText(Glib::ustring const &value, Geom::Point pos, doub /* Create */ Inkscape::XML::Node *rrect = xml_doc->createElement("svg:rect"); SPCSSAttr *css = sp_repr_css_attr_new (); - gchar color_line[64]; - sp_svg_write_color (color_line, sizeof(color_line), background); - sp_repr_css_set_property (css, "fill", color_line); - sp_repr_css_set_property (css, "fill-opacity", "0.5"); + sp_repr_css_set_property_string(css, "fill", Inkscape::Colors::rgba_to_hex(background)); + sp_repr_css_set_property_double(css, "fill-opacity", 0.5); sp_repr_css_set_property (css, "stroke-width", "0"); Glib::ustring css_str; sp_repr_css_write_string(css,css_str); @@ -1331,13 +1329,7 @@ void MeasureTool::setMeasureItem(Geom::PathVector pathv, bool is_curve, bool mar } sp_repr_css_set_property (css, "stroke-width", stroke_width.str().c_str()); sp_repr_css_set_property (css, "fill", "none"); - if(color) { - char color_line[64]; - sp_svg_write_color (color_line, sizeof(color_line), color); - sp_repr_css_set_property (css, "stroke", color_line); - } else { - sp_repr_css_set_property (css, "stroke", "#ff0000"); - } + sp_repr_css_set_property_string(css, "stroke", color ? Inkscape::Colors::rgba_to_hex(color) : "#ff0000"); char const * stroke_linecap = is_curve ? "butt" : "square"; sp_repr_css_set_property (css, "stroke-linecap", stroke_linecap); sp_repr_css_set_property (css, "stroke-linejoin", "miter"); diff --git a/src/ui/tools/node-tool.cpp b/src/ui/tools/node-tool.cpp index 0780530cfb87acf18e646b508fecf76689aa83e6..02d8edf7840c71dad8fa8485f661e7e5ba0da8cf 100644 --- a/src/ui/tools/node-tool.cpp +++ b/src/ui/tools/node-tool.cpp @@ -491,7 +491,7 @@ bool NodeTool::root_handler(CanvasEvent const &event) auto c = shape->curveForEdit()->transformed(over_item->i2dt_affine()); auto flash = new Inkscape::CanvasItemBpath(_desktop->getCanvasTemp(), c.get_pathvector(), true); - flash->set_stroke(over_item->highlight_color()); + flash->set_stroke(over_item->highlight_color().toRGBA()); flash->set_fill(0x0, SP_WIND_RULE_NONZERO); // No fill. flash_tempitem = _desktop->add_temporary_canvasitem(flash, prefs->getInt("/tools/nodes/pathflash_timeout", 500)); diff --git a/src/ui/tools/pen-tool.cpp b/src/ui/tools/pen-tool.cpp index a648ea2f81170ce14a707ddfb6c407f543aab85c..e90afec817485d4bc8dc05339b372cae253effa5 100644 --- a/src/ui/tools/pen-tool.cpp +++ b/src/ui/tools/pen-tool.cpp @@ -1222,12 +1222,14 @@ void PenTool::_setAngleDistanceStatusMessage(Geom::Point const p, int pc_point_t void PenTool::_bsplineSpiroColor() { static Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + auto highlight = currentLayer()->highlight_color(); + auto other = prefs->getColor("/tools/nodes/highlight_color", "#ff0000ff"); if (this->spiro){ this->red_color = 0xff000000; this->green_color = 0x00ff0000; } else if(this->bspline) { - this->highlight_color = currentLayer()->highlight_color(); - if((unsigned int)prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff) == this->highlight_color){ + highlight_color = highlight.toRGBA(); + if(other == highlight) { this->green_color = 0xff00007f; this->red_color = 0xff00007f; } else { @@ -1235,9 +1237,9 @@ void PenTool::_bsplineSpiroColor() this->red_color = this->highlight_color; } } else { - this->highlight_color = currentLayer()->highlight_color(); + highlight_color = highlight.toRGBA(); this->red_color = 0xff00007f; - if((unsigned int)prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff) == this->highlight_color){ + if(other == highlight) { this->green_color = 0x00ff007f; } else { this->green_color = this->highlight_color; diff --git a/src/ui/tools/pencil-tool.cpp b/src/ui/tools/pencil-tool.cpp index eeaefee65c6b2beb1bf54c132e05f8a361362fce..c3813ed1c5aa6d8275e3acfb86b56f2d2621d2af 100644 --- a/src/ui/tools/pencil-tool.cpp +++ b/src/ui/tools/pencil-tool.cpp @@ -1139,12 +1139,15 @@ void PencilTool::_fitAndSplit() { /// \todo fixme: auto layer = _desktop->layerManager().currentLayer(); - this->highlight_color = layer->highlight_color(); - if((unsigned int)prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff) == this->highlight_color){ - this->green_color = 0x00ff007f; + auto highlight = layer->highlight_color(); + auto other = prefs->getColor("/tools/nodes/highlight_color", "#ff0000ff"); + + if(other == highlight) { + green_color = 0x00ff007f; } else { - this->green_color = this->highlight_color; + green_color = highlight.toRGBA(); } + highlight_color = highlight.toRGBA(); auto cshape = new Inkscape::CanvasItemBpath(_desktop->getCanvasSketch(), red_curve.get_pathvector(), true); cshape->set_stroke(green_color); diff --git a/src/ui/tools/spray-tool.cpp b/src/ui/tools/spray-tool.cpp index 037b94f8cb25fef8945e765b52c992d07dfd95f1..5f4d3ca6dbbd80a7e5f0c393a145a87ef227ab4b 100644 --- a/src/ui/tools/spray-tool.cpp +++ b/src/ui/tools/spray-tool.cpp @@ -30,6 +30,7 @@ #include <2geom/circle.h> +#include "colors/utils.h" #include "context-fns.h" #include "desktop-style.h" #include "desktop.h" @@ -47,8 +48,6 @@ #include "object/sp-shape.h" #include "object/sp-use.h" -#include "svg/svg-color.h" - #include "ui/icon-names.h" #include "ui/toolbar/spray-toolbar.h" #include "ui/widget/events/canvas-event.h" @@ -602,17 +601,16 @@ static bool fit_item(SPDesktop *desktop, } double opacity = 1.0; gchar color_string[32]; *color_string = 0; - float r = SP_RGBA32_R_F(rgba); - float g = SP_RGBA32_G_F(rgba); - float b = SP_RGBA32_B_F(rgba); - float a = SP_RGBA32_A_F(rgba); - if(!over_transparent && (a == 0 || a < 1e-6)){ + auto color = Colors::Color(rgba); + bool invisible = color.getOpacity() < 1e-6; + + if(!over_transparent && invisible){ if(!no_overlap && (picker || over_transparent || over_no_transparent)){ showHidden(items_down); } return false; } - if(!over_no_transparent && a > 0){ + if(!over_no_transparent && !invisible){ if(!no_overlap && (picker || over_transparent || over_no_transparent)){ showHidden(items_down); } @@ -620,8 +618,7 @@ static bool fit_item(SPDesktop *desktop, } if(picker && do_trace){ - float hsl[3]; - SPColor::rgb_to_hsl_floatv (hsl, r, g, b); + auto hsl = *color.converted(Colors::Space::Type::HSL); gdouble val = 0; switch (pick) { @@ -629,16 +626,16 @@ static bool fit_item(SPDesktop *desktop, val = 1 - hsl[2]; // inverse lightness; to match other picks where black = max break; case PICK_OPACITY: - val = a; + val = color.getOpacity(); break; case PICK_R: - val = r; + val = color[0]; break; case PICK_G: - val = g; + val = color[1]; break; case PICK_B: - val = b; + val = color[2]; break; case PICK_H: val = hsl[0]; @@ -655,9 +652,9 @@ static bool fit_item(SPDesktop *desktop, if (rand_picked > 0) { val = randomize01 (val, rand_picked); - r = randomize01 (r, rand_picked); - g = randomize01 (g, rand_picked); - b = randomize01 (b, rand_picked); + for (auto i = 0; i < 3; i++) { + color.set(i, randomize01(color[i], rand_picked)); + } } if (gamma_picked != 0) { @@ -668,25 +665,19 @@ static bool fit_item(SPDesktop *desktop, power = 1 + fabs(gamma_picked); val = pow (val, power); - r = pow ((double)r, (double)power); - g = pow ((double)g, (double)power); - b = pow ((double)b, (double)power); + for (auto i = 0; i < 3; i++) { + color.set(i, pow(color[i], (double)power)); + } } if (invert_picked) { val = 1 - val; - r = 1 - r; - g = 1 - g; - b = 1 - b; + color.invert(); } val = CLAMP (val, 0, 1); - r = CLAMP (r, 0, 1); - g = CLAMP (g, 0, 1); - b = CLAMP (b, 0, 1); + color.normalize(); - // recompose tweaked color - rgba = SP_RGBA32_F_COMPOSE(r, g, b, a); if (pick_to_size) { if(!trace_scale){ if(pick_inverse_value) { @@ -760,13 +751,7 @@ static bool fit_item(SPDesktop *desktop, } } if (pick_to_color) { - sp_svg_write_color(color_string, sizeof(color_string), rgba); - if(pick_fill){ - sp_repr_css_set_property(css, "fill", color_string); - } - if(pick_stroke){ - sp_repr_css_set_property(css, "stroke", color_string); - } + sp_repr_css_set_property_string(css, pick_fill ? "fill" : "stroke", Inkscape::Colors::rgba_to_hex(rgba)); } if (opacity < 1e-6) { // invisibly transparent, skip if(!no_overlap && (picker || over_transparent || over_no_transparent)){ @@ -779,23 +764,11 @@ static bool fit_item(SPDesktop *desktop, if(!pick_center){ rgba = rgba2; } + auto color = Colors::Color(rgba); if (pick_inverse_value) { - r = 1 - SP_RGBA32_R_F(rgba); - g = 1 - SP_RGBA32_G_F(rgba); - b = 1 - SP_RGBA32_B_F(rgba); - } else { - r = SP_RGBA32_R_F(rgba); - g = SP_RGBA32_G_F(rgba); - b = SP_RGBA32_B_F(rgba); - } - rgba = SP_RGBA32_F_COMPOSE(r, g, b, a); - sp_svg_write_color(color_string, sizeof(color_string), rgba); - if(pick_fill){ - sp_repr_css_set_property(css, "fill", color_string); - } - if(pick_stroke){ - sp_repr_css_set_property(css, "stroke", color_string); + color.invert(); } + sp_repr_css_set_property_string(css, pick_fill ? "fill" : "stroke", color.toString()); } if(!no_overlap && (picker || over_transparent || over_no_transparent)){ showHidden(items_down); diff --git a/src/ui/tools/tool-base.cpp b/src/ui/tools/tool-base.cpp index 27efd7c2bc62da24ff0b0b59aa2e5bccfba33c4b..59ec50c90f7a00422f028f1da5e3aad58787b106 100644 --- a/src/ui/tools/tool-base.cpp +++ b/src/ui/tools/tool-base.cpp @@ -162,14 +162,16 @@ void ToolBase::set_cursor(std::string filename) */ Glib::RefPtr ToolBase::get_cursor(Gtk::Widget &widget, std::string const &filename) const { - bool fillHasColor = false; - bool strokeHasColor = false; - guint32 fillColor = sp_desktop_get_color_tool(_desktop, getPrefsPath(), true, &fillHasColor); - guint32 strokeColor = sp_desktop_get_color_tool(_desktop, getPrefsPath(), false, &strokeHasColor); - double fillOpacity = fillHasColor ? sp_desktop_get_opacity_tool(_desktop, getPrefsPath(), true) : 1.0; - double strokeOpacity = strokeHasColor ? sp_desktop_get_opacity_tool(_desktop, getPrefsPath(), false) : 1.0; - return load_svg_cursor(widget, filename, - fillColor, strokeColor, fillOpacity, strokeOpacity); + auto fillColor = sp_desktop_get_color_tool(_desktop, getPrefsPath(), true); + if (fillColor) { + fillColor->addOpacity(sp_desktop_get_opacity_tool(_desktop, getPrefsPath(), true)); + } + + auto strokeColor = sp_desktop_get_color_tool(_desktop, getPrefsPath(), false); + if (strokeColor) { + strokeColor->addOpacity(sp_desktop_get_opacity_tool(_desktop, getPrefsPath(), false)); + } + return load_svg_cursor(widget, filename, fillColor, strokeColor); } /** diff --git a/src/ui/tools/tweak-tool.cpp b/src/ui/tools/tweak-tool.cpp index 604612d3541e2fae57570ad6ddee1995c7feb4aa..84f21202b0834e5617cbff5d5ca6c3408f445f6b 100644 --- a/src/ui/tools/tweak-tool.cpp +++ b/src/ui/tools/tweak-tool.cpp @@ -585,71 +585,27 @@ sp_tweak_dilate_recursive (Inkscape::Selection *selection, SPItem *item, Geom::P return did; } - static void -tweak_colorpaint (float *color, guint32 goal, double force, bool do_h, bool do_s, bool do_l) -{ - float rgb_g[3]; - - if (!do_h || !do_s || !do_l) { - float hsl_g[3]; - SPColor::rgb_to_hsl_floatv (hsl_g, SP_RGBA32_R_F(goal), SP_RGBA32_G_F(goal), SP_RGBA32_B_F(goal)); - float hsl_c[3]; - SPColor::rgb_to_hsl_floatv (hsl_c, color[0], color[1], color[2]); - if (!do_h) { - hsl_g[0] = hsl_c[0]; - } - if (!do_s) { - hsl_g[1] = hsl_c[1]; - } - if (!do_l) { - hsl_g[2] = hsl_c[2]; - } - SPColor::hsl_to_rgb_floatv (rgb_g, hsl_g[0], hsl_g[1], hsl_g[2]); - } else { - rgb_g[0] = SP_RGBA32_R_F(goal); - rgb_g[1] = SP_RGBA32_G_F(goal); - rgb_g[2] = SP_RGBA32_B_F(goal); - } - - for (int i = 0; i < 3; i++) { - double d = rgb_g[i] - color[i]; - color[i] += d * force; - } -} - - static void -tweak_colorjitter (float *color, double force, bool do_h, bool do_s, bool do_l) +static Color tweak_color(guint mode, Color const &color, Color const &goal, double force, bool do_h, bool do_s, bool do_l) { - float hsl_c[3]; - SPColor::rgb_to_hsl_floatv (hsl_c, color[0], color[1], color[2]); - - if (do_h) { - hsl_c[0] += g_random_double_range(-0.5, 0.5) * force; - if (hsl_c[0] > 1) { - hsl_c[0] -= 1; - } - if (hsl_c[0] < 0) { - hsl_c[0] += 1; + // Tweak colors are entirely based on HSL values; + if (auto hsl = color.converted(Colors::Space::Type::HSL)) { + unsigned int pin = (do_h * 1) + (do_s * 2) + (do_l * 4); + if (mode == TWEAK_MODE_COLORPAINT) { + hsl->average(goal, force, pin); + } else if (mode == TWEAK_MODE_COLORJITTER) { + hsl->jitter(force, pin); } + if (auto copy = hsl->converted(color.getSpace())) + return *copy; } - if (do_s) { - hsl_c[1] += g_random_double_range(-hsl_c[1], 1 - hsl_c[1]) * force; - } - if (do_l) { - hsl_c[2] += g_random_double_range(-hsl_c[2], 1 - hsl_c[2]) * force; - } - - SPColor::hsl_to_rgb_floatv (color, hsl_c[0], hsl_c[1], hsl_c[2]); + return color; // Bad conversion } - static void -tweak_color (guint mode, float *color, guint32 goal, double force, bool do_h, bool do_s, bool do_l) +static void tweak_stop_color(guint mode, SPStop *stop, Color const &goal, double force, bool do_h, bool do_s, bool do_l) { - if (mode == TWEAK_MODE_COLORPAINT) { - tweak_colorpaint (color, goal, force, do_h, do_s, do_l); - } else if (mode == TWEAK_MODE_COLORJITTER) { - tweak_colorjitter (color, force, do_h, do_s, do_l); - } + auto copy = stop->getColor(); + tweak_color(mode, copy, goal, force, do_h, do_s, do_l); + stop->setColor(copy); } static void @@ -686,7 +642,7 @@ tweak_profile (double dist, double radius) } static void tweak_colors_in_gradient(SPItem *item, Inkscape::PaintTarget fill_or_stroke, - guint32 const rgb_goal, Geom::Point p_w, double radius, double force, guint mode, + Color const &goal, Geom::Point p_w, double radius, double force, guint mode, bool do_h, bool do_s, bool do_l, bool /*do_o*/) { SPGradient *gradient = getGradient(item, fill_or_stroke); @@ -778,10 +734,10 @@ static void tweak_colors_in_gradient(SPItem *item, Inkscape::PaintTarget fill_or // so it only affects the ends of this interstop; // distribute the force between the two endstops so that they // get all the painting even if they are not touched by the brush - tweak_color (mode, stop->getColor().v.c, rgb_goal, + tweak_stop_color(mode, stop, goal, force * (pos_e - offset_l) / (offset_h - offset_l), do_h, do_s, do_l); - tweak_color(mode, prevStop->getColor().v.c, rgb_goal, + tweak_stop_color(mode, prevStop, goal, force * (offset_h - pos_e) / (offset_h - offset_l), do_h, do_s, do_l); stop->updateRepr(); @@ -791,14 +747,14 @@ static void tweak_colors_in_gradient(SPItem *item, Inkscape::PaintTarget fill_or // wide brush, may affect more than 2 stops, // paint each stop by the force from the profile curve if (offset_l <= pos_e && offset_l > pos_e - r) { - tweak_color(mode, prevStop->getColor().v.c, rgb_goal, + tweak_stop_color(mode, prevStop, goal, force * tweak_profile (fabs (pos_e - offset_l), r), do_h, do_s, do_l); child_prev->updateRepr(); } if (offset_h >= pos_e && offset_h < pos_e + r) { - tweak_color (mode, stop->getColor().v.c, rgb_goal, + tweak_stop_color(mode, prevStop, goal, force * tweak_profile (fabs (pos_e - offset_h), r), do_h, do_s, do_l); stop->updateRepr(); @@ -820,7 +776,7 @@ static void tweak_colors_in_gradient(SPItem *item, Inkscape::PaintTarget fill_or for( unsigned j=0; j < array->nodes[i].size(); j+=3 ) { SPStop *stop = array->nodes[i][j]->stop; double distance = Geom::L2(Geom::Point(p - array->nodes[i][j]->p)); - tweak_color (mode, stop->getColor().v.c, rgb_goal, + tweak_stop_color(mode, stop, goal, force * tweak_profile (distance, radius), do_h, do_s, do_l); stop->updateRepr(); } @@ -829,10 +785,9 @@ static void tweak_colors_in_gradient(SPItem *item, Inkscape::PaintTarget fill_or } } - static bool +static bool sp_tweak_color_recursive (guint mode, SPItem *item, SPItem *item_at_point, - guint32 fill_goal, bool do_fill, - guint32 stroke_goal, bool do_stroke, + std::optional &fill_goal, std::optional &stroke_goal, float opacity_goal, bool do_opacity, bool do_blur, bool reverse, Geom::Point p, double radius, double force, @@ -845,8 +800,7 @@ sp_tweak_color_recursive (guint mode, SPItem *item, SPItem *item_at_point, auto childItem = cast(&child); if (childItem) { if (sp_tweak_color_recursive (mode, childItem, item_at_point, - fill_goal, do_fill, - stroke_goal, do_stroke, + fill_goal, stroke_goal, opacity_goal, do_opacity, do_blur, reverse, p, radius, force, do_h, do_s, do_l, do_o)) { @@ -932,22 +886,22 @@ sp_tweak_color_recursive (guint mode, SPItem *item, SPItem *item_at_point, return true; // do not do colors, blur is a separate mode } - if (do_fill) { + if (fill_goal) { if (style->fill.isPaintserver()) { - tweak_colors_in_gradient(item, Inkscape::FOR_FILL, fill_goal, p, radius, this_force, mode, do_h, do_s, do_l, do_o); + tweak_colors_in_gradient(item, Inkscape::FOR_FILL, *fill_goal, p, radius, this_force, mode, do_h, do_s, do_l, do_o); did = true; } else if (style->fill.isColor()) { - tweak_color (mode, style->fill.value.color.v.c, fill_goal, this_force, do_h, do_s, do_l); + style->fill.setColor(tweak_color(mode, style->fill.getColor(), *fill_goal, this_force, do_h, do_s, do_l)); item->updateRepr(); did = true; } } - if (do_stroke) { + if (stroke_goal) { if (style->stroke.isPaintserver()) { - tweak_colors_in_gradient(item, Inkscape::FOR_STROKE, stroke_goal, p, radius, this_force, mode, do_h, do_s, do_l, do_o); + tweak_colors_in_gradient(item, Inkscape::FOR_STROKE, *stroke_goal, p, radius, this_force, mode, do_h, do_s, do_l, do_o); did = true; } else if (style->stroke.isColor()) { - tweak_color (mode, style->stroke.value.color.v.c, stroke_goal, this_force, do_h, do_s, do_l); + style->stroke.setColor(tweak_color(mode, style->stroke.getColor(), *stroke_goal, this_force, do_h, do_s, do_l)); item->updateRepr(); did = true; } @@ -977,40 +931,15 @@ sp_tweak_dilate (TweakTool *tc, Geom::Point event_p, Geom::Point p, Geom::Point SPItem *item_at_point = tc->getDesktop()->getItemAtPoint(event_p, true); - bool do_fill = false, do_stroke = false, do_opacity = false; - guint32 fill_goal = sp_desktop_get_color_tool(desktop, "/tools/tweak", true, &do_fill); - guint32 stroke_goal = sp_desktop_get_color_tool(desktop, "/tools/tweak", false, &do_stroke); + bool do_opacity = false; + auto fill_goal = sp_desktop_get_color_tool(desktop, "/tools/tweak", true); + auto stroke_goal = sp_desktop_get_color_tool(desktop, "/tools/tweak", false); double opacity_goal = sp_desktop_get_master_opacity_tool(desktop, "/tools/tweak", &do_opacity); if (reverse) { -#if 0 - // HSL inversion - float hsv[3]; - float rgb[3]; - SPColor::rgb_to_hsv_floatv (hsv, - SP_RGBA32_R_F(fill_goal), - SP_RGBA32_G_F(fill_goal), - SP_RGBA32_B_F(fill_goal)); - SPColor::hsv_to_rgb_floatv (rgb, hsv[0]<.5? hsv[0]+.5 : hsv[0]-.5, 1 - hsv[1], 1 - hsv[2]); - fill_goal = SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 1); - SPColor::rgb_to_hsv_floatv (hsv, - SP_RGBA32_R_F(stroke_goal), - SP_RGBA32_G_F(stroke_goal), - SP_RGBA32_B_F(stroke_goal)); - SPColor::hsv_to_rgb_floatv (rgb, hsv[0]<.5? hsv[0]+.5 : hsv[0]-.5, 1 - hsv[1], 1 - hsv[2]); - stroke_goal = SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 1); -#else - // RGB inversion - fill_goal = SP_RGBA32_U_COMPOSE( - (255 - SP_RGBA32_R_U(fill_goal)), - (255 - SP_RGBA32_G_U(fill_goal)), - (255 - SP_RGBA32_B_U(fill_goal)), - (255 - SP_RGBA32_A_U(fill_goal))); - stroke_goal = SP_RGBA32_U_COMPOSE( - (255 - SP_RGBA32_R_U(stroke_goal)), - (255 - SP_RGBA32_G_U(stroke_goal)), - (255 - SP_RGBA32_B_U(stroke_goal)), - (255 - SP_RGBA32_A_U(stroke_goal))); -#endif + if (fill_goal) + fill_goal->invert(); + if (stroke_goal) + stroke_goal->invert(); opacity_goal = 1 - opacity_goal; } @@ -1025,10 +954,9 @@ sp_tweak_dilate (TweakTool *tc, Geom::Point event_p, Geom::Point p, Geom::Point std::vector items(selection->items().begin(), selection->items().end()); for(auto item : items){ if (is_color_mode (tc->mode)) { - if (do_fill || do_stroke || do_opacity) { + if (fill_goal || stroke_goal || do_opacity) { if (sp_tweak_color_recursive (tc->mode, item, item_at_point, - fill_goal, do_fill, - stroke_goal, do_stroke, + fill_goal, stroke_goal, opacity_goal, do_opacity, tc->mode == TWEAK_MODE_BLUR, reverse, p, radius, color_force, tc->do_h, tc->do_s, tc->do_l, tc->do_o)) { diff --git a/src/ui/util.cpp b/src/ui/util.cpp index 0e6f7f3395508b2004d6058fb0a4bfb9eeb2d8f5..ac99180ac7bcd87a961a948cad743ddefe02251e 100644 --- a/src/ui/util.cpp +++ b/src/ui/util.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,9 @@ #include #include #include + +#include "colors/color.h" +#include "colors/utils.h" // color to hex string #include "util/numeric/converters.h" #if (defined (_WIN32) || defined (_WIN64)) @@ -254,6 +258,19 @@ void ellipsize(Gtk::Label &label, int const max_width_chars, Pango::EllipsizeMod } // namespace Inkscape::UI +/** + * Color is store as a string in the form #RRGGBBAA, '0' means "unset" + * + * @param color - The string color from glade. + */ +unsigned int get_color_value(const Glib::ustring color) +{ + Gdk::RGBA gdk_color = Gdk::RGBA(color); + return SP_RGBA32_F_COMPOSE(gdk_color.get_red(), gdk_color.get_green(), + gdk_color.get_blue(), gdk_color.get_alpha()); +} + + Gdk::RGBA mix_colors(const Gdk::RGBA& a, const Gdk::RGBA& b, float ratio) { auto lerp = [](double v0, double v1, double t){ return (1.0 - t) * v0 + t * v1; }; Gdk::RGBA result; @@ -291,6 +308,11 @@ guint32 to_guint32(Gdk::RGBA const &rgba) static_cast(0xFF * rgba.get_alpha() + 0.5); } +Gdk::RGBA color_to_rgba(Inkscape::Colors::Color const &color) +{ + return to_rgba(color.toRGBA()); +} + Gdk::RGBA to_rgba(guint32 const u32) { auto rgba = Gdk::RGBA{}; @@ -301,6 +323,22 @@ Gdk::RGBA to_rgba(guint32 const u32) return rgba; } +/** + * These GUI related color conversions allow us to convert from + * SVG xml attributes to Gdk colors, without needing the entire CMS + * framework, which would be excessive for widget painting. + */ +Glib::ustring gdk_to_css_color(const Gdk::RGBA& color) { + return Inkscape::Colors::rgba_to_hex(to_guint32(color), true); +} +Gdk::RGBA css_color_to_gdk(const char *value) { + try { + return to_rgba(value ? Inkscape::Colors::hex_to_rgba(value) : 0x0); + } catch (Inkscape::Colors::ColorError &e) { + return {}; + } +} + // 2Geom <-> Cairo Cairo::RectangleInt geom_to_cairo(const Geom::IntRect &rect) diff --git a/src/ui/util.h b/src/ui/util.h index 9dfaf8b334d6c7c5c57c6a84f65c732ae6b0d2d5..cecc32ac7b2c5fdeabec87f144c8438f02f2f726 100644 --- a/src/ui/util.h +++ b/src/ui/util.h @@ -52,6 +52,10 @@ class TextBuffer; class Widget; } // namespace Gtk +namespace Inkscape::Colors { +class Color; +} // namespace Inkscape::Colors + Glib::ustring ink_ellipsize_text(Glib::ustring const &src, std::size_t maxlen); void reveal_widget(Gtk::Widget *widget, bool show); @@ -190,7 +194,12 @@ double get_luminance(const Gdk::RGBA &color); Gdk::RGBA get_color_with_class(Gtk::Widget &widget, Glib::ustring const &css_class); +// Convert a Gdk color to a hex code for css injection. +Glib::ustring gdk_to_css_color(const Gdk::RGBA& color); +Gdk::RGBA css_color_to_gdk(const char *value); + guint32 to_guint32(Gdk::RGBA const &rgba); +Gdk::RGBA color_to_rgba(Inkscape::Colors::Color const &color); Gdk::RGBA to_rgba(guint32 const u32); // convert Gdk::RGBA into 32-bit rrggbbaa color, optionally replacing alpha, if specified @@ -216,6 +225,7 @@ Cairo::RefPtr create_cubic_gradient( // If on Windows, get the native window & set it to DWMA_USE_IMMERSIVE_DARK_MODE void set_dark_titlebar(Glib::RefPtr const &surface, bool is_dark); +unsigned int get_color_value(const Glib::ustring color); // Cover for Glib::wrap not passing through const. template diff --git a/src/ui/widget/canvas-grid.cpp b/src/ui/widget/canvas-grid.cpp index 4880b5f4937ee15e5d71d0688cbac6d1d96f3668..ec56885c53f8d3e7ec00c4c5a98d76c0877e5f90 100644 --- a/src/ui/widget/canvas-grid.cpp +++ b/src/ui/widget/canvas-grid.cpp @@ -447,7 +447,7 @@ void CanvasGrid::_createGuideItem(Geom::Point const &pos, bool horiz) } _active_guide = make_canvasitem(desktop->getCanvasGuides(), Glib::ustring(), Geom::Point(), Geom::Point()); - _active_guide->set_stroke(desktop->getNamedView()->guidehicolor); + _active_guide->set_stroke(desktop->getNamedView()->getGuideHiColor().toRGBA()); } void CanvasGrid::_rulerMotion(GtkEventControllerMotion const *controller_c, double x, double y, bool horiz) diff --git a/src/ui/widget/canvas.cpp b/src/ui/widget/canvas.cpp index 96d7959c7acaaca4f4de4fe33094abf6594826f5..79a2d21fe5ded5becb9acafb1cd2bc2c7e84f59f 100644 --- a/src/ui/widget/canvas.cpp +++ b/src/ui/widget/canvas.cpp @@ -42,8 +42,8 @@ #include "canvas/stores.h" #include "canvas/synchronizer.h" #include "canvas/util.h" -#include "color/cms-system.h" // Color correction -#include "color.h" // Background color +#include "colors/cms/transform.h" +#include "colors/cms/system.h" #include "desktop.h" #include "display/control/canvas-item-drawing.h" #include "display/control/canvas-item-group.h" @@ -156,7 +156,7 @@ struct RedrawData bool decoupled_mode; Cairo::RefPtr snapshot_drawn; Geom::OptIntRect grabbed; - std::shared_ptr cms_transform; + std::shared_ptr cms_transform; // Saved prefs int coarsener_min_size; @@ -1830,8 +1830,7 @@ void Canvas::set_cms_transform() // auto surface = get_surface(); // auto the_monitor = display->get_monitor_at_surface(surface); - auto cms_system = CMSSystem::get(); - _cms_transform = cms_system->get_cms_transform( /* monitor */ ); + _cms_transform = Colors::CMS::System::get().getDisplayTransform(); } // Change cursor @@ -2431,16 +2430,11 @@ void CanvasPrivate::paint_single_buffer(Cairo::RefPtr const auto buf = CanvasItemBuffer{ rect, scale_factor, cr, outline_pass }; canvasitem_ctx->root()->render(buf); - // Apply CMS transform. + // Apply CMS transform for the screen. This rarely is used by modern desktops, but sometimes + // the user will apply an RGB transform to color correct their screen. This happens now, so the + // drawing plus all other canvas items (selection boxes, handles, etc) are also color corrected. if (rd.cms_transform) { - surface->flush(); - auto px = surface->get_data(); - int stride = surface->get_stride(); - for (int i = 0; i < surface->get_height(); i++) { - auto row = px + i * stride; - Inkscape::CMSSystem::do_transform(rd.cms_transform->getHandle(), row, row, surface->get_width()); - } - surface->mark_dirty(); + rd.cms_transform->do_transform(surface->cobj(), surface->cobj()); } // Paint over newly drawn content with a translucent random colour. diff --git a/src/ui/widget/canvas.h b/src/ui/widget/canvas.h index 3928bcc4c32235f2aa8d93d2591d79b9f733e431..37d83c5dfe40a6815d9e918709c8795b67d07fab 100644 --- a/src/ui/widget/canvas.h +++ b/src/ui/widget/canvas.h @@ -42,9 +42,12 @@ namespace Inkscape { class CanvasItem; class CanvasItemGroup; -class CMSTransform; class Drawing; +namespace Colors::CMS { + class Transform; +} + namespace UI::Widget { class CanvasPrivate; @@ -197,7 +200,7 @@ private: // CMS bool _cms_active = false; - std::shared_ptr _cms_transform; ///< The lcms transform to apply to canvas. + std::shared_ptr _cms_transform; ///< The lcms transform to apply to canvas. void set_cms_transform(); ///< Set the lcms transform. /* Internal state */ diff --git a/src/ui/widget/canvas/graphics.cpp b/src/ui/widget/canvas/graphics.cpp index a1eafc2d04a29852530d490f3efcf27e3bba6bde..1184b897345d020901db13ddd129120dc14c3805 100644 --- a/src/ui/widget/canvas/graphics.cpp +++ b/src/ui/widget/canvas/graphics.cpp @@ -6,6 +6,8 @@ #include #include +#include "colors/color.h" +#include "display/cairo-utils.h" #include "helper/geom.h" #include "ui/util.h" #include "util.h" @@ -24,15 +26,17 @@ Cairo::RefPtr rgba_to_pattern(std::uint32_t const rgba) int constexpr w = 6; int constexpr h = 6; - auto dark = checkerboard_darken(rgba); + auto color = Colors::Color(rgba); + auto dark = checkerboard_darken(color); + color.enableOpacity(false); auto surface = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, 2 * w, 2 * h); auto cr = Cairo::Context::create(surface); cr->set_operator(Cairo::Context::Operator::SOURCE); - cr->set_source_rgb(SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba)); + ink_cairo_set_source_color(cr, color); cr->paint(); - cr->set_source_rgb(dark[0], dark[1], dark[2]); + ink_cairo_set_source_color(cr, dark); cr->rectangle(0, 0, w, h); cr->rectangle(w, h, w, h); cr->fill(); diff --git a/src/ui/widget/canvas/util.cpp b/src/ui/widget/canvas/util.cpp index 3d9d59bc1cf60171c302f6a090b42598148c98b4..3a1def52edfe417647affda87764e6a26680fd90 100644 --- a/src/ui/widget/canvas/util.cpp +++ b/src/ui/widget/canvas/util.cpp @@ -42,16 +42,10 @@ Cairo::RefPtr shrink_region(Cairo::RefPtr const &r return reg2; } -std::array checkerboard_darken(std::array const &rgb, float amount) +Colors::Color checkerboard_darken(Colors::Color color) { - std::array hsl; - SPColor::rgb_to_hsl_floatv(&hsl[0], rgb[0], rgb[1], rgb[2]); - hsl[2] += (hsl[2] < 0.08 ? 0.08 : -0.08) * amount; - - std::array rgb2; - SPColor::hsl_to_rgb_floatv(&rgb2[0], hsl[0], hsl[1], hsl[2]); - - return rgb2; + auto opacity = color.stealOpacity(); + return Colors::make_contrasted_color(color, 1.0 - opacity); } } // namespace Widget diff --git a/src/ui/widget/canvas/util.h b/src/ui/widget/canvas/util.h index c2c1ad3a6b57ad9ca00b69bfc6ebe8c768925566..e83d07c5c781b172b8475b9cc0d5192e90474050 100644 --- a/src/ui/widget/canvas/util.h +++ b/src/ui/widget/canvas/util.h @@ -6,7 +6,9 @@ #include <2geom/int-rect.h> #include <2geom/affine.h> #include -#include "color.h" + +#include "colors/color.h" +#include "colors/utils.h" namespace Inkscape { namespace UI { @@ -50,11 +52,12 @@ inline auto premultiplied(std::array arr) return arr; } -std::array checkerboard_darken(std::array const &rgb, float amount = 1.0f); +Colors::Color checkerboard_darken(Colors::Color color); -inline auto checkerboard_darken(uint32_t rgba) +inline std::array checkerboard_darken(uint32_t rgba) { - return checkerboard_darken(rgb_to_array(rgba), 1.0f - SP_RGBA32_A_U(rgba) / 255.0f); + auto const color = checkerboard_darken(Colors::Color{rgba}); + return {(float)color[0], (float)color[1], (float)color[2]}; } } // namespace Widget diff --git a/src/ui/widget/color-entry.cpp b/src/ui/widget/color-entry.cpp index a4f7f76e3179b1d5b8ac6e4fbcbd1343a6550738..f8b7fb48237013e4f5110e9cc6848f1348f3cb2c 100644 --- a/src/ui/widget/color-entry.cpp +++ b/src/ui/widget/color-entry.cpp @@ -20,17 +20,16 @@ namespace Inkscape { namespace UI { namespace Widget { -ColorEntry::ColorEntry(SelectedColor &color) - : _color(color) +ColorEntry::ColorEntry(std::shared_ptr colors) + : _colors(std::move(colors)) , _updating(false) , _updatingrgba(false) , _prevpos(0) - , _lastcolor(0) + , _lastcolor() { set_name("ColorEntry"); - _color_changed_connection = color.signal_changed.connect(sigc::mem_fun(*this, &ColorEntry::_onColorChanged)); - _color_dragged_connection = color.signal_dragged.connect(sigc::mem_fun(*this, &ColorEntry::_onColorChanged)); + _color_changed_connection = _colors->signal_changed.connect(sigc::mem_fun(*this, &ColorEntry::_onColorChanged)); signal_activate().connect(sigc::mem_fun(*this, &ColorEntry::_onColorChanged)); get_buffer()->signal_inserted_text().connect(sigc::mem_fun(*this, &ColorEntry::_inputCheck)); _onColorChanged(); @@ -44,7 +43,6 @@ ColorEntry::ColorEntry(SelectedColor &color) ColorEntry::~ColorEntry() { _color_changed_connection.disconnect(); - _color_dragged_connection.disconnect(); } void ColorEntry::_inputCheck(guint pos, const gchar * /*chars*/, guint n_chars) @@ -56,74 +54,17 @@ void ColorEntry::_inputCheck(guint pos, const gchar * /*chars*/, guint n_chars) void ColorEntry::on_changed() { - if (_updating) { + if (_updating || _updatingrgba) { return; } - if (_updatingrgba) { - return; // Typing text into entry box - } - - Glib::ustring text = get_text(); - bool changed = false; - - // Coerce the value format to hexadecimal - for (auto it = text.begin(); it != text.end(); /*++it*/) { - if (!g_ascii_isxdigit(*it)) { - text.erase(it); - changed = true; - } else { - ++it; - } - } - - if (text.size() > 8) { - text.erase(_prevpos, 1); - changed = true; - } - // autofill rules - gchar *str = g_strdup(text.c_str()); - gchar *end = nullptr; - guint64 rgba = g_ascii_strtoull(str, &end, 16); - ptrdiff_t len = end - str; - if (len < 8) { - if (len == 0) { - rgba = _lastcolor; - } else if (len <= 2) { - if (len == 1) { - rgba *= 17; - } - rgba = (rgba << 24) + (rgba << 16) + (rgba << 8); - } else if (len <= 4) { - // display as rrggbbaa - rgba = rgba << (4 * (4 - len)); - guint64 r = rgba & 0xf000; - guint64 g = rgba & 0x0f00; - guint64 b = rgba & 0x00f0; - guint64 a = rgba & 0x000f; - rgba = 17 * ((r << 12) + (g << 8) + (b << 4) + a); - } else { - rgba = rgba << (4 * (8 - len)); - } - - if (len == 7) { - rgba = (rgba & 0xfffffff0) + (_lastcolor & 0x00f); - } else if (len == 5) { - rgba = (rgba & 0xfffff000) + (_lastcolor & 0xfff); - } else if (len != 4 && len != 8) { - rgba = (rgba & 0xffffff00) + (_lastcolor & 0x0ff); - } - } + auto new_color = Colors::Color::parse(get_text()); _updatingrgba = true; - if (changed) { - set_text(str); + if (new_color) { + _colors->setAll(*new_color); } - SPColor color(rgba); - _color.setColorAlpha(color, SP_RGBA32_A_F(rgba)); _updatingrgba = false; - - g_free(str); } @@ -133,14 +74,15 @@ void ColorEntry::_onColorChanged() return; } - SPColor color = _color.color(); - gdouble alpha = _color.alpha(); - - _lastcolor = color.toRGBA32(alpha); + if (_colors->isEmpty()) { + set_text(_("N/A")); + return; + } - auto text = Inkscape::ustring::format_classic(std::hex, std::setw(8), std::setfill('0'), _lastcolor); + _lastcolor = _colors->getAverage();; + auto text = _lastcolor->toString(); - Glib::ustring old_text = get_text(); + std::string old_text = get_text(); if (old_text != text) { _updating = true; set_text(text); diff --git a/src/ui/widget/color-entry.h b/src/ui/widget/color-entry.h index 4df80def9cc9979a59f86efbcab6bcf984fe2178..4a487403a2238c4fdcb85f41ce0645020c79c013 100644 --- a/src/ui/widget/color-entry.h +++ b/src/ui/widget/color-entry.h @@ -13,7 +13,9 @@ #define SEEN_COLOR_ENTRY_H #include -#include "ui/selected-color.h" + +#include "colors/color.h" +#include "colors/color-set.h" namespace Inkscape { namespace UI { @@ -22,7 +24,7 @@ namespace Widget { class ColorEntry : public Gtk::Entry { public: - ColorEntry(SelectedColor &color); + ColorEntry(std::shared_ptr color); ~ColorEntry() override; protected: @@ -32,13 +34,13 @@ private: void _onColorChanged(); void _inputCheck(guint pos, const gchar * /*chars*/, guint /*n_chars*/); - SelectedColor &_color; - sigc::connection _color_changed_connection; - sigc::connection _color_dragged_connection; + std::shared_ptr _colors; bool _updating; bool _updatingrgba; - guint32 _lastcolor; int _prevpos; + std::optional _lastcolor; + + sigc::connection _color_changed_connection; }; } diff --git a/src/ui/widget/color-icc-selector.cpp b/src/ui/widget/color-icc-selector.cpp deleted file mode 100644 index edf1000cd04b58cb0feb615f942a3347ad94a3fd..0000000000000000000000000000000000000000 --- a/src/ui/widget/color-icc-selector.cpp +++ /dev/null @@ -1,934 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file - * TODO: insert short description here - *//* - * Authors: see git history - * - * Copyright (C) 2018 Authors - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ - -#include "color-icc-selector.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "colorspace.h" -#include "document.h" -#include "inkscape.h" -#include "profile-manager.h" -#include "ui/dialog-events.h" -#include "ui/util.h" -#include "ui/widget/color-scales.h" -#include "ui/widget/color-slider.h" - -#define noDEBUG_LCMS - -#include "object/color-profile.h" -#include "color/color-profile-cms-fns.h" - -#ifdef DEBUG_LCMS -#include "preferences.h" -#endif // DEBUG_LCMS - -#ifdef DEBUG_LCMS -extern guint update_in_progress; -#define DEBUG_MESSAGE(key, ...) \ - { \ - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); \ - bool dump = prefs->getBool("/options/scislac/" #key); \ - bool dumpD = prefs->getBool("/options/scislac/" #key "D"); \ - bool dumpD2 = prefs->getBool("/options/scislac/" #key "D2"); \ - dumpD && = ((update_in_progress == 0) || dumpD2); \ - if (dump) { \ - g_message(__VA_ARGS__); \ - } \ - if (dumpD) { \ - GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, \ - GTK_BUTTONS_OK, __VA_ARGS__); \ - g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog); \ - gtk_widget_set_visible(dialog, true); \ - } \ - } -#endif // DEBUG_LCMS - -static constexpr int XPAD = 4; -static constexpr int YPAD = 1; - -namespace { - -GtkWidget *ink_combo_box_new_with_model(GtkTreeModel *model) -{ - auto combobox = Gtk::make_managed(); - gtk_combo_box_set_model(combobox->gobj(), model); - return combobox->Gtk::Widget::gobj(); -} - -size_t maxColorspaceComponentCount = 0; - -/** - * Internal variable to track all known colorspaces. - */ -std::set knownColorspaces; - -/** - * Helper function to handle GTK2/GTK3 attachment #ifdef code. - */ -void attachToGridOrTable(GtkWidget *parent, GtkWidget *child, guint left, guint top, guint width, guint height, - bool hexpand = false, bool centered = false, guint xpadding = XPAD, guint ypadding = YPAD) -{ - gtk_widget_set_margin_start(child, xpadding); - gtk_widget_set_margin_end(child, xpadding); - gtk_widget_set_margin_top(child, ypadding); - gtk_widget_set_margin_bottom(child, ypadding); - - if (hexpand) { - gtk_widget_set_hexpand(child, TRUE); - } - - if (centered) { - gtk_widget_set_halign(child, GTK_ALIGN_CENTER); - gtk_widget_set_valign(child, GTK_ALIGN_CENTER); - } - - gtk_grid_attach(GTK_GRID(parent), child, left, top, width, height); -} - -} // namespace - -/* -icSigRgbData -icSigCmykData -icSigCmyData -*/ -#define SPACE_ID_RGB 0 -#define SPACE_ID_CMY 1 -#define SPACE_ID_CMYK 2 - -colorspace::Component::Component() - : name() - , tip() - , scale(1) -{ -} - -colorspace::Component::Component(std::string name, std::string tip, guint scale) - : name(std::move(name)) - , tip(std::move(tip)) - , scale(scale) -{ -} - -static cmsUInt16Number *getScratch() -{ - // bytes per pixel * input channels * width - static std::array scritch; - return scritch.data(); -} - -std::vector colorspace::getColorSpaceInfo(uint32_t space) -{ - static std::map > sets; - if (sets.empty()) { - sets[cmsSigXYZData].emplace_back("_X", "X", 2); // TYPE_XYZ_16 - sets[cmsSigXYZData].emplace_back("_Y", "Y", 1); - sets[cmsSigXYZData].emplace_back("_Z", "Z", 2); - - sets[cmsSigLabData].emplace_back("_L", "L", 100); // TYPE_Lab_16 - sets[cmsSigLabData].emplace_back("_a", "a", 256); - sets[cmsSigLabData].emplace_back("_b", "b", 256); - - // cmsSigLuvData - - sets[cmsSigYCbCrData].emplace_back("_Y", "Y", 1); // TYPE_YCbCr_16 - sets[cmsSigYCbCrData].emplace_back("C_b", "Cb", 1); - sets[cmsSigYCbCrData].emplace_back("C_r", "Cr", 1); - - sets[cmsSigYxyData].emplace_back("_Y", "Y", 1); // TYPE_Yxy_16 - sets[cmsSigYxyData].emplace_back("_x", "x", 1); - sets[cmsSigYxyData].emplace_back("y", "y", 1); - - sets[cmsSigRgbData].emplace_back(_("_R:"), _("Red"), 1); // TYPE_RGB_16 - sets[cmsSigRgbData].emplace_back(_("_G:"), _("Green"), 1); - sets[cmsSigRgbData].emplace_back(_("_B:"), _("Blue"), 1); - - sets[cmsSigGrayData].emplace_back(_("G:"), _("Gray"), 1); // TYPE_GRAY_16 - - sets[cmsSigHsvData].emplace_back(_("_H:"), _("Hue"), 360); // TYPE_HSV_16 - sets[cmsSigHsvData].emplace_back(_("_S:"), _("Saturation"), 1); - sets[cmsSigHsvData].emplace_back("_V:", "Value", 1); - - sets[cmsSigHlsData].emplace_back(_("_H:"), _("Hue"), 360); // TYPE_HLS_16 - sets[cmsSigHlsData].emplace_back(_("_L:"), _("Lightness"), 1); - sets[cmsSigHlsData].emplace_back(_("_S:"), _("Saturation"), 1); - - sets[cmsSigCmykData].emplace_back(_("_C:"), _("Cyan"), 1); // TYPE_CMYK_16 - sets[cmsSigCmykData].emplace_back(_("_M:"), _("Magenta"), 1); - sets[cmsSigCmykData].emplace_back(_("_Y:"), _("Yellow"), 1); - sets[cmsSigCmykData].emplace_back(_("_K:"), _("Black"), 1); - - sets[cmsSigCmyData].emplace_back(_("_C:"), _("Cyan"), 1); // TYPE_CMY_16 - sets[cmsSigCmyData].emplace_back(_("_M:"), _("Magenta"), 1); - sets[cmsSigCmyData].emplace_back(_("_Y:"), _("Yellow"), 1); - - for (auto & set : sets) { - knownColorspaces.insert(set.first); - maxColorspaceComponentCount = std::max(maxColorspaceComponentCount, set.second.size()); - } - } - - std::vector target; - - if (sets.find(space) != sets.end()) { - target = sets[space]; - } - return target; -} - -std::vector colorspace::getColorSpaceInfo(Inkscape::ColorProfile *prof) -{ - return getColorSpaceInfo(asICColorSpaceSig(prof->getColorSpace())); -} - -namespace Inkscape::UI::Widget { - -/** - * Class containing the parts for a single color component's UI presence. - */ -class ComponentUI final { -public: - explicit ComponentUI(colorspace::Component component = {}) - : _component(std::move(component)) - , _adj(nullptr) - , _slider(nullptr) - , _btn(nullptr) - , _label(nullptr) - , _map(4 * 1024, 0xFF) - { - } - - colorspace::Component _component; - Glib::RefPtr _adj; // Component adjustment - Inkscape::UI::Widget::ColorSlider *_slider; - GtkWidget *_btn; // spinbutton - GtkWidget *_label; // Label - std::vector _map; -}; - -/** - * Class that implements the internals of the selector. - */ -class ColorICCSelectorImpl final { -public: - ColorICCSelectorImpl(ColorICCSelector *owner, SelectedColor &color); - - void _adjustmentChanged(Glib::RefPtr const &adjustment); - - void _sliderGrabbed(); - void _sliderReleased(); - void _sliderChanged(); - - static void _profileSelected(GtkWidget *src, gpointer data); - static void _fixupHit(GtkWidget *src, gpointer data); - - void _setProfile(const std::string &profile); - void _switchToProfile(gchar const *name); - - void _updateSliders(gint ignore); - void _profilesChanged(std::string const &name); - - ColorICCSelector *_owner; - SelectedColor &_color; - - gboolean _updating : 1; - gboolean _dragging : 1; - - guint32 _fixupNeeded; - GtkWidget *_fixupBtn; - GtkWidget *_profileSel; - - std::vector _compUI; - - Glib::RefPtr _adj; // Channel adjustment - Inkscape::UI::Widget::ColorSlider *_slider; - GtkWidget *_sbtn; // Spinbutton - GtkWidget *_label; // Label - - std::string _profileName; - Inkscape::ColorProfile *_prof; - guint _profChannelCount; - gulong _profChangedID; -}; - -ColorICCSelector::ColorICCSelector(SelectedColor &color, bool no_alpha) - : _impl{std::make_unique(this, color)} -{ - init(no_alpha); - color.signal_changed.connect(sigc::mem_fun(*this, &ColorICCSelector::_colorChanged)); - color.signal_icc_changed.connect(sigc::mem_fun(*this, &ColorICCSelector::_colorChanged)); -} - -ColorICCSelector::~ColorICCSelector() = default; - -ColorICCSelectorImpl::ColorICCSelectorImpl(ColorICCSelector *owner, SelectedColor &color) - : _owner(owner) - , _color(color) - , _updating(FALSE) - , _dragging(FALSE) - , _fixupNeeded(0) - , _fixupBtn(nullptr) - , _profileSel(nullptr) - , _compUI() - , _adj(nullptr) - , _slider(nullptr) - , _sbtn(nullptr) - , _label(nullptr) - , _profileName() - , _prof(nullptr) - , _profChannelCount(0) - , _profChangedID(0) -{ -} - -void ColorICCSelector::init(bool no_alpha) -{ - gint row = 0; - - _impl->_updating = FALSE; - _impl->_dragging = FALSE; - - GtkWidget *t = GTK_WIDGET(gobj()); - - _impl->_compUI.clear(); - - // Create components - row = 0; - - _impl->_fixupBtn = gtk_button_new_with_label(_("Fix")); - g_signal_connect(G_OBJECT(_impl->_fixupBtn), "clicked", G_CALLBACK(ColorICCSelectorImpl::_fixupHit), - _impl.get()); - gtk_widget_set_sensitive(_impl->_fixupBtn, FALSE); - gtk_widget_set_tooltip_text(_impl->_fixupBtn, _("Fix RGB fallback to match icc-color() value.")); - gtk_widget_set_visible(_impl->_fixupBtn, true); - - attachToGridOrTable(t, _impl->_fixupBtn, 0, row, 1, 1); - - // Combobox and store with 2 columns : label (0) and full name (1) - GtkListStore *store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); - _impl->_profileSel = ink_combo_box_new_with_model(GTK_TREE_MODEL(store)); - - GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); - gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(_impl->_profileSel), renderer, TRUE); - gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(_impl->_profileSel), renderer, "text", 0, nullptr); - - GtkTreeIter iter; - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, 0, _(""), 1, "null", -1); - - gtk_widget_set_visible(_impl->_profileSel, true); - gtk_combo_box_set_active(GTK_COMBO_BOX(_impl->_profileSel), 0); - - attachToGridOrTable(t, _impl->_profileSel, 1, row, 1, 1); - - _impl->_profChangedID = g_signal_connect(G_OBJECT(_impl->_profileSel), "changed", - G_CALLBACK(ColorICCSelectorImpl::_profileSelected), _impl.get()); - - row++; - - // populate the data for colorspaces and channels: - std::vector things = colorspace::getColorSpaceInfo(cmsSigRgbData); - - for (size_t i = 0; i < maxColorspaceComponentCount; i++) { - if (i < things.size()) { - _impl->_compUI.emplace_back(things[i]); - } - else { - _impl->_compUI.emplace_back(); - } - - auto const labelStr = i < things.size() ? things[i].name.c_str() : ""; - _impl->_compUI[i]._label = gtk_label_new_with_mnemonic(labelStr); - - gtk_widget_set_halign(_impl->_compUI[i]._label, GTK_ALIGN_END); - gtk_widget_set_visible(_impl->_compUI[i]._label, true); - - attachToGridOrTable(t, _impl->_compUI[i]._label, 0, row, 1, 1); - - // Adjustment - guint scaleValue = _impl->_compUI[i]._component.scale; - gdouble step = static_cast(scaleValue) / 100.0; - gdouble page = static_cast(scaleValue) / 10.0; - gint digits = (step > 0.9) ? 0 : 2; - _impl->_compUI[i]._adj = Gtk::Adjustment::create(0.0, 0.0, scaleValue, step, page, page); - - // Slider - _impl->_compUI[i]._slider = - Gtk::make_managed(_impl->_compUI[i]._adj); - _impl->_compUI[i]._slider->set_tooltip_text((i < things.size()) ? things[i].tip.c_str() : ""); - _impl->_compUI[i]._slider->set_visible(true); - - attachToGridOrTable(t, _impl->_compUI[i]._slider->Gtk::Widget::gobj(), 1, row, 1, 1, true); - - auto const spinbutton = Gtk::make_managed(_impl->_compUI[i]._adj, step, digits); - _impl->_compUI[i]._btn = spinbutton->Gtk::Widget::gobj(); - spinbutton->set_tooltip_text(i < things.size() ? things[i].tip.c_str() : ""); - sp_dialog_defocus_on_enter(*spinbutton); - gtk_label_set_mnemonic_widget(GTK_LABEL(_impl->_compUI[i]._label), _impl->_compUI[i]._btn); - gtk_widget_set_visible(_impl->_compUI[i]._btn, true); - - attachToGridOrTable(t, _impl->_compUI[i]._btn, 2, row, 1, 1, false, true); - - // Signals - _impl->_compUI[i]._adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*_impl, &ColorICCSelectorImpl::_adjustmentChanged), _impl->_compUI[i]._adj)); - - _impl->_compUI[i]._slider->signal_grabbed.connect(sigc::mem_fun(*_impl, &ColorICCSelectorImpl::_sliderGrabbed)); - _impl->_compUI[i]._slider->signal_released.connect( - sigc::mem_fun(*_impl, &ColorICCSelectorImpl::_sliderReleased)); - _impl->_compUI[i]._slider->signal_value_changed.connect( - sigc::mem_fun(*_impl, &ColorICCSelectorImpl::_sliderChanged)); - - row++; - } - - // Label - _impl->_label = gtk_label_new_with_mnemonic(_("_A:")); - - gtk_widget_set_halign(_impl->_label, GTK_ALIGN_END); - gtk_widget_set_visible(_impl->_label, true); - - attachToGridOrTable(t, _impl->_label, 0, row, 1, 1); - - // Adjustment - _impl->_adj = Gtk::Adjustment::create(0.0, 0.0, 100.0, 1.0, 10.0, 10.0); - - // Slider - _impl->_slider = Gtk::make_managed(_impl->_adj); - _impl->_slider->set_tooltip_text(_("Alpha (opacity)")); - _impl->_slider->set_visible(true); - - attachToGridOrTable(t, _impl->_slider->Gtk::Widget::gobj(), 1, row, 1, 1, true); - - _impl->_slider->setColors(SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 0.0), SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 0.5), - SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 1.0)); - - // Spinbutton - auto const spinbuttonalpha = Gtk::make_managed(_impl->_adj, 1.0); - _impl->_sbtn = spinbuttonalpha->Gtk::Widget::gobj(); - spinbuttonalpha->set_tooltip_text(_("Alpha (opacity)")); - sp_dialog_defocus_on_enter(*spinbuttonalpha); - gtk_label_set_mnemonic_widget(GTK_LABEL(_impl->_label), _impl->_sbtn); - gtk_widget_set_visible(_impl->_sbtn, true); - - if (no_alpha) { - _impl->_slider->set_visible(false); - gtk_widget_set_visible(_impl->_label, false); - gtk_widget_set_visible(_impl->_sbtn, false); - } - - attachToGridOrTable(t, _impl->_sbtn, 2, row, 1, 1, false, true); - - // Signals - _impl->_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*_impl, &ColorICCSelectorImpl::_adjustmentChanged), _impl->_adj)); - - _impl->_slider->signal_grabbed.connect(sigc::mem_fun(*_impl, &ColorICCSelectorImpl::_sliderGrabbed)); - _impl->_slider->signal_released.connect(sigc::mem_fun(*_impl, &ColorICCSelectorImpl::_sliderReleased)); - _impl->_slider->signal_value_changed.connect(sigc::mem_fun(*_impl, &ColorICCSelectorImpl::_sliderChanged)); - - gtk_widget_set_visible(t, true); -} - -void ColorICCSelectorImpl::_fixupHit(GtkWidget * /*src*/, gpointer data) -{ - ColorICCSelectorImpl *self = reinterpret_cast(data); - gtk_widget_set_sensitive(self->_fixupBtn, FALSE); - self->_adjustmentChanged(self->_compUI[0]._adj); -} - -void ColorICCSelectorImpl::_profileSelected(GtkWidget * /*src*/, gpointer data) -{ - ColorICCSelectorImpl *self = reinterpret_cast(data); - - GtkTreeIter iter; - if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(self->_profileSel), &iter)) { - GtkTreeModel *store = gtk_combo_box_get_model(GTK_COMBO_BOX(self->_profileSel)); - gchar *name = nullptr; - - gtk_tree_model_get(store, &iter, 1, &name, -1); - self->_switchToProfile(name); - gtk_widget_set_tooltip_text(self->_profileSel, name); - - g_free(name); - } -} - -void ColorICCSelectorImpl::_switchToProfile(gchar const *name) -{ - bool dirty = false; - SPColor tmp(_color.color()); - - if (name && std::string(name) != "null") { - if (tmp.getColorProfile() == name) { -#ifdef DEBUG_LCMS - g_message("Already at name [%s]", name); -#endif // DEBUG_LCMS - } - else { -#ifdef DEBUG_LCMS - g_message("Need to switch to profile [%s]", name); -#endif // DEBUG_LCMS - - if (auto newProf = SP_ACTIVE_DOCUMENT->getProfileManager().find(name)) { - cmsHTRANSFORM trans = newProf->getTransfFromSRGB8(); - if (trans) { - guint32 val = _color.color().toRGBA32(0); - guchar pre[4] = { - static_cast(SP_RGBA32_R_U(val)), - static_cast(SP_RGBA32_G_U(val)), - static_cast(SP_RGBA32_B_U(val)), - 255}; -#ifdef DEBUG_LCMS - g_message("Shoving in [%02x] [%02x] [%02x]", pre[0], pre[1], pre[2]); -#endif // DEBUG_LCMS - cmsUInt16Number post[4] = { 0, 0, 0, 0 }; - cmsDoTransform(trans, pre, post, 1); -#ifdef DEBUG_LCMS - g_message("got on out [%04x] [%04x] [%04x] [%04x]", post[0], post[1], post[2], post[3]); -#endif // DEBUG_LCMS - guint count = cmsChannelsOf(asICColorSpaceSig(newProf->getColorSpace())); - - std::vector things = - colorspace::getColorSpaceInfo(asICColorSpaceSig(newProf->getColorSpace())); - - std::vector colors; - for (guint i = 0; i < count; i++) { - gdouble val = - (((gdouble)post[i]) / 65535.0) * (gdouble)((i < things.size()) ? things[i].scale : 1); -#ifdef DEBUG_LCMS - g_message(" scaled %d by %d to be %f", i, ((i < things.size()) ? things[i].scale : 1), val); -#endif // DEBUG_LCMS - colors.push_back(val); - } - - cmsHTRANSFORM retrans = newProf->getTransfToSRGB8(); - if (retrans) { - cmsDoTransform(retrans, post, pre, 1); -#ifdef DEBUG_LCMS - g_message(" back out [%02x] [%02x] [%02x]", pre[0], pre[1], pre[2]); -#endif // DEBUG_LCMS - tmp.set(SP_RGBA32_U_COMPOSE(pre[0], pre[1], pre[2], 0xff)); - tmp.setColorProfile(newProf); - tmp.setColors(std::move(colors)); - } else { - g_warning("Couldn't get sRGB from color profile."); - } - - dirty = true; - } - } - } - } - else { -#ifdef DEBUG_LCMS - g_message("NUKE THE ICC"); -#endif // DEBUG_LCMS - if (tmp.hasColorProfile()) { - tmp.unsetColorProfile(); - dirty = true; - _fixupHit(nullptr, this); - } - else { -#ifdef DEBUG_LCMS - g_message("No icc to nuke"); -#endif // DEBUG_LCMS - } - } - - if (dirty) { -#ifdef DEBUG_LCMS - g_message("+----------------"); - g_message("+ new color is [%s]", tmp.toString().c_str()); -#endif // DEBUG_LCMS - _setProfile(tmp.getColorProfile()); - _color.setColor(tmp); -#ifdef DEBUG_LCMS - g_message("+_________________"); -#endif // DEBUG_LCMS - } -} - -struct _cmp { - bool operator()(const SPObject * const & a, const SPObject * const & b) - { - const Inkscape::ColorProfile &a_prof = reinterpret_cast(*a); - const Inkscape::ColorProfile &b_prof = reinterpret_cast(*b); - gchar *a_name_casefold = g_utf8_casefold(a_prof.name, -1 ); - gchar *b_name_casefold = g_utf8_casefold(b_prof.name, -1 ); - int result = g_strcmp0(a_name_casefold, b_name_casefold); - g_free(a_name_casefold); - g_free(b_name_casefold); - return result < 0; - } -}; - -template -struct static_caster { To * operator () (From * value) const { return static_cast(value); } }; - -void ColorICCSelectorImpl::_profilesChanged(std::string const &name) -{ - GtkComboBox *combo = GTK_COMBO_BOX(_profileSel); - - g_signal_handler_block(G_OBJECT(_profileSel), _profChangedID); - - GtkListStore *store = GTK_LIST_STORE(gtk_combo_box_get_model(combo)); - gtk_list_store_clear(store); - - GtkTreeIter iter; - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, 0, _(""), 1, "null", -1); - - gtk_combo_box_set_active(combo, 0); - - int index = 1; - std::vector current = SP_ACTIVE_DOCUMENT->getResourceList("iccprofile"); - - std::set _current; - std::transform(current.begin(), - current.end(), - std::inserter(_current, _current.begin()), - static_caster()); - - for (auto &it: _current) { - Inkscape::ColorProfile *prof = it; - - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, 0, ink_ellipsize_text(prof->name, 25).c_str(), 1, prof->name, -1); - - if (name == prof->name) { - gtk_combo_box_set_active(combo, index); - gtk_widget_set_tooltip_text(_profileSel, prof->name); - } - - index++; - } - - g_signal_handler_unblock(G_OBJECT(_profileSel), _profChangedID); -} - -void ColorICCSelector::on_show() -{ - Gtk::Grid::on_show(); - _colorChanged(); -} - -// Helpers for setting color value - -void ColorICCSelector::_colorChanged() -{ - _impl->_updating = TRUE; - auto color = _impl->_color.color(); - auto name = color.getColorProfile(); - -#ifdef DEBUG_LCMS - g_message("/^^^^^^^^^ %p::_colorChanged(%08x:%s)", this, color.toRGBA32(_impl->_color.alpha()), name.c_str()); -#endif // DEBUG_LCMS - - _impl->_profilesChanged(name); - ColorScales<>::setScaled(_impl->_adj, _impl->_color.alpha()); - - _impl->_setProfile(name); - _impl->_fixupNeeded = 0; - gtk_widget_set_sensitive(_impl->_fixupBtn, FALSE); - - if (_impl->_prof) { - if (_impl->_prof->getTransfToSRGB8()) { - cmsUInt16Number tmp[4]; - for (guint i = 0; i < _impl->_profChannelCount; i++) { - auto colors = color.getColors(); - gdouble val = 0.0; - if (colors.size() > i) { - auto scale = static_cast(_impl->_compUI[i]._component.scale); - if (_impl->_compUI[i]._component.scale == 256) { - val = (colors[i] + 128.0) / scale; - } - else { - val = colors[i] / scale; - } - } - tmp[i] = val * 0x0ffff; - } - guchar post[4] = { 0, 0, 0, 0 }; - cmsHTRANSFORM trans = _impl->_prof->getTransfToSRGB8(); - if (trans) { - cmsDoTransform(trans, tmp, post, 1); - guint32 other = SP_RGBA32_U_COMPOSE(post[0], post[1], post[2], 255); - if (other != color.toRGBA32(255)) { - _impl->_fixupNeeded = other; - gtk_widget_set_sensitive(_impl->_fixupBtn, TRUE); -#ifdef DEBUG_LCMS - g_message("Color needs to change 0x%06x to 0x%06x", color.toRGBA32(255) >> 8, other >> 8); -#endif // DEBUG_LCMS - } - } - } - } - _impl->_updateSliders(-1); - - _impl->_updating = FALSE; -#ifdef DEBUG_LCMS - g_message("\\_________ %p::_colorChanged()", this); -#endif // DEBUG_LCMS -} - -void ColorICCSelectorImpl::_setProfile(const std::string &profile) -{ -#ifdef DEBUG_LCMS - g_message("/^^^^^^^^^ %p::_setProfile(%s)", this, profile.c_str()); -#endif // DEBUG_LCMS - bool profChanged = false; - if (_prof && _profileName != profile) { - // Need to clear out the prior one - profChanged = true; - _profileName.clear(); - _prof = nullptr; - _profChannelCount = 0; - } else if (!_prof && !profile.empty()) { - profChanged = true; - } - - for (auto & i : _compUI) { - gtk_widget_set_visible(i._label, false); - i._slider->set_visible(false); - gtk_widget_set_visible(i._btn, false); - } - - if (!profile.empty()) { - _prof = SP_ACTIVE_DOCUMENT->getProfileManager().find(profile.c_str()); - if (_prof && (asICColorProfileClassSig(_prof->getProfileClass()) != cmsSigNamedColorClass)) { - _profChannelCount = _prof->getChannelCount(); - - if (profChanged) { - std::vector things = - colorspace::getColorSpaceInfo(asICColorSpaceSig(_prof->getColorSpace())); - for (size_t i = 0; (i < things.size()) && (i < _profChannelCount); ++i) { - _compUI[i]._component = things[i]; - } - - for (guint i = 0; i < _profChannelCount; i++) { - gtk_label_set_text_with_mnemonic(GTK_LABEL(_compUI[i]._label), - (i < things.size()) ? things[i].name.c_str() : ""); - - _compUI[i]._slider->set_tooltip_text((i < things.size()) ? things[i].tip.c_str() : ""); - gtk_widget_set_tooltip_text(_compUI[i]._btn, (i < things.size()) ? things[i].tip.c_str() : ""); - - _compUI[i]._slider->setColors(SPColor(0.0, 0.0, 0.0).toRGBA32(0xff), - SPColor(0.5, 0.5, 0.5).toRGBA32(0xff), - SPColor(1.0, 1.0, 1.0).toRGBA32(0xff)); - gtk_widget_set_visible(_compUI[i]._label, true); - _compUI[i]._slider->set_visible(true); - gtk_widget_set_visible(_compUI[i]._btn, true); - } - for (size_t i = _profChannelCount; i < _compUI.size(); i++) { - gtk_widget_set_visible(_compUI[i]._label, false); - _compUI[i]._slider->set_visible(false); - gtk_widget_set_visible(_compUI[i]._btn, false); - } - } - } - else { - // Give up for now on named colors - _prof = nullptr; - } - } - -#ifdef DEBUG_LCMS - g_message("\\_________ %p::_setProfile()", this); -#endif // DEBUG_LCMS -} - -void ColorICCSelectorImpl::_updateSliders(gint ignore) -{ - _slider->set_sensitive(false); - - if (_color.color().hasColorProfile()) { - auto colors = _color.color().getColors(); - if (colors.size() != _profChannelCount) { - g_warning("Can't set profile with %d colors to %d channels", (int)colors.size(), _profChannelCount); - } - for (guint i = 0; i < _profChannelCount; i++) { - double val = 0.0; - auto scale = static_cast(_compUI[i]._component.scale); - if (_compUI[i]._component.scale == 256) { - val = (colors[i] + 128.0) / scale; - } else { - val = colors[i] / scale; - } - _compUI[i]._adj->set_value(val); - } - - if (_prof) { - _slider->set_sensitive(true); - - if (_prof->getTransfToSRGB8()) { - for (guint i = 0; i < _profChannelCount; i++) { - if (static_cast(i) != ignore) { - cmsUInt16Number *scratch = getScratch(); - cmsUInt16Number filler[4] = { 0, 0, 0, 0 }; - for (guint j = 0; j < _profChannelCount; j++) { - filler[j] = 0x0ffff * ColorScales<>::getScaled(_compUI[j]._adj); - } - - cmsUInt16Number *p = scratch; - for (guint x = 0; x < 1024; x++) { - for (guint j = 0; j < _profChannelCount; j++) { - if (j == i) { - *p++ = x * 0x0ffff / 1024; - } - else { - *p++ = filler[j]; - } - } - } - - cmsHTRANSFORM trans = _prof->getTransfToSRGB8(); - if (trans) { - cmsDoTransform(trans, scratch, _compUI[i]._map.data(), 1024); - if (_compUI[i]._slider) { - _compUI[i]._slider->setMap(_compUI[i]._map.data()); - } - } - } - } - } - } - } - - guint32 start = _color.color().toRGBA32(0x00); - guint32 mid = _color.color().toRGBA32(0x7f); - guint32 end = _color.color().toRGBA32(0xff); - - _slider->setColors(start, mid, end); -} - -void ColorICCSelectorImpl::_adjustmentChanged(Glib::RefPtr const &adjustment) -{ -#ifdef DEBUG_LCMS - g_message("/^^^^^^^^^ %p::_adjustmentChanged()", this); -#endif // DEBUG_LCMS - - ColorICCSelector *iccSelector = _owner; - if (iccSelector->_impl->_updating) { - return; - } - - iccSelector->_impl->_updating = TRUE; - - gint match = -1; - - SPColor newColor(iccSelector->_impl->_color.color()); - gfloat scaled = ColorScales<>::getScaled(iccSelector->_impl->_adj); - if (iccSelector->_impl->_adj == adjustment) { -#ifdef DEBUG_LCMS - g_message("ALPHA"); -#endif // DEBUG_LCMS - } - else { - for (size_t i = 0; i < iccSelector->_impl->_compUI.size(); i++) { - if (iccSelector->_impl->_compUI[i]._adj == adjustment) { - match = i; - break; - } - } - if (match >= 0) { -#ifdef DEBUG_LCMS - g_message(" channel %d", match); -#endif // DEBUG_LCMS - } - - cmsUInt16Number tmp[4]; - for (guint i = 0; i < 4; i++) { - tmp[i] = ColorScales<>::getScaled(iccSelector->_impl->_compUI[i]._adj) * 0x0ffff; - } - guchar post[4] = { 0, 0, 0, 0 }; - - cmsHTRANSFORM trans = iccSelector->_impl->_prof->getTransfToSRGB8(); - if (trans) { - cmsDoTransform(trans, tmp, post, 1); - } - - // Set the sRGB version of the color first. - guint32 prior = iccSelector->_impl->_color.color().toRGBA32(255); - guint32 newer = SP_RGBA32_U_COMPOSE(post[0], post[1], post[2], 255); - - if (prior != newer) { -#ifdef DEBUG_LCMS - g_message("Transformed color from 0x%08x to 0x%08x", prior, newer); - g_message(" ~~~~ FLIP"); -#endif // DEBUG_LCMS - - // Be careful to always set() and then setColors() to retain ICC data. - newColor.set(newer); - if (iccSelector->_impl->_color.color().hasColorProfile()) { - std::vector colors; - for (guint i = 0; i < iccSelector->_impl->_profChannelCount; i++) { - double val = ColorScales<>::getScaled(iccSelector->_impl->_compUI[i]._adj); - val *= iccSelector->_impl->_compUI[i]._component.scale; - if (iccSelector->_impl->_compUI[i]._component.scale == 256) { - val -= 128; - } - colors.push_back(val); - } - newColor.setColors(std::move(colors)); - } - } - } - iccSelector->_impl->_color.setColorAlpha(newColor, scaled); - iccSelector->_impl->_updateSliders(match); - - iccSelector->_impl->_updating = FALSE; -#ifdef DEBUG_LCMS - g_message("\\_________ %p::_adjustmentChanged()", this); -#endif // DEBUG_LCMS -} - -void ColorICCSelectorImpl::_sliderGrabbed() -{ -} - -void ColorICCSelectorImpl::_sliderReleased() -{ -} - -void ColorICCSelectorImpl::_sliderChanged() -{ -} - -Gtk::Widget *ColorICCSelectorFactory::createWidget(Inkscape::UI::SelectedColor &color, bool no_alpha) const -{ - return Gtk::make_managed(color, no_alpha); -} - -Glib::ustring ColorICCSelectorFactory::modeName() const -{ - return _("CMS"); -} - -} // namespace Inkscape::UI::widget - -/* - 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 : diff --git a/src/ui/widget/color-icc-selector.h b/src/ui/widget/color-icc-selector.h deleted file mode 100644 index 9f8afee259afff66398ccbd81fccfeaa0bdf37f2..0000000000000000000000000000000000000000 --- a/src/ui/widget/color-icc-selector.h +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file - * TODO: insert short description here - *//* - * Authors: see git history - * - * Copyright (C) 2018 Authors - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ -#ifndef SEEN_SP_COLOR_ICC_SELECTOR_H -#define SEEN_SP_COLOR_ICC_SELECTOR_H - -#include -#include -#include -#include "ui/selected-color.h" - -namespace Inkscape { - -class ColorProfile; - -namespace UI::Widget { - -class ColorICCSelectorImpl; - -class ColorICCSelector final - : public Gtk::Grid - { -public: - ColorICCSelector(SelectedColor &color, bool no_alpha); - ~ColorICCSelector() final; - - ColorICCSelector(const ColorICCSelector &obj) = delete; - ColorICCSelector &operator=(const ColorICCSelector &obj) = delete; - - void init(bool no_alpha); - -protected: - void on_show() final; - - virtual void _colorChanged(); - - void _recalcColor(bool changing); - -private: - friend class ColorICCSelectorImpl; - std::unique_ptr _impl; -}; - - -class ColorICCSelectorFactory final : public ColorSelectorFactory { -public: - Gtk::Widget *createWidget(SelectedColor &color, bool no_alpha) const final; - Glib::ustring modeName() const final; -}; - -} // namespace UI::Widget - -} // namespace Inkscape - -#endif // SEEN_SP_COLOR_ICC_SELECTOR_H - -/* - 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 : diff --git a/src/ui/widget/color-notebook.cpp b/src/ui/widget/color-notebook.cpp index b1884015e57439dbeafbfce375b0fdc7cc1dc823..3a833f247730d492d7c3ec832e62140952038ca4 100644 --- a/src/ui/widget/color-notebook.cpp +++ b/src/ui/widget/color-notebook.cpp @@ -26,8 +26,6 @@ #include "document.h" #include "inkscape.h" #include "preferences.h" -#include "profile-manager.h" -#include "color/cms-system.h" #include "object/color-profile.h" #include "ui/dialog-events.h" #include "ui/icon-loader.h" @@ -38,22 +36,20 @@ #include "ui/widget/color-scales.h" #include "ui/widget/icon-combobox.h" -using Inkscape::CMSSystem; - static constexpr int XPAD = 2; static constexpr int YPAD = 1; namespace Inkscape::UI::Widget { -ColorNotebook::ColorNotebook(SelectedColor &color, bool no_alpha) - : _selected_color(color) +ColorNotebook::ColorNotebook(SelectedColor color) + : _selected_color(std::move(color)) { + assert(_selected_color); set_name("ColorNotebook"); - _initUI(no_alpha); + _initUI(!_selected_color->getAlphaConstraint().value_or(true)); - _selected_color.signal_changed.connect(sigc::mem_fun(*this, &ColorNotebook::_onSelectedColorChanged)); - _selected_color.signal_dragged.connect(sigc::mem_fun(*this, &ColorNotebook::_onSelectedColorChanged)); + //_selected_color.signal_changed.connect(sigc::mem_fun(*this, &ColorNotebook::_onSelectedColorChanged)); auto desktop = SP_ACTIVE_DESKTOP; _doc_replaced_connection = desktop->connectDocumentReplaced(sigc::hide<0>(sigc::mem_fun(*this, &ColorNotebook::setDocument))); @@ -76,12 +72,12 @@ ColorNotebook::Page::Page(std::unique_ptr se void ColorNotebook::setDocument(SPDocument *document) { _document = document; - _icc_changed_connection.disconnect(); + /*_icc_changed_connection.disconnect(); if (document) { _icc_changed_connection = document->connectResourcesChanged("iccprofile", [this]() { _selected_color.emitIccChanged(); }); - } + }*/ } void ColorNotebook::set_label(const Glib::ustring& label) { @@ -230,20 +226,18 @@ void ColorNotebook::_onPickerClicked(GtkWidget * /*widget*/, ColorNotebook *colo Inkscape::UI::Tools::sp_toggle_dropper(SP_ACTIVE_DESKTOP); auto tool = dynamic_cast(SP_ACTIVE_DESKTOP->getTool()); if (tool) { - colorbook->_onetimepick = tool->onetimepick_signal.connect(sigc::mem_fun(*colorbook, &ColorNotebook::_pickColor)); + colorbook->_onetimepick = tool->onetimepick_signal.connect([colorbook](Colors::Color const &color) { + // Set color to color notebook here. + colorbook->_selected_color->setAll(color); + //_onSelectedColorChanged(); + }); } } } -void ColorNotebook::_pickColor(ColorRGBA *color) { - // Set color to color notebook here. - _selected_color.setValue(color->getIntValue()); - _onSelectedColorChanged(); -} +//void ColorNotebook::_onSelectedColorChanged() { _updateICCButtons(); } -void ColorNotebook::_onSelectedColorChanged() { _updateICCButtons(); } - -void ColorNotebook::_updateICCButtons() +/*void ColorNotebook::_updateICCButtons() { if (!_document) { return; @@ -254,7 +248,6 @@ void ColorNotebook::_updateICCButtons() g_return_if_fail((0.0 <= alpha) && (alpha <= 1.0)); - /* update color management icon*/ gtk_widget_set_sensitive(_colormanaged, color.hasColorProfile()); gtk_widget_set_sensitive(_toomuchink, false); gtk_widget_set_sensitive(_outofgamut, false); @@ -265,13 +258,11 @@ void ColorNotebook::_updateICCButtons() // Set notebook page to cms if icc profile being used. _setCurrentPage(getPageIndex("CMS"), true); - /* update out-of-gamut icon */ Inkscape::ColorProfile *target_profile = _document->getProfileManager().find(name.c_str()); if (target_profile) gtk_widget_set_sensitive(_outofgamut, target_profile->GamutCheck(color)); - /* update too-much-ink icon */ Inkscape::ColorProfile *prof = _document->getProfileManager().find(name.c_str()); if (prof && prof->isPrintColorSpace()) { gtk_widget_set_visible(_toomuchink, true); @@ -280,9 +271,6 @@ void ColorNotebook::_updateICCButtons() ink_sum += i; } - /* Some literature states that when the sum of paint values exceed 320%, it is considered to be a satured color, - which means the paper can get too wet due to an excessive amount of ink. This may lead to several issues - such as misalignment and poor quality of printing in general.*/ if (ink_sum > 3.2) gtk_widget_set_sensitive(_toomuchink, true); } @@ -294,7 +282,7 @@ void ColorNotebook::_updateICCButtons() auto page = prefs->getString("/colorselector/page"); _setCurrentPage(getPageIndex(page), true); } -} +}*/ int ColorNotebook::getPageIndex(const Glib::ustring &name) { diff --git a/src/ui/widget/color-notebook.h b/src/ui/widget/color-notebook.h index b0e14f092a138e2b179b6bd7ed9d436ffadb2ca8..831dd6ef5f1c777a15e519c3ddefbe36f48f0d7b 100644 --- a/src/ui/widget/color-notebook.h +++ b/src/ui/widget/color-notebook.h @@ -21,10 +21,10 @@ #include // for GtkWidget, Widget (ptr only) #include // for connection +#include "colors/color-set.h" +#include "color-scales.h" #include "preferences.h" // for PrefObserver -#include "ui/selected-color.h" // for ColorSelectorFactory, SelectedColor (... -class ColorRGBA; class SPDocument; namespace Gtk { @@ -42,7 +42,7 @@ class ColorNotebook : public Gtk::Grid { public: - ColorNotebook(SelectedColor &color, bool no_alpha = false); + ColorNotebook(SelectedColor color); ~ColorNotebook() override; void set_label(const Glib::ustring& label); @@ -59,16 +59,14 @@ protected: void _addPage(Page &page, bool no_alpha, const Glib::ustring vpath); void setDocument(SPDocument *document); - void _pickColor(ColorRGBA *color); static void _onPickerClicked(GtkWidget *widget, ColorNotebook *colorbook); - virtual void _onSelectedColorChanged(); + //virtual void _onSelectedColorChanged(); int getPageIndex(const Glib::ustring &name); int getPageIndex(Gtk::Widget *widget); void _updateICCButtons(); void _setCurrentPage(int i, bool sync_combo); - Inkscape::UI::SelectedColor &_selected_color; unsigned long _entryId = 0; Gtk::Stack* _book = nullptr; Gtk::StackSwitcher* _switcher = nullptr; @@ -89,7 +87,7 @@ private: SPDocument *_document = nullptr; sigc::connection _doc_replaced_connection; - sigc::connection _icc_changed_connection; + SelectedColor _selected_color; }; } // namespace Inkscape::UI::Widget diff --git a/src/ui/widget/color-picker.cpp b/src/ui/widget/color-picker.cpp index 4544dd6970c5a199f10cdc7895ac144dc43356ce..cc14d0a5987f020e47e156800c3fe4d1994f98b4 100644 --- a/src/ui/widget/color-picker.cpp +++ b/src/ui/widget/color-picker.cpp @@ -13,10 +13,11 @@ #include "color-picker.h" +#include + #include "desktop.h" #include "document-undo.h" #include "inkscape.h" - #include "ui/dialog-events.h" #include "ui/widget/color-notebook.h" @@ -24,32 +25,48 @@ static bool _in_use = false; namespace Inkscape::UI::Widget { -ColorPicker::ColorPicker(Glib::ustring const &title, +ColorPicker::ColorPicker(Glib::ustring title, Glib::ustring const &tip, - std::uint32_t const rgba, + Colors::Color const &initial, bool const undo, - Gtk::Button * const external_button) - : _preview(Gtk::make_managed(rgba)) - , _title(title) - , _rgba(rgba) + bool use_transparency) + : _preview(Gtk::make_managed(initial.toRGBA())) + , _title(std::move(title)) , _undo(undo) + , _colors(std::make_shared(nullptr, use_transparency)) { - auto button = external_button ? external_button : this; - button->set_child(*_preview); // set tooltip if given, otherwise leave original tooltip in place (from external button) if (!tip.empty()) { - button->set_tooltip_text(tip); + set_tooltip_text(tip); } - _selected_color.signal_changed.connect(sigc::mem_fun(*this, &ColorPicker::_onSelectedColorChanged)); - _selected_color.signal_dragged.connect(sigc::mem_fun(*this, &ColorPicker::_onSelectedColorChanged)); - _selected_color.signal_released.connect(sigc::mem_fun(*this, &ColorPicker::_onSelectedColorChanged)); + _colors->set(initial); + _construct(); +} + +ColorPicker::ColorPicker(BaseObjectType *cobject, Glib::RefPtr const &, + Glib::ustring title, bool use_transparency) + : Gtk::Button(cobject) + , _preview(Gtk::make_managed(0x0)) + , _title(std::move(title)) + , _colors(std::make_shared(nullptr, use_transparency)) +{ + _construct(); +} + +void ColorPicker::_construct() +{ + set_child(*_preview); + + _colors->signal_changed.connect(sigc::mem_fun(*this, &ColorPicker::_onSelectedColorChanged)); + _colors->signal_released.connect(sigc::mem_fun(*this, &ColorPicker::_onSelectedColorChanged)); + + auto color_selector = Gtk::make_managed(_colors); + color_selector->set_label(_title); + color_selector->set_margin(4); - if (external_button) { - external_button->signal_clicked().connect([this] { on_clicked(); }); - } - sp_transientize(_colorSelectorDialog); - _colorSelectorDialog.set_title(title); + _colorSelectorDialog.set_child(*color_selector); + _colorSelectorDialog.set_title(_title); _colorSelectorDialog.set_hide_on_close(); property_sensitive().signal_changed().connect([this](){ @@ -59,19 +76,14 @@ ColorPicker::ColorPicker(Glib::ustring const &title, ColorPicker::~ColorPicker() = default; -void ColorPicker::setRgba32(std::uint32_t const rgba) +void ColorPicker::setColor(Colors::Color const &color) { if (_in_use) return; - set_preview(rgba); - _rgba = rgba; - - if (_color_selector) - { - _updating = true; - _selected_color.setValue(rgba); - _updating = false; - } + _updating = true; + set_preview(color.toRGBA()); + _colors->set(color); + _updating = false; } void ColorPicker::closeWindow() @@ -85,61 +97,36 @@ void ColorPicker::open() { void ColorPicker::on_clicked() { - if (!_color_selector) { - _color_selector = Gtk::make_managed(_selected_color, _ignore_transparency); - _color_selector->set_label(_title); - _color_selector->set_margin(4); - _colorSelectorDialog.set_child(*_color_selector); - } - - _updating = true; - _selected_color.setValue(_rgba); - _updating = false; - _colorSelectorDialog.present(); } -void ColorPicker::on_changed(std::uint32_t) +void ColorPicker::on_changed(Colors::Color const &color) { } -void ColorPicker::_onSelectedColorChanged() { - if (_updating) { - return; - } - - if (_in_use) { +void ColorPicker::_onSelectedColorChanged() +{ + if (_updating || _in_use) return; - } else { - _in_use = true; - } - - auto const rgba = _selected_color.value(); - set_preview(rgba); if (_undo && SP_ACTIVE_DESKTOP) { DocumentUndo::done(SP_ACTIVE_DESKTOP->getDocument(), /* TODO: annotate */ "color-picker.cpp:129", ""); } - on_changed(rgba); + _in_use = true; + if (auto color = _colors->get()) { + set_preview(color->toRGBA()); + on_changed(*color); + _changed_signal.emit(*color); + } _in_use = false; - _rgba = rgba; - _changed_signal.emit(rgba); -} - -void ColorPicker::set_preview(std::uint32_t const rgba) -{ - _preview->setRgba32(_ignore_transparency ? rgba | 0xff : rgba); -} -void ColorPicker::use_transparency(bool enable) { - _ignore_transparency = !enable; - set_preview(_rgba); } -std::uint32_t ColorPicker::get_current_color() const +void ColorPicker::set_preview(std::uint32_t const rgba) { - return _rgba; + bool has_alpha = _colors->getAlphaConstraint().value_or(true); + _preview->setRgba32(has_alpha ? rgba : rgba | 0xff); } } // namespace Inkscape::UI::Widget diff --git a/src/ui/widget/color-picker.h b/src/ui/widget/color-picker.h index b4799b4238db224ac89abbb4bc0ccb4201273c3d..8ab68493b8a151f26cefe4c04828ee0b389ee047 100644 --- a/src/ui/widget/color-picker.h +++ b/src/ui/widget/color-picker.h @@ -19,13 +19,16 @@ #include #include -#include "ui/selected-color.h" +#include "colors/color.h" +#include "colors/color-set.h" #include "ui/widget/color-preview.h" #include #include #include -struct SPColorSelector; +namespace Gtk { +class Builder; +} namespace Inkscape::UI::Widget { @@ -33,49 +36,52 @@ class ColorNotebook; class ColorPicker : public Gtk::Button { public: - [[nodiscard]] ColorPicker(Glib::ustring const &title, + [[nodiscard]] ColorPicker(Glib::ustring title, Glib::ustring const &tip, - std::uint32_t rgba, + Colors::Color const &initial, bool undo, - Gtk::Button *external_button = nullptr); + bool use_transparency = true); + + ColorPicker(BaseObjectType *cobject, Glib::RefPtr const &, + Glib::ustring title, bool use_transparency = true); ~ColorPicker() override; - void setRgba32(std::uint32_t rgba); - void setSensitive(bool sensitive) { set_sensitive(sensitive); } + void setColor(Colors::Color const &); void open(); void closeWindow(); - sigc::connection connectChanged(sigc::slot slot) + sigc::connection connectChanged(sigc::slot slot) { return _changed_signal.connect(std::move(slot)); } - void use_transparency(bool enable); - [[nodiscard]] std::uint32_t get_current_color() const; + [[nodiscard]] Colors::Color get_current_color() const { + if (_colors->isEmpty()) + return Colors::Color(0x0); + return _colors->getAverage(); + } protected: void _onSelectedColorChanged(); void on_clicked() override; - virtual void on_changed(std::uint32_t); + virtual void on_changed(Colors::Color const &); ColorPreview *_preview = nullptr; Glib::ustring _title; - sigc::signal _changed_signal; - std::uint32_t _rgba = 0 ; + sigc::signal _changed_signal; bool _undo = false; bool _updating = false; - Gtk::Window _colorSelectorDialog; + void setupDialog(Glib::ustring const &title); + Gtk::Dialog _colorSelectorDialog; - SelectedColor _selected_color; + std::shared_ptr _colors; private: + void _construct(); void set_preview(std::uint32_t rgba); - - ColorNotebook *_color_selector = nullptr; - bool _ignore_transparency = false; }; @@ -84,18 +90,17 @@ public: [[nodiscard]] LabelledColorPicker(Glib::ustring const &label, Glib::ustring const &title, Glib::ustring const &tip, - std::uint32_t rgba, + Colors::Color const &initial, bool undo) - : Labelled(label, tip, new ColorPicker(title, tip, rgba, undo)) { - + : Labelled(label, tip, new ColorPicker(title, tip, initial, undo)) { property_sensitive().signal_changed().connect([this](){ getWidget()->set_sensitive(is_sensitive()); }); } - void setRgba32(std::uint32_t const rgba) + void setColor(Colors::Color const &color) { - static_cast(getWidget())->setRgba32(rgba); + static_cast(getWidget())->setColor(color); } void closeWindow() @@ -103,7 +108,7 @@ public: static_cast(getWidget())->closeWindow(); } - sigc::connection connectChanged(sigc::slot slot) + sigc::connection connectChanged(sigc::slot slot) { return static_cast(getWidget())->connectChanged(std::move(slot)); } diff --git a/src/ui/widget/color-preview.cpp b/src/ui/widget/color-preview.cpp index 23a940c4c707d677d93485263830d9a7e4a8bb06..3d88806fba72edb2d6674c0b3c3386a2e64cddad 100644 --- a/src/ui/widget/color-preview.cpp +++ b/src/ui/widget/color-preview.cpp @@ -17,7 +17,8 @@ #include #include "display/cairo-utils.h" -#include "hsluv.h" +#include "colors/color.h" +#include "colors/spaces/enum.h" namespace Inkscape::UI::Widget { @@ -62,14 +63,12 @@ void ColorPreview::draw_func(Cairo::RefPtr const &cr, cairo_pattern_t *checkers = ink_cairo_pattern_create_checkerboard(); cairo_set_source(cr->cobj(), checkers); cr->fill_preserve(); - auto color = _rgba; + auto color = Colors::Color(_rgba); // if preview is disabled render colors in gray if (!_enabled) { - auto l = Hsluv::rgb_to_perceptual_lightness(Hsluv::Triplet{SP_RGBA32_R_F(color), SP_RGBA32_G_F(color), SP_RGBA32_B_F(color)}); - l = Hsluv::from_linear(l); - color = SP_RGBA32_F_COMPOSE(l, l, l, (color & 0xff) / 255.0); + color.convert(Colors::Space::Type::Gray); } - ink_cairo_set_source_rgba32(cr->cobj(), color); + ink_cairo_set_source_color(cr, color); cr->fill(); cairo_pattern_destroy(checkers); @@ -81,7 +80,8 @@ void ColorPreview::draw_func(Cairo::RefPtr const &cr, cairo_line_to(cr->cobj(), x, height); cairo_line_to(cr->cobj(), x, y); cairo_close_path (cr->cobj()); - ink_cairo_set_source_rgba32(cr->cobj(), color | 0xff); + color.enableOpacity(false); + ink_cairo_set_source_color(cr, color); cr->fill(); } diff --git a/src/ui/widget/color-scales.cpp b/src/ui/widget/color-scales.cpp index 8a7b608e65b3e323b0df465fcffa85578622a9ba..74af7a62dea61e3b0038d1e3b0e49f97f4885e01 100644 --- a/src/ui/widget/color-scales.cpp +++ b/src/ui/widget/color-scales.cpp @@ -25,31 +25,21 @@ #include #include -#include "oklab.h" +#include "colors/spaces/components.h" +#include "colors/spaces/enum.h" +#include "colors/spaces/oklch.h" #include "preferences.h" #include "ui/dialog-events.h" #include "ui/icon-loader.h" #include "ui/pack.h" -#include "ui/selected-color.h" -#include "ui/widget/color-icc-selector.h" #include "ui/widget/color-slider.h" #include "ui/widget/ink-color-wheel.h" #include "ui/widget/oklab-color-wheel.h" -constexpr static int CSC_CHANNEL_R = (1 << 0); -constexpr static int CSC_CHANNEL_G = (1 << 1); -constexpr static int CSC_CHANNEL_B = (1 << 2); -constexpr static int CSC_CHANNEL_A = (1 << 3); -constexpr static int CSC_CHANNEL_H = (1 << 0); -constexpr static int CSC_CHANNEL_S = (1 << 1); -constexpr static int CSC_CHANNEL_V = (1 << 2); -constexpr static int CSC_CHANNEL_C = (1 << 0); -constexpr static int CSC_CHANNEL_M = (1 << 1); -constexpr static int CSC_CHANNEL_Y = (1 << 2); -constexpr static int CSC_CHANNEL_K = (1 << 3); -constexpr static int CSC_CHANNEL_CMYKA = (1 << 4); - -constexpr static int CSC_CHANNELS_ALL = 0; +constexpr static int CSC_CHANNEL_H = 0; +constexpr static int CSC_CHANNEL_S = 1; +constexpr static int CSC_CHANNEL_V = 2; +constexpr static int CSC_CHANNELS_ALL = 10; constexpr static int XPAD = 2; constexpr static int YPAD = 2; @@ -73,7 +63,7 @@ static const char* color_mode_icons[] = { }; const char* color_mode_name[] = { - N_("None"), N_("RGB"), N_("HSL"), N_("CMYK"), N_("HSV"), N_("HSLuv"), N_("OKHSL"), N_("CMS"), nullptr + N_("None"), N_("RGB"), N_("HSL"), N_("CMYK"), N_("HSV"), N_("HSLuv"), N_("OKHSL"), nullptr }; const char* get_color_mode_icon(SPColorScalesMode mode) { @@ -96,7 +86,7 @@ std::unique_ptr get_factory(SPColorScalesMod case SPColorScalesMode::CMYK: return std::make_unique>(); case SPColorScalesMode::HSLUV: return std::make_unique>(); case SPColorScalesMode::OKLAB: return std::make_unique>(); - case SPColorScalesMode::CMS: return std::make_unique(); + //case SPColorScalesMode::CMS: return std::make_unique(); default: throw std::invalid_argument("There's no factory for the requested color mode"); } @@ -112,7 +102,7 @@ std::vector get_color_pickers() { SPColorScalesMode::CMYK, SPColorScalesMode::OKLAB, SPColorScalesMode::HSLUV, - SPColorScalesMode::CMS + //SPColorScalesMode::CMS }) { auto label = get_color_mode_label(mode); @@ -151,14 +141,16 @@ gchar const * const ColorScales::_pref_wheel_visibilit "/wheel_vis_okhsl"; template -ColorScales::ColorScales(SelectedColor &color, bool no_alpha) +ColorScales::ColorScales(SelectedColor color, bool no_alpha) : Gtk::Box() - , _color(color) + , _color(std::move(color)) , _range_limit(255.0) , _updating(false) , _dragging(false) , _wheel(nullptr) { + assert(_color); + for (gint i = 0; i < 5; i++) { _l[i] = nullptr; _s[i] = nullptr; @@ -167,8 +159,7 @@ ColorScales::ColorScales(SelectedColor &color, bool no_alpha) _initUI(no_alpha); - _color_changed = _color.signal_changed.connect([this](){ _onColorChanged(); }); - _color_dragged = _color.signal_dragged.connect([this](){ _onColorChanged(); }); + _color_changed = _color->signal_changed.connect([this](){ _onColorChanged(); }); } template @@ -319,102 +310,52 @@ void ColorScales::_initUI(bool no_alpha) template void ColorScales::_recalcColor() { - SPColor color; - gfloat alpha = 1.0; - gfloat c[5]; - - if constexpr ( - MODE == SPColorScalesMode::RGB || - MODE == SPColorScalesMode::HSL || - MODE == SPColorScalesMode::HSV || - MODE == SPColorScalesMode::HSLUV || - MODE == SPColorScalesMode::OKLAB) - { - _getRgbaFloatv(c); - color.set(c[0], c[1], c[2]); - alpha = c[3]; - } else if constexpr (MODE == SPColorScalesMode::CMYK) { - _getCmykaFloatv(c); - - float rgb[3]; - SPColor::cmyk_to_rgb_floatv(rgb, c[0], c[1], c[2], c[3]); - color.set(rgb[0], rgb[1], rgb[2]); - alpha = c[4]; - } else { - g_warning("file %s: line %d: Illegal color selector mode NONE", __FILE__, __LINE__); - } - - _color.setColorAlpha(color, alpha); + if (_color->isEmpty()) + g_warning("Color setter can't do anything, nothing to set to."); + _color->setAll(_getColor()); } template void ColorScales::_updateDisplay(bool update_wheel) { -#ifdef DUMP_CHANGE_INFO - g_message("ColorScales::_onColorChanged( this=%p, %f, %f, %f, %f) %d", this, - _color.color().v.c[0], - _color.color().v.c[1], _color.color().v.c[2], _color.alpha(), int(update_wheel); -#endif - - gfloat tmp[3]; - gfloat c[5] = { 0.0, 0.0, 0.0, 0.0 }; + if (_color->isEmpty()) { + g_warning("Empty ColorSet for ColorScale..."); + return; + } - SPColor color = _color.color(); + auto color = _color->getAverage(); if constexpr (MODE == SPColorScalesMode::RGB) { - color.get_rgb_floatv(c); - c[3] = _color.alpha(); - c[4] = 0.0; + color.convert(Colors::Space::Type::RGB); } else if constexpr (MODE == SPColorScalesMode::HSL) { - color.get_rgb_floatv(tmp); - SPColor::rgb_to_hsl_floatv(c, tmp[0], tmp[1], tmp[2]); - c[3] = _color.alpha(); - c[4] = 0.0; - // N.B. We setRgb() with emit = false, to avoid a warning from PaintSelector. - if (update_wheel) { _wheel->setRgb(tmp[0], tmp[1], tmp[2], true, false); } + color.convert(Colors::Space::Type::HSL); } else if constexpr (MODE == SPColorScalesMode::HSV) { - color.get_rgb_floatv(tmp); - SPColor::rgb_to_hsv_floatv(c, tmp[0], tmp[1], tmp[2]); - c[3] = _color.alpha(); - c[4] = 0.0; - if (update_wheel) { _wheel->setRgb(tmp[0], tmp[1], tmp[2], true, false); } + color.convert(Colors::Space::Type::HSV); } else if constexpr (MODE == SPColorScalesMode::CMYK) { - color.get_cmyk_floatv(c); - c[4] = _color.alpha(); + color.convert(Colors::Space::Type::CMYK); } else if constexpr (MODE == SPColorScalesMode::HSLUV) { - color.get_rgb_floatv(tmp); - SPColor::rgb_to_hsluv_floatv(c, tmp[0], tmp[1], tmp[2]); - c[3] = _color.alpha(); - c[4] = 0.0; - if (update_wheel) { _wheel->setRgb(tmp[0], tmp[1], tmp[2], true, false); } + color.convert(Colors::Space::Type::HSLUV); } else if constexpr (MODE == SPColorScalesMode::OKLAB) { - color.get_rgb_floatv(tmp); - // OKLab color space is more sensitive to numerical errors; use doubles. - auto const hsl = Oklab::oklab_to_okhsl(Oklab::rgb_to_oklab({tmp[0], tmp[1], tmp[2]})); - _updating = true; - for (size_t i : {0, 1, 2}) { - setScaled(_a[i], hsl[i]); - } - setScaled(_a[3], _color.alpha()); - setScaled(_a[4], 0.0); - _updateSliders(CSC_CHANNELS_ALL); - _updating = false; - if (update_wheel) { - _wheel->setRgb(tmp[0], tmp[1], tmp[2], true, false); - } - return; + color.convert(Colors::Space::Type::OKHSL); } else { g_warning("file %s: line %d: Illegal color selector mode NONE", __FILE__, __LINE__); } _updating = true; - setScaled(_a[0], c[0]); - setScaled(_a[1], c[1]); - setScaled(_a[2], c[2]); - setScaled(_a[3], c[3]); - setScaled(_a[4], c[4]); + for (size_t i = 0; i < 5; i++) { + if (i < color.size()) { + setScaled(_a[i], color[i]); + } else { + setScaled(_a[i], 0.0); + } + } _updateSliders(CSC_CHANNELS_ALL); _updating = false; + + if (_wheel && update_wheel) { + // N.B. We setColor() with emit = false, to avoid a warning from PaintSelector. + _wheel->setColor(color, true, false); + } } /* Helpers for setting color value */ @@ -466,108 +407,46 @@ void ColorScales::on_show() } template -void ColorScales::_getRgbaFloatv(gfloat *rgba) +Colors::Color ColorScales::_getColor() { - g_return_if_fail(rgba != nullptr); + Colors::Color color(0x0); if constexpr (MODE == SPColorScalesMode::RGB) { - rgba[0] = getScaled(_a[0]); - rgba[1] = getScaled(_a[1]); - rgba[2] = getScaled(_a[2]); - rgba[3] = getScaled(_a[3]); + color.convert(Colors::Space::Type::RGB); } else if constexpr (MODE == SPColorScalesMode::HSL) { - SPColor::hsl_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2])); - rgba[3] = getScaled(_a[3]); + color.convert(Colors::Space::Type::HSL); } else if constexpr (MODE == SPColorScalesMode::HSV) { - SPColor::hsv_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2])); - rgba[3] = getScaled(_a[3]); + color.convert(Colors::Space::Type::HSV); } else if constexpr (MODE == SPColorScalesMode::CMYK) { - SPColor::cmyk_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]), - getScaled(_a[2]), getScaled(_a[3])); - rgba[3] = getScaled(_a[4]); + color.convert(Colors::Space::Type::CMYK); } else if constexpr (MODE == SPColorScalesMode::HSLUV) { - SPColor::hsluv_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]), - getScaled(_a[2])); - rgba[3] = getScaled(_a[3]); + color.convert(Colors::Space::Type::HSLUV); } else if constexpr (MODE == SPColorScalesMode::OKLAB) { - auto const tmp = Oklab::oklab_to_rgb( - Oklab::okhsl_to_oklab({ getScaled(_a[0]), - getScaled(_a[1]), - getScaled(_a[2]) })); - for (size_t i : {0, 1, 2}) { - rgba[i] = static_cast(tmp[i]); - } - rgba[3] = getScaled(_a[3]); + color.convert(Colors::Space::Type::OKHSL); } else { g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__); } -} - -template -void ColorScales::_getCmykaFloatv(gfloat *cmyka) -{ - gfloat rgb[3]; - - g_return_if_fail(cmyka != nullptr); - - if constexpr (MODE == SPColorScalesMode::RGB) { - SPColor::rgb_to_cmyk_floatv(cmyka, getScaled(_a[0]), getScaled(_a[1]), - getScaled(_a[2])); - cmyka[4] = getScaled(_a[3]); - } else if constexpr (MODE == SPColorScalesMode::HSL) { - SPColor::hsl_to_rgb_floatv(rgb, getScaled(_a[0]), getScaled(_a[1]), - getScaled(_a[2])); - SPColor::rgb_to_cmyk_floatv(cmyka, rgb[0], rgb[1], rgb[2]); - cmyka[4] = getScaled(_a[3]); - } else if constexpr (MODE == SPColorScalesMode::HSLUV) { - SPColor::hsluv_to_rgb_floatv(rgb, getScaled(_a[0]), getScaled(_a[1]), - getScaled(_a[2])); - SPColor::rgb_to_cmyk_floatv(cmyka, rgb[0], rgb[1], rgb[2]); - cmyka[4] = getScaled(_a[3]); - } else if constexpr (MODE == SPColorScalesMode::OKLAB) { - auto const tmp = Oklab::oklab_to_rgb( - Oklab::okhsl_to_oklab({ getScaled(_a[0]), - getScaled(_a[1]), - getScaled(_a[2]) })); - SPColor::rgb_to_cmyk_floatv(cmyka, (float)tmp[0], (float)tmp[1], (float)tmp[2]); - cmyka[4] = getScaled(_a[3]); - } else if constexpr (MODE == SPColorScalesMode::CMYK) { - cmyka[0] = getScaled(_a[0]); - cmyka[1] = getScaled(_a[1]); - cmyka[2] = getScaled(_a[2]); - cmyka[3] = getScaled(_a[3]); - cmyka[4] = getScaled(_a[4]); - } else { - g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__); + for (size_t i = 0; i < color.size(); i++) { + color.set(i, getScaled(_a[i])); } + return color; } template guint32 ColorScales::_getRgba32() { - gfloat c[4]; - guint32 rgba; - - _getRgbaFloatv(c); - - rgba = SP_RGBA32_F_COMPOSE(c[0], c[1], c[2], c[3]); - - return rgba; + return _getColor().toRGBA(); } template void ColorScales::setupMode(bool no_alpha) { - gfloat rgba[4]; - gfloat c[4]; - int alpha_index = 0; - if constexpr (MODE == SPColorScalesMode::NONE) { - rgba[0] = rgba[1] = rgba[2] = rgba[3] = 1.0; - } else { - _getRgbaFloatv(rgba); + return; } + Colors::Color c = _getColor(); + if constexpr (MODE == SPColorScalesMode::RGB) { _setRangeLimit(255.0); _a[3]->set_upper(100.0); @@ -580,7 +459,6 @@ void ColorScales::setupMode(bool no_alpha) _l[2]->set_markup_with_mnemonic(_("_B:")); _s[2]->set_tooltip_text(_("Blue")); _b[2]->set_tooltip_text(_("Blue")); - alpha_index = 3; _l[3]->set_markup_with_mnemonic(_("_A:")); _s[3]->set_tooltip_text(_("Alpha (opacity)")); _b[3]->set_tooltip_text(_("Alpha (opacity)")); @@ -589,10 +467,10 @@ void ColorScales::setupMode(bool no_alpha) _s[4]->set_visible(false); _b[4]->set_visible(false); _updating = true; - setScaled(_a[0], rgba[0]); - setScaled(_a[1], rgba[1]); - setScaled(_a[2], rgba[2]); - setScaled(_a[3], rgba[3]); + setScaled(_a[0], c[0]); + setScaled(_a[1], c[1]); + setScaled(_a[2], c[2]); + setScaled(_a[3], c[3]); _updateSliders(CSC_CHANNELS_ALL); _updating = false; } else if constexpr (MODE == SPColorScalesMode::HSL) { @@ -611,7 +489,6 @@ void ColorScales::setupMode(bool no_alpha) _s[2]->set_tooltip_text(_("Lightness")); _b[2]->set_tooltip_text(_("Lightness")); - alpha_index = 3; _l[3]->set_markup_with_mnemonic(_("_A:")); _s[3]->set_tooltip_text(_("Alpha (opacity)")); _b[3]->set_tooltip_text(_("Alpha (opacity)")); @@ -620,14 +497,11 @@ void ColorScales::setupMode(bool no_alpha) _s[4]->set_visible(false); _b[4]->set_visible(false); _updating = true; - c[0] = 0.0; - - SPColor::rgb_to_hsl_floatv(c, rgba[0], rgba[1], rgba[2]); setScaled(_a[0], c[0]); setScaled(_a[1], c[1]); setScaled(_a[2], c[2]); - setScaled(_a[3], rgba[3]); + setScaled(_a[3], c[3]); _updateSliders(CSC_CHANNELS_ALL); _updating = false; @@ -647,7 +521,6 @@ void ColorScales::setupMode(bool no_alpha) _s[2]->set_tooltip_text(_("Value")); _b[2]->set_tooltip_text(_("Value")); - alpha_index = 3; _l[3]->set_markup_with_mnemonic(_("_A:")); _s[3]->set_tooltip_text(_("Alpha (opacity)")); _b[3]->set_tooltip_text(_("Alpha (opacity)")); @@ -656,14 +529,11 @@ void ColorScales::setupMode(bool no_alpha) _s[4]->set_visible(false); _b[4]->set_visible(false); _updating = true; - c[0] = 0.0; - - SPColor::rgb_to_hsv_floatv(c, rgba[0], rgba[1], rgba[2]); setScaled(_a[0], c[0]); setScaled(_a[1], c[1]); setScaled(_a[2], c[2]); - setScaled(_a[3], rgba[3]); + setScaled(_a[3], c[3]); _updateSliders(CSC_CHANNELS_ALL); _updating = false; @@ -685,7 +555,6 @@ void ColorScales::setupMode(bool no_alpha) _s[3]->set_tooltip_text(_("Black")); _b[3]->set_tooltip_text(_("Black")); - alpha_index = 4; _l[4]->set_markup_with_mnemonic(_("_A:")); _s[4]->set_tooltip_text(_("Alpha (opacity)")); _b[4]->set_tooltip_text(_("Alpha (opacity)")); @@ -696,13 +565,12 @@ void ColorScales::setupMode(bool no_alpha) _b[4]->set_visible(true); _updating = true; - SPColor::rgb_to_cmyk_floatv(c, rgba[0], rgba[1], rgba[2]); setScaled(_a[0], c[0]); setScaled(_a[1], c[1]); setScaled(_a[2], c[2]); setScaled(_a[3], c[3]); + setScaled(_a[4], c[4]); - setScaled(_a[4], rgba[3]); _updateSliders(CSC_CHANNELS_ALL); _updating = false; } else if constexpr (MODE == SPColorScalesMode::HSLUV) { @@ -721,7 +589,6 @@ void ColorScales::setupMode(bool no_alpha) _s[2]->set_tooltip_text(_("Lightness")); _b[2]->set_tooltip_text(_("Lightness")); - alpha_index = 3; _l[3]->set_markup_with_mnemonic(_("_A:")); _s[3]->set_tooltip_text(_("Alpha (opacity)")); _b[3]->set_tooltip_text(_("Alpha (opacity)")); @@ -734,14 +601,11 @@ void ColorScales::setupMode(bool no_alpha) _s[4]->set_visible(false); _b[4]->set_visible(false); _updating = true; - c[0] = 0.0; - - SPColor::rgb_to_hsluv_floatv(c, rgba[0], rgba[1], rgba[2]); setScaled(_a[0], c[0]); setScaled(_a[1], c[1]); setScaled(_a[2], c[2]); - setScaled(_a[3], rgba[3]); + setScaled(_a[3], c[3]); _updateSliders(CSC_CHANNELS_ALL); _updating = false; @@ -761,7 +625,6 @@ void ColorScales::setupMode(bool no_alpha) _s[2]->set_tooltip_text(_("Lightness")); _b[2]->set_tooltip_text(_("Lightness")); - alpha_index = 3; _l[3]->set_markup_with_mnemonic(_("_A:")); _s[3]->set_tooltip_text(_("Alpha (opacity)")); _b[3]->set_tooltip_text(_("Alpha (opacity)")); @@ -771,11 +634,10 @@ void ColorScales::setupMode(bool no_alpha) _b[4]->set_visible(false); _updating = true; - auto const tmp = Oklab::oklab_to_okhsl(Oklab::rgb_to_oklab({rgba[0], rgba[1], rgba[2]})); for (size_t i : {0, 1, 2}) { - setScaled(_a[i], tmp[i]); + setScaled(_a[i], c[i]); } - setScaled(_a[3], rgba[3]); + setScaled(_a[3], c.getOpacity()); _updateSliders(CSC_CHANNELS_ALL); _updating = false; @@ -783,7 +645,8 @@ void ColorScales::setupMode(bool no_alpha) g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__); } - if (no_alpha && alpha_index > 0) { + if (no_alpha) { + auto alpha_index = c.getOpacityChannel(); _l[alpha_index]->set_visible(false); _s[alpha_index]->set_visible(false); _b[alpha_index]->set_visible(false); @@ -800,7 +663,7 @@ void ColorScales::_sliderAnyGrabbed() if (!_dragging) { _dragging = true; - _color.setHeld(true); + _color->grab(); } } @@ -811,7 +674,7 @@ void ColorScales::_sliderAnyReleased() if (_dragging) { _dragging = false; - _color.setHeld(false); + _color->release(); } } @@ -828,7 +691,7 @@ void ColorScales::_adjustmentChanged(int channel) { if (_updating) { return; } - _updateSliders((1 << channel)); + _updateSliders(channel); _recalcColor(); } @@ -846,214 +709,74 @@ void ColorScales::_wheelChanged() if (_updating) { return; } _updating = true; - - double rgb[3]; - _wheel->getRgbV(rgb); - SPColor color(rgb[0], rgb[1], rgb[2]); - _color_changed.block(); - _color_dragged.block(); // Color - _color.setHeld(_wheel->isAdjusting()); - _color.setColor(color); + if (_wheel->isAdjusting()) { + _color->grab(); + } else { + _color->release(); + } + _color->setAll(_wheel->getColor()); // Sliders _updateDisplay(false); _color_changed.unblock(); - _color_dragged.unblock(); - _updating = false; } template -void ColorScales::_updateSliders(guint channels) +void ColorScales::_updateSliders(guint channel_pin) { - gfloat rgb0[3], rgbm[3], rgb1[3]; + auto color = _getColor(); -#ifdef SPCS_PREVIEW - guint32 rgba; -#endif - - std::array const adj = [this]() -> std::array { - if constexpr (MODE == SPColorScalesMode::CMYK) { - return { getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), getScaled(_a[3]) }; - } else { - return { getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 0.0 }; - } - }(); + // Opacity is not shown in color sliders + if (channel_pin == color.getOpacityChannel()) + return; + color.enableOpacity(false); - if constexpr (MODE == SPColorScalesMode::RGB) { - if ((channels != CSC_CHANNEL_R) && (channels != CSC_CHANNEL_A)) { - /* Update red */ - _s[0]->setColors(SP_RGBA32_F_COMPOSE(0.0, adj[1], adj[2], 1.0), - SP_RGBA32_F_COMPOSE(0.5, adj[1], adj[2], 1.0), - SP_RGBA32_F_COMPOSE(1.0, adj[1], adj[2], 1.0)); - } - if ((channels != CSC_CHANNEL_G) && (channels != CSC_CHANNEL_A)) { - /* Update green */ - _s[1]->setColors(SP_RGBA32_F_COMPOSE(adj[0], 0.0, adj[2], 1.0), - SP_RGBA32_F_COMPOSE(adj[0], 0.5, adj[2], 1.0), - SP_RGBA32_F_COMPOSE(adj[0], 1.0, adj[2], 1.0)); - } - if ((channels != CSC_CHANNEL_B) && (channels != CSC_CHANNEL_A)) { - /* Update blue */ - _s[2]->setColors(SP_RGBA32_F_COMPOSE(adj[0], adj[1], 0.0, 1.0), - SP_RGBA32_F_COMPOSE(adj[0], adj[1], 0.5, 1.0), - SP_RGBA32_F_COMPOSE(adj[0], adj[1], 1.0, 1.0)); - } - if (channels != CSC_CHANNEL_A) { - /* Update alpha */ - _s[3]->setColors(SP_RGBA32_F_COMPOSE(adj[0], adj[1], adj[2], 0.0), - SP_RGBA32_F_COMPOSE(adj[0], adj[1], adj[2], 0.5), - SP_RGBA32_F_COMPOSE(adj[0], adj[1], adj[2], 1.0)); - } - } else if constexpr (MODE == SPColorScalesMode::HSL) { - /* Hue is never updated */ - if ((channels != CSC_CHANNEL_S) && (channels != CSC_CHANNEL_A)) { - /* Update saturation */ - SPColor::hsl_to_rgb_floatv(rgb0, adj[0], 0.0, adj[2]); - SPColor::hsl_to_rgb_floatv(rgbm, adj[0], 0.5, adj[2]); - SPColor::hsl_to_rgb_floatv(rgb1, adj[0], 1.0, adj[2]); - _s[1]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), - SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), - SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); - } - if ((channels != CSC_CHANNEL_V) && (channels != CSC_CHANNEL_A)) { - /* Update value */ - SPColor::hsl_to_rgb_floatv(rgb0, adj[0], adj[1], 0.0); - SPColor::hsl_to_rgb_floatv(rgbm, adj[0], adj[1], 0.5); - SPColor::hsl_to_rgb_floatv(rgb1, adj[0], adj[1], 1.0); - _s[2]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), - SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), - SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); - } - if (channels != CSC_CHANNEL_A) { - /* Update alpha */ - SPColor::hsl_to_rgb_floatv(rgb0, adj[0], adj[1], adj[2]); - _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0), - SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5), - SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0)); - } - } else if constexpr (MODE == SPColorScalesMode::HSV) { - /* Hue is never updated */ - if ((channels != CSC_CHANNEL_S) && (channels != CSC_CHANNEL_A)) { - /* Update saturation */ - SPColor::hsv_to_rgb_floatv(rgb0, adj[0], 0.0, adj[2]); - SPColor::hsv_to_rgb_floatv(rgbm, adj[0], 0.5, adj[2]); - SPColor::hsv_to_rgb_floatv(rgb1, adj[0], 1.0, adj[2]); - _s[1]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), - SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), - SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); - } - if ((channels != CSC_CHANNEL_V) && (channels != CSC_CHANNEL_A)) { - /* Update value */ - SPColor::hsv_to_rgb_floatv(rgb0, adj[0], adj[1], 0.0); - SPColor::hsv_to_rgb_floatv(rgbm, adj[0], adj[1], 0.5); - SPColor::hsv_to_rgb_floatv(rgb1, adj[0], adj[1], 1.0); - _s[2]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), - SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), - SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); - } - if (channels != CSC_CHANNEL_A) { - /* Update alpha */ - SPColor::hsv_to_rgb_floatv(rgb0, adj[0], adj[1], adj[2]); - _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0), - SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5), - SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0)); - } - } else if constexpr (MODE == SPColorScalesMode::CMYK) { - if ((channels != CSC_CHANNEL_C) && (channels != CSC_CHANNEL_CMYKA)) { - /* Update C */ - SPColor::cmyk_to_rgb_floatv(rgb0, 0.0, adj[1], adj[2], adj[3]); - SPColor::cmyk_to_rgb_floatv(rgbm, 0.5, adj[1], adj[2], adj[3]); - SPColor::cmyk_to_rgb_floatv(rgb1, 1.0, adj[1], adj[2], adj[3]); - _s[0]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), - SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), - SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); - } - if ((channels != CSC_CHANNEL_M) && (channels != CSC_CHANNEL_CMYKA)) { - /* Update M */ - SPColor::cmyk_to_rgb_floatv(rgb0, adj[0], 0.0, adj[2], adj[3]); - SPColor::cmyk_to_rgb_floatv(rgbm, adj[0], 0.5, adj[2], adj[3]); - SPColor::cmyk_to_rgb_floatv(rgb1, adj[0], 1.0, adj[2], adj[3]); - _s[1]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), - SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), - SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); - } - if ((channels != CSC_CHANNEL_Y) && (channels != CSC_CHANNEL_CMYKA)) { - /* Update Y */ - SPColor::cmyk_to_rgb_floatv(rgb0, adj[0], adj[1], 0.0, adj[3]); - SPColor::cmyk_to_rgb_floatv(rgbm, adj[0], adj[1], 0.5, adj[3]); - SPColor::cmyk_to_rgb_floatv(rgb1, adj[0], adj[1], 1.0, adj[3]); - _s[2]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), - SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), - SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); - } - if ((channels != CSC_CHANNEL_K) && (channels != CSC_CHANNEL_CMYKA)) { - /* Update K */ - SPColor::cmyk_to_rgb_floatv(rgb0, adj[0], adj[1], adj[2], 0.0); - SPColor::cmyk_to_rgb_floatv(rgbm, adj[0], adj[1], adj[2], 0.5); - SPColor::cmyk_to_rgb_floatv(rgb1, adj[0], adj[1], adj[2], 1.0); - _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), - SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), - SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); - } - if (channels != CSC_CHANNEL_CMYKA) { - /* Update alpha */ - SPColor::cmyk_to_rgb_floatv(rgb0, adj[0], adj[1], adj[2], adj[3]); - _s[4]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0), - SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5), - SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0)); - } - } else if constexpr (MODE == SPColorScalesMode::HSLUV) { - if ((channels != CSC_CHANNEL_H) && (channels != CSC_CHANNEL_A)) { + if constexpr (MODE == SPColorScalesMode::HSLUV) { + if (channel_pin != CSC_CHANNEL_H) { /* Update hue */ - _s[0]->setMap(hsluvHueMap(adj[1], adj[2], &_sliders_maps[0])); + _s[0]->setMap(hsluvHueMap(color[1], color[2], &_sliders_maps[0])); } - if ((channels != CSC_CHANNEL_S) && (channels != CSC_CHANNEL_A)) { + if (channel_pin != CSC_CHANNEL_S) { /* Update saturation (scaled chroma) */ - _s[1]->setMap(hsluvSaturationMap(adj[0], adj[2], &_sliders_maps[1])); + _s[1]->setMap(hsluvSaturationMap(color[0], color[2], &_sliders_maps[1])); } - if ((channels != CSC_CHANNEL_V) && (channels != CSC_CHANNEL_A)) { + if (channel_pin != CSC_CHANNEL_V) { /* Update lightness */ - _s[2]->setMap(hsluvLightnessMap(adj[0], adj[1], &_sliders_maps[2])); - } - if (channels != CSC_CHANNEL_A) { - /* Update alpha */ - SPColor::hsluv_to_rgb_floatv(rgb0, adj[0], adj[1], adj[2]); - _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0), - SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5), - SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0)); + _s[2]->setMap(hsluvLightnessMap(color[0], color[1], &_sliders_maps[2])); } } else if constexpr (MODE == SPColorScalesMode::OKLAB) { - if (channels != CSC_CHANNEL_H && channels != CSC_CHANNEL_A) { - _s[0]->setMap(Oklab::render_hue_scale(adj[1], adj[2], &_sliders_maps[0])); - } - if ((channels != CSC_CHANNEL_S) && (channels != CSC_CHANNEL_A)) { - _s[1]->setMap(Oklab::render_saturation_scale(360.0 * adj[0], adj[2], &_sliders_maps[1])); + if (channel_pin != CSC_CHANNEL_H) { + _s[0]->setMap(Inkscape::Colors::Space::render_hue_scale(color[1], color[2], &_sliders_maps[0])); } - if ((channels != CSC_CHANNEL_V) && (channels != CSC_CHANNEL_A)) { - _s[2]->setMap(Oklab::render_lightness_scale(360.0 * adj[0], adj[1], &_sliders_maps[2])); + if (channel_pin != CSC_CHANNEL_S) { + _s[1]->setMap(Inkscape::Colors::Space::render_saturation_scale(360.0 * color[0], color[2], &_sliders_maps[1])); } - if (channels != CSC_CHANNEL_A) { // Update the alpha gradient. - auto const rgb = Oklab::oklab_to_rgb( - Oklab::okhsl_to_oklab({ getScaled(_a[0]), - getScaled(_a[1]), - getScaled(_a[2]) })); - _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 0.0), - SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 0.5), - SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 1.0)); + if (channel_pin != CSC_CHANNEL_V) { + _s[2]->setMap(Inkscape::Colors::Space::render_lightness_scale(360.0 * color[0], color[1], &_sliders_maps[2])); } - } else { - g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__); } -#ifdef SPCS_PREVIEW - rgba = sp_color_scales_get_rgba32(cs); - sp_color_preview_set_rgba32(SP_COLOR_PREVIEW(_p), rgba); -#endif + // We request the opacity channel even though color has no opacity, this is ok as Color::set will handle it. + for (auto &channel : color.getSpace()->getComponents(true)) { + // Ignore any hue, any mapped and any updated channels + if (channel.id != "a" && (MODE == SPColorScalesMode::HSLUV || MODE == SPColorScalesMode::OKLAB)) + continue; + if (channel.scale == 360 || channel.index == channel_pin) + continue; + + auto low = color; + auto mid = color; + auto high = color; + low.set(channel.index, 0.0); + mid.set(channel.index, 0.5); + high.set(channel.index, 1.0); + _s[channel.index]->setColors(low.toRGBA(), mid.toRGBA(), high.toRGBA()); + } } static guchar const *sp_color_scales_hue_map() @@ -1063,9 +786,10 @@ static guchar const *sp_color_scales_hue_map() guchar *p; p = m.data(); + auto color = Colors::Color(Colors::Space::Type::HSL, {0, 1.0, 0.5}); for (gint h = 0; h < 1024; h++) { - gfloat rgb[3]; - SPColor::hsl_to_rgb_floatv(rgb, h / 1024.0, 1.0, 0.5); + color.set(0, h / 1024.0); + auto rgb = *color.converted(Colors::Space::Type::RGB); *p++ = SP_COLOR_F_TO_U(rgb[0]); *p++ = SP_COLOR_F_TO_U(rgb[1]); *p++ = SP_COLOR_F_TO_U(rgb[2]); @@ -1139,8 +863,13 @@ template guchar const *ColorScales::hsluvHueMap(gfloat s, gfloat l, std::array *map) { - return sp_color_scales_hsluv_map(map->data(), [s, l] (float *colors, float h) { - SPColor::hsluv_to_rgb_floatv(colors, h, s, l); + auto color = Colors::Color(Colors::Space::Type::HSLUV, {0, s, l}); + return sp_color_scales_hsluv_map(map->data(), [&color] (float *colors, float h) { + color.set(0, (double)h); + auto rgb = *color.converted(Colors::Space::Type::RGB); + *colors++ = rgb[0]; + *colors++ = rgb[1]; + *colors++ = rgb[2]; }); } @@ -1148,8 +877,13 @@ template guchar const *ColorScales::hsluvSaturationMap(gfloat h, gfloat l, std::array *map) { - return sp_color_scales_hsluv_map(map->data(), [h, l] (float *colors, float s) { - SPColor::hsluv_to_rgb_floatv(colors, h, s, l); + auto color = Colors::Color(Colors::Space::Type::HSLUV, {h, 0, l}); + return sp_color_scales_hsluv_map(map->data(), [&color] (float *colors, float s) { + color.set(1, (double)s); + auto rgb = *color.converted(Colors::Space::Type::RGB); + *colors++ = rgb[0]; + *colors++ = rgb[1]; + *colors++ = rgb[2]; }); } @@ -1157,8 +891,13 @@ template guchar const *ColorScales::hsluvLightnessMap(gfloat h, gfloat s, std::array *map) { - return sp_color_scales_hsluv_map(map->data(), [h, s] (float *colors, float l) { - SPColor::hsluv_to_rgb_floatv(colors, h, s, l); + auto color = Colors::Color(Colors::Space::Type::HSLUV, {h, s, 0}); + return sp_color_scales_hsluv_map(map->data(), [&color] (float *colors, float l) { + color.set(2, (double)l); + auto rgb = *color.converted(Colors::Space::Type::RGB); + *colors++ = rgb[0]; + *colors++ = rgb[1]; + *colors++ = rgb[2]; }); } @@ -1167,9 +906,9 @@ ColorScalesFactory::ColorScalesFactory() {} template -Gtk::Widget *ColorScalesFactory::createWidget(Inkscape::UI::SelectedColor &color, bool no_alpha) const +Gtk::Widget *ColorScalesFactory::createWidget(Inkscape::UI::SelectedColor color, bool no_alpha) const { - Gtk::Widget *w = Gtk::make_managed>(color, no_alpha); + Gtk::Widget *w = Gtk::make_managed>(std::move(color), no_alpha); return w; } diff --git a/src/ui/widget/color-scales.h b/src/ui/widget/color-scales.h index b533ff73e1d9473866e5e195a63d2a960e8f5edf..b3e2afc04acd5fb1bdd3203f46f939ceb1427e9c 100644 --- a/src/ui/widget/color-scales.h +++ b/src/ui/widget/color-scales.h @@ -19,14 +19,29 @@ #include #include +#include "colors/color-set.h" #include "helper/auto-connection.h" -#include "ui/selected-color.h" namespace Gtk { class Label; class SpinButton; } // namespace Gtk + +// TODO: Replace this later, originally ui/selected-color.h +namespace Inkscape::UI { + +typedef std::shared_ptr SelectedColor; + +class ColorSelectorFactory { +public: + virtual ~ColorSelectorFactory() = default; + + virtual Gtk::Widget* createWidget(SelectedColor colors, bool no_alpha) const = 0; + virtual Glib::ustring modeName() const = 0; +}; +} + namespace Inkscape::UI::Widget { class ColorSlider; @@ -53,7 +68,7 @@ public: static double getScaled(Glib::RefPtr const &a); static void setScaled(Glib::RefPtr &a, double v, bool constrained = false); - ColorScales(SelectedColor &color, bool no_alpha); + ColorScales(SelectedColor color, bool no_alpha); void setupMode(bool no_alpha); SPColorScalesMode getMode() const; @@ -77,8 +92,7 @@ protected: void _adjustmentChanged(int channel); void _wheelChanged(); - void _getRgbaFloatv(gfloat *rgba); - void _getCmykaFloatv(gfloat *cmyka); + Colors::Color _getColor(); guint32 _getRgba32(); void _updateSliders(guint channels); void _recalcColor(); @@ -86,7 +100,7 @@ protected: void _setRangeLimit(gdouble upper); - SelectedColor &_color; + SelectedColor _color; gdouble _range_limit; gboolean _updating : 1; gboolean _dragging : 1; @@ -115,7 +129,7 @@ class ColorScalesFactory : public Inkscape::UI::ColorSelectorFactory public: ColorScalesFactory(); - Gtk::Widget *createWidget(Inkscape::UI::SelectedColor &color, bool no_alpha) const override; + Gtk::Widget *createWidget(Inkscape::UI::SelectedColor color, bool no_alpha) const override; Glib::ustring modeName() const override; }; diff --git a/src/ui/widget/color-slider.cpp b/src/ui/widget/color-slider.cpp index e75fc9fb495ca746b13f3bc93adbe8952693c6b9..541575cd35f41d7c32ef4cf01a578225adddca42 100644 --- a/src/ui/widget/color-slider.cpp +++ b/src/ui/widget/color-slider.cpp @@ -18,6 +18,7 @@ #include #include +#include "colors/color.h" #include "ui/widget/color-slider.h" #include "preferences.h" #include "ui/controller.h" @@ -52,10 +53,10 @@ ColorSlider::ColorSlider(Glib::RefPtr adjustment) _cm[2] = 0x00; _cm[3] = 0xff; - _c0[0] = 0xff; - _c0[1] = 0xff; - _c0[2] = 0xff; - _c0[3] = 0xff; + _c1[0] = 0xff; + _c1[1] = 0xff; + _c1[2] = 0xff; + _c1[3] = 0xff; _b0 = 0x5f; _b1 = 0xa0; diff --git a/src/ui/widget/fill-style.cpp b/src/ui/widget/fill-style.cpp index 503f8cf6f522855c1890b35b49dee3592a6c8641..477ede501e91c942afe5eec6237279ba0fbd34b8 100644 --- a/src/ui/widget/fill-style.cpp +++ b/src/ui/widget/fill-style.cpp @@ -58,11 +58,15 @@ namespace Widget { FillNStroke::FillNStroke(FillOrStroke k) : Gtk::Box(Gtk::Orientation::VERTICAL) , kind(k) + , _solid_colors(std::make_shared()) , subselChangedConn() , eventContextConn() { + // Single fallback color + _solid_colors->set(Color(0x000000ff)); + // Add and connect up the paint selector widget: - _psel = Gtk::make_managed(kind); + _psel = Gtk::make_managed(kind, _solid_colors); _psel->set_visible(true); append(*_psel); _psel->signal_mode_changed().connect(sigc::mem_fun(*this, &FillNStroke::paintModeChangeCB)); @@ -189,7 +193,7 @@ void FillNStroke::performUpdate() } } SPIPaint &targPaint = *query.getFillOrStroke(kind == FILL); - SPIScale24 &targOpacity = *(kind == FILL ? query.fill_opacity.upcast() : query.stroke_opacity.upcast()); + double targOpacity = kind == FILL ? query.fill_opacity : query.stroke_opacity; switch (result) { case QUERY_STYLE_NOTHING: { @@ -212,7 +216,10 @@ void FillNStroke::performUpdate() } if (targPaint.set && targPaint.isColor()) { - _psel->setColorAlpha(targPaint.value.color, SP_SCALE24_TO_FLOAT(targOpacity.value)); + // This is terrible, future refactoring has been written + auto color = targPaint.getColor(); + color.addOpacity(targOpacity); + _solid_colors->set(color); } else if (targPaint.set && targPaint.isPaintserver()) { SPPaintServer* server = (kind == FILL) ? query.getFillPaintServer() : query.getStrokePaintServer(); @@ -349,9 +356,9 @@ void FillNStroke::dragFromPaint() case UI::Widget::PaintSelector::MODE_SOLID_COLOR: { // local change, do not update from selection _drag_id = g_timeout_add_full(G_PRIORITY_DEFAULT, 100, dragDelayCB, this, nullptr); - _psel->setFlatColor(_desktop, - (kind == FILL) ? "fill" : "stroke", - (kind == FILL) ? "fill-opacity" : "stroke-opacity"); + + sp_desktop_set_color(_desktop, _solid_colors->getAverage(), false, kind == FILL); + DocumentUndo::maybeDone(_desktop->doc(), (kind == FILL) ? undo_F_label : undo_S_label, (kind == FILL) ? _("Set fill color") : _("Set stroke color"), INKSCAPE_ICON("dialog-fill-and-stroke")); @@ -428,8 +435,9 @@ void FillNStroke::updateFromPaint(bool switch_style) } case UI::Widget::PaintSelector::MODE_SOLID_COLOR: { - _psel->setFlatColor(_desktop, (kind == FILL) ? "fill" : "stroke", - (kind == FILL) ? "fill-opacity" : "stroke-opacity"); + + sp_desktop_set_color(_desktop, _solid_colors->getAverage(), false, kind == FILL); + DocumentUndo::maybeDone(_desktop->getDocument(), (kind == FILL) ? undo_F_label : undo_S_label, (kind == FILL) ? _("Set fill color") : _("Set stroke color"), INKSCAPE_ICON("dialog-fill-and-stroke")); @@ -462,11 +470,12 @@ void FillNStroke::updateFromPaint(bool switch_style) int result = objects_query_fillstroke(items, &query, kind == FILL); if (result == QUERY_STYLE_MULTIPLE_SAME) { SPIPaint &targPaint = *query.getFillOrStroke(kind == FILL); - SPColor common; + Inkscape::Colors::Color common(0x000000ff); if (!targPaint.isColor()) { - common = sp_desktop_get_color(_desktop, kind == FILL); + if (auto color = sp_desktop_get_color(_desktop, kind == FILL)) + common = *color; } else { - common = targPaint.value.color; + common = targPaint.getColor(); } vector = sp_document_default_gradient_vector(document, common, 1.0, createSwatch); } diff --git a/src/ui/widget/fill-style.h b/src/ui/widget/fill-style.h index 165aec791bf9a99b3b8bfff5767b8f5ab3e60933..8cd12a5540f0f15507cc2b9ee1eddcf6597efaf4 100644 --- a/src/ui/widget/fill-style.h +++ b/src/ui/widget/fill-style.h @@ -36,6 +36,7 @@ namespace Widget { class FillNStroke : public Gtk::Box { private: FillOrStroke kind; + std::shared_ptr _solid_colors; SPDesktop *_desktop = nullptr; PaintSelector *_psel = nullptr; guint32 _last_drag = 0; diff --git a/src/ui/widget/gradient-editor.cpp b/src/ui/widget/gradient-editor.cpp index 405bfad34a05ed9a7152e9e23a1bc8274d8b57fb..bcc4d39d88b0a5888b4cbc1af403cd7928df6b5b 100644 --- a/src/ui/widget/gradient-editor.cpp +++ b/src/ui/widget/gradient-editor.cpp @@ -77,7 +77,7 @@ void set_icon(Gtk::Button &btn, char const *pixmap) } // draw solid color circle with black outline; right side is to show checkerboard if color's alpha is > 0 -Glib::RefPtr draw_circle(int size, guint32 rgba) { +Glib::RefPtr draw_circle(int size, Colors::Color color) { int width = size; int height = size; gint w2 = width / 2; @@ -104,7 +104,8 @@ Glib::RefPtr draw_circle(int size, guint32 rgba) { cairo_close_path(cr); // solid part - ink_cairo_set_source_rgba32(cr, rgba | 0xff); + auto opacity = color.stealOpacity(); + ink_cairo_set_source_color(cr, color); cairo_fill(cr); x = w2; @@ -115,13 +116,14 @@ Glib::RefPtr draw_circle(int size, guint32 rgba) { cairo_close_path(cr); // (semi)transparent part - if ((rgba & 0xff) != 0xff) { + if (opacity < 1.0) { cairo_pattern_t* checkers = ink_cairo_pattern_create_checkerboard(); cairo_set_source(cr, checkers); cairo_fill_preserve(cr); cairo_pattern_destroy(checkers); } - ink_cairo_set_source_rgba32(cr, rgba); + color.addOpacity(opacity); + ink_cairo_set_source_color(cr, color); cairo_fill(cr); cairo_destroy(cr); @@ -134,7 +136,7 @@ Glib::RefPtr draw_circle(int size, guint32 rgba) { Glib::RefPtr get_stop_pixmap(SPStop* stop) { const int size = 30; - return draw_circle(size, stop->getColor().toRGBA32(stop->getOpacity())); + return draw_circle(size, stop->getColor()); } Glib::ustring get_repeat_icon(SPGradientSpread mode) { @@ -156,9 +158,10 @@ Glib::ustring get_repeat_icon(SPGradientSpread mode) { return ico; } -GradientEditor::GradientEditor(const char* prefs) : +GradientEditor::GradientEditor(const char* prefs): _builder(Inkscape::UI::create_builder("gradient-edit.glade")), _selector(Gtk::make_managed()), + _colors(new Colors::ColorSet()), _repeat_popover{std::make_unique(Gtk::PositionType::BOTTOM)}, _repeat_icon(get_widget(_builder, "repeatIco")), _stop_tree(get_widget(_builder, "stopList")), @@ -211,7 +214,7 @@ GradientEditor::GradientEditor(const char* prefs) : gradBox.append(_gradient_image); // add color selector - auto const color_selector = Gtk::make_managed(_selected_color); + auto const color_selector = Gtk::make_managed(_colors); color_selector->set_label(_("Stop color")); color_selector->set_visible(true); _colors_box.append(*color_selector); @@ -289,11 +292,8 @@ GradientEditor::GradientEditor(const char* prefs) : get_widget(_builder, "repeatMode").set_popover(*_repeat_popover); set_repeat_icon(SP_GRADIENT_SPREAD_PAD); - _selected_color.signal_changed.connect([=]() { - set_stop_color(_selected_color.color(), _selected_color.alpha()); - }); - _selected_color.signal_dragged.connect([=]() { - set_stop_color(_selected_color.color(), _selected_color.alpha()); + _colors->signal_changed.connect([=]() { + set_stop_color(_colors->getAverage()); }); _offset_btn.signal_changed().connect([=]() { @@ -315,7 +315,8 @@ GradientEditor::GradientEditor(const char* prefs) : GradientEditor::~GradientEditor() noexcept { } -void GradientEditor::set_stop_color(SPColor color, float opacity) { +void GradientEditor::set_stop_color(Inkscape::Colors::Color const &color) +{ if (_update.pending()) return; SPGradient* vector = get_gradient_vector(); @@ -330,7 +331,7 @@ void GradientEditor::set_stop_color(SPColor color, float opacity) { // update list view too row->set_value(_stop_color, get_stop_pixmap(stop)); - sp_set_gradient_stop_color(_document, stop, color, opacity); + sp_set_gradient_stop_color(_document, stop, color); } } } @@ -355,13 +356,13 @@ SPStop* GradientEditor::get_nth_stop(size_t index) { // stop has been selected in a list view void GradientEditor::stop_selected() { + _colors->clear(); if (auto row = current_stop()) { SPStop* stop = row->get_value(_stopObj); if (stop) { auto scoped(_update.block()); - _selected_color.setColor(stop->getColor()); - _selected_color.setAlpha(stop->getOpacity()); + _colors->set(stop->getId(), stop->getColor()); auto stops = sp_get_before_after_stops(stop); if (stops.first && stops.second) { @@ -380,9 +381,6 @@ void GradientEditor::stop_selected() { else { // no selection auto scoped(_update.block()); - - _selected_color.setColor(SPColor()); - _offset_btn.set_range(0, 0); _offset_btn.set_value(0); _offset_btn.set_sensitive(false); diff --git a/src/ui/widget/gradient-editor.h b/src/ui/widget/gradient-editor.h index af5ce636bca5a0f53f1e21d1b1d4a7d5401b1802..21ef0cbde1a2e3fa06c172bb1fc833ff997f07ca 100644 --- a/src/ui/widget/gradient-editor.h +++ b/src/ui/widget/gradient-editor.h @@ -19,9 +19,9 @@ #include #include +#include "colors/color-set.h" #include "object/sp-gradient.h" #include "object/sp-stop.h" -#include "ui/selected-color.h" #include "spin-scale.h" #include "gradient-with-stops.h" #include "gradient-selector-interface.h" @@ -86,7 +86,7 @@ private: void set_repeat_icon(SPGradientSpread mode); void reverse_gradient(); void turn_gradient(double angle, bool relative); - void set_stop_color(SPColor color, float opacity); + void set_stop_color(Colors::Color const &color); std::optional current_stop(); SPStop* get_nth_stop(size_t index); SPStop* get_current_stop(); @@ -97,7 +97,7 @@ private: Glib::RefPtr _builder; GradientSelector* _selector; - Inkscape::UI::SelectedColor _selected_color; + std::shared_ptr _colors; std::unique_ptr _repeat_popover; Gtk::Image& _repeat_icon; GradientWithStops _gradient_image; diff --git a/src/ui/widget/gradient-image.cpp b/src/ui/widget/gradient-image.cpp index 6f573789d2c1425e7f1fb983da8cdcf5929a4621..f39edd0f8c15a6ff737ad9f1a084d452d1faeb70 100644 --- a/src/ui/widget/gradient-image.cpp +++ b/src/ui/widget/gradient-image.cpp @@ -129,12 +129,14 @@ sp_gradstop_to_pixbuf_ref (SPStop *stop, int width, int height) if (stop) { /* Alpha area */ cairo_rectangle(ct, 0, 0, width/2, height); - ink_cairo_set_source_rgba32(ct, stop->get_rgba32()); + ink_cairo_set_source_color(ct, stop->getColor()); cairo_fill(ct); /* Solid area */ + auto no_alpha = stop->getColor(); + no_alpha.enableOpacity(false); cairo_rectangle(ct, width/2, 0, width, height); - ink_cairo_set_source_rgba32(ct, stop->get_rgba32() | 0xff); + ink_cairo_set_source_color(ct, no_alpha); cairo_fill(ct); } diff --git a/src/ui/widget/gradient-vector-selector.cpp b/src/ui/widget/gradient-vector-selector.cpp index 5fa9f0f5bbc48b26f8c95abaa692ab7c3f98329c..f40bba773b19cfa1b2991e294991515ddb746c86 100644 --- a/src/ui/widget/gradient-vector-selector.cpp +++ b/src/ui/widget/gradient-vector-selector.cpp @@ -35,11 +35,8 @@ #include "object/sp-defs.h" #include "object/sp-stop.h" -#include "ui/selected-color.h" #include "ui/widget/gradient-image.h" -using Inkscape::UI::SelectedColor; - void gr_get_usage_counts(SPDocument *doc, std::map *mapUsageCount ); unsigned long sp_gradient_to_hhssll(SPGradient *gr); @@ -251,11 +248,8 @@ Glib::ustring gr_ellipsize_text(Glib::ustring const &src, size_t maxlen) */ unsigned long sp_gradient_to_hhssll(SPGradient *gr) { - SPStop *stop = gr->getFirstStop(); - unsigned long rgba = stop->get_rgba32(); - float hsl[3]; - SPColor::rgb_to_hsl_floatv (hsl, SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba)); - + auto hsl = gr->getFirstStop()->getColor(); + hsl.convert(Inkscape::Colors::Space::Type::HSL); return ((int)(hsl[0]*100 * 10000)) + ((int)(hsl[1]*100 * 100)) + ((int)(hsl[2]*100 * 1)); } diff --git a/src/ui/widget/gradient-with-stops.cpp b/src/ui/widget/gradient-with-stops.cpp index 56cf8c0047dd30848c72e3ebfa47d91ba3c28513..281c485c52f4d24788f8296ff3d9e5dae2e2c073 100644 --- a/src/ui/widget/gradient-with-stops.cpp +++ b/src/ui/widget/gradient-with-stops.cpp @@ -27,6 +27,7 @@ #include "ui/controller.h" #include "ui/cursor-utils.h" #include "ui/util.h" +#include "util/numeric/converters.h" #include "util/object-renderer.h" // c.f. share/ui/style.css @@ -92,7 +93,7 @@ void GradientWithStops::modified() { SPStop* stop = _gradient->getFirstStop(); while (stop) { _stops.push_back(stop_t { - .offset = stop->offset, .color = stop->getColor(), .opacity = stop->getOpacity() + .offset = stop->offset, .color = stop->getColor() }); stop = stop->getNextStop(); } @@ -451,9 +452,9 @@ void GradientWithStops::draw_func(Cairo::RefPtr const &cr, auto const &bg = _background_color; // stop handle outlines and selection indicator use theme colors: - _template.set_style(".outer", "fill", rgba_to_css_color(fg)); - _template.set_style(".inner", "stroke", rgba_to_css_color(bg)); - _template.set_style(".hole", "fill", rgba_to_css_color(bg)); + _template.set_style(".outer", "fill", gdk_to_css_color(fg)); + _template.set_style(".inner", "stroke", gdk_to_css_color(bg)); + _template.set_style(".hole", "fill", gdk_to_css_color(bg)); auto tip = _tip_template.render(scale); @@ -461,12 +462,12 @@ void GradientWithStops::draw_func(Cairo::RefPtr const &cr, const auto& stop = _stops[i]; // stop handle shows stop color and opacity: - _template.set_style(".color", "fill", rgba_to_css_color(stop.color)); - _template.set_style(".opacity", "opacity", double_to_css_value(stop.opacity)); + _template.set_style(".color", "fill", stop.color.toString(false)); + _template.set_style(".opacity", "opacity", Util::format_number(stop.opacity)); // show/hide selection indicator const auto is_selected = _focused_stop == static_cast(i); - _template.set_style(".selected", "opacity", double_to_css_value(is_selected ? 1 : 0)); + _template.set_style(".selected", "opacity", Util::format_number(is_selected ? 1 : 0)); // render stop handle auto pix = _template.render(scale); diff --git a/src/ui/widget/gradient-with-stops.h b/src/ui/widget/gradient-with-stops.h index 991ff081b727dd0472e02ca4fb259734bf2d0637..5d63688a5c07f384ab08ecc72cdd838b5077a4da 100644 --- a/src/ui/widget/gradient-with-stops.h +++ b/src/ui/widget/gradient-with-stops.h @@ -21,6 +21,7 @@ #include // Gtk::EventSequenceState #include +#include "colors/color.h" #include "helper/auto-connection.h" #include "ui/svg-renderer.h" #include "ui/widget/widget-vfuncs-class-init.h" @@ -116,7 +117,7 @@ private: struct stop_t { double offset; - SPColor color; + Inkscape::Colors::Color color; double opacity; }; std::vector _stops; diff --git a/src/ui/widget/ink-color-wheel.cpp b/src/ui/widget/ink-color-wheel.cpp index 9c73b17ead4091da88fe3484a4e06971b8c3f585..c98e090b2f5e42e5587c32aed55f1729909de843 100644 --- a/src/ui/widget/ink-color-wheel.cpp +++ b/src/ui/widget/ink-color-wheel.cpp @@ -13,20 +13,29 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ -#include -#include +#include "ui/widget/ink-color-wheel.h" + #include <2geom/angle.h> #include <2geom/coord.h> -#include +#include +#include #include #include #include +#include +#include +#include "colors/spaces/enum.h" +#include "colors/spaces/hsluv.h" +#include "colors/utils.h" #include "ui/controller.h" #include "ui/dialog/color-item.h" #include "ui/util.h" #include "ui/widget/bin.h" -#include "ui/widget/ink-color-wheel.h" + +using namespace Inkscape::Colors; +using Inkscape::Colors::Space::Luv; +using Inkscape::Colors::Space::Type; namespace Inkscape::UI::Widget { @@ -73,8 +82,7 @@ struct Intersection final static double lerp(double v0, double v1, double t0, double t1, double t); static ColorPoint lerp(ColorPoint const &v0, ColorPoint const &v1, double t0, double t1, double t); -static guint32 hsv_to_rgb(double h, double s, double v); -static double luminance(guint32 color); +static double luminance(Color const &color); static Geom::Point to_pixel_coordinate(Geom::Point const &point, double scale, double resize); static Geom::Point from_pixel_coordinate(Geom::Point const &point, double scale, double resize); static std::vector to_pixel_coordinate(std::vector const &points, double scale, @@ -84,9 +92,10 @@ static void draw_vertical_padding(ColorPoint p0, ColorPoint p1, int padding, boo /* Base Color Wheel */ -ColorWheel::ColorWheel() +ColorWheel::ColorWheel(Type type, std::vector initial_color) : Gtk::AspectFrame(0.5, 0.5, 1.0, false) , _bin{Gtk::make_managed()} + , _values{type, std::move(initial_color)} , _drawing_area{Gtk::make_managed()} { set_name("ColorWheel"); @@ -108,37 +117,6 @@ ColorWheel::ColorWheel() (*_drawing_area, *this); } -bool ColorWheel::setHue(double h, bool const emit) -{ - h = std::clamp(h, MIN_HUE, MAX_HUE); - bool const changed = std::exchange(_values[0], h) != h; - if (changed && emit) color_changed(); - return changed; -} - -bool ColorWheel::setSaturation(double s, bool const emit) -{ - s = std::clamp(s, MIN_SATURATION, MAX_SATURATION); - bool const changed = std::exchange(_values[1], s) != s; - if (changed && emit) color_changed(); - return changed; -} - -bool ColorWheel::setLightness(double l, bool const emit) -{ - l = std::clamp(l, MIN_LIGHTNESS, MAX_LIGHTNESS); - bool const changed = std::exchange(_values[2], l) != l; - if (changed && emit) color_changed(); - return changed; -} - -void ColorWheel::getValues(double *a, double *b, double *c) const -{ - if (a) *a = _values[0]; - if (b) *b = _values[1]; - if (c) *c = _values[2]; -} - sigc::connection ColorWheel::connect_color_changed(sigc::slot slot) { return _signal_color_changed.connect(std::move(slot)); @@ -202,99 +180,19 @@ bool ColorWheel::on_key_released(GtkEventControllerKey const * /*controller*/, /* HSL Color Wheel */ -bool ColorWheelHSL::setRgb(double r, double g, double b, - bool const overrideHue, bool const emit) +bool ColorWheelHSL::setColor(Color const &color, + bool const /*overrideHue*/, bool const emit) { - auto const old_values = _values; - auto const [min, max] = std::minmax({r, g, b}); - - _values[2] = max; - - if (min == max) { - if (overrideHue) { - _values[0] = 0.0; - } - } else { - if (max == r) { - _values[0] = ((g - b) / (max - min) ) / 6.0; - } else if (max == g) { - _values[0] = ((b - r) / (max - min) + 2) / 6.0; - } else { - _values[0] = ((r - g) / (max - min) + 4) / 6.0; - } - - if (_values[0] < 0.0) { - _values[0] += 1.0; - } - } - - if (max == 0) { - _values[1] = 0; - } else { - _values[1] = (max - min) / max; - } - - bool changed = false; - - if (_values[0] != old_values[0]) { - changed = true; + if (_values.set(color, true)) { _triangle_corners.reset(); - } - - if (_values[1] != old_values[1] || _values[2] != old_values[2]) { - changed = true; _marker_point.reset(); + if (emit) + color_changed(); + else + queue_drawing_area_draw(); + return true; } - - if (changed && emit) color_changed(); - return changed; -} - -void ColorWheelHSL::getRgb(double *r, double *g, double *b) const -{ - guint32 color = getRgb(); - *r = ((color & 0x00ff0000) >> 16) / 255.0; - *g = ((color & 0x0000ff00) >> 8) / 255.0; - *b = ((color & 0x000000ff) ) / 255.0; -} - -void ColorWheelHSL::getRgbV(double *rgb) const -{ - guint32 color = getRgb(); - rgb[0] = ((color & 0x00ff0000) >> 16) / 255.0; - rgb[1] = ((color & 0x0000ff00) >> 8) / 255.0; - rgb[2] = ((color & 0x000000ff) ) / 255.0; -} - -guint32 ColorWheelHSL::getRgb() const -{ - return hsv_to_rgb(_values[0], _values[1], _values[2]); -} - -bool ColorWheelHSL::setHue(double const h, bool const emit) -{ - bool const changed = ColorWheel::setHue(h, emit); - if (changed) _triangle_corners.reset(); - return changed; -} - -bool ColorWheelHSL::setSaturation(double const s, bool const emit) -{ - bool const changed = ColorWheel::setSaturation(s, emit); - if (changed) _marker_point.reset(); - return changed; -} - -bool ColorWheelHSL::setLightness(double const l, bool const emit) -{ - bool const changed = ColorWheel::setLightness(l, emit); - if (changed) _marker_point.reset(); - return changed; -} - -void ColorWheelHSL::getHsl(double *h, double *s, double *l) const -{ - getValues(h, s, l); + return false; } void ColorWheelHSL::update_ring_source() @@ -327,7 +225,7 @@ void ColorWheelHSL::update_ring_source() angle += 2.0 * M_PI; } double hue = angle/(2.0 * M_PI); - *p++ = hsv_to_rgb(hue, 1.0, 1.0); + *p++ = Color(Type::HSV, {hue, 1.0, 1.0}).toARGB(); } } } @@ -391,13 +289,13 @@ ColorWheelHSL::update_triangle_source() auto p = _buffer_triangle.data() + y * (stride / 4); int x = 0; for (; x <= x_start; ++x) { - *p++ = side0.get_color(); + *p++ = side0.color.toARGB(); } for (; x < x_end; ++x) { - *p++ = lerp(side0, side1, side0.x, side1.x, x).get_color(); + *p++ = lerp(side0, side1, side0.x, side1.x, x).color.toARGB(); } for (; x < width; ++x) { - *p++ = side1.get_color(); + *p++ = side1.color.toARGB(); } } } @@ -454,9 +352,8 @@ void ColorWheelHSL::on_drawing_area_draw(Cairo::RefPtr const &cr cr->stroke(); cr->restore(); // Paint line on ring - double l = 0.0; - guint32 color_on_ring = hsv_to_rgb(_values[0], 1.0, 1.0); - if (luminance(color_on_ring) < 0.5) l = 1.0; + auto color_on_ring = Color(Type::HSV, {_values[0], 1.0, 1.0}); + double l = luminance(color_on_ring) < 0.5 ? 1.0 : 0.0; cr->save(); cr->set_source_rgb(l, l, l); cr->move_to(cx + cos(_values[0] * M_PI * 2.0) * (r_min + 1), @@ -478,9 +375,7 @@ void ColorWheelHSL::on_drawing_area_draw(Cairo::RefPtr const &cr // Draw marker auto const &[mx, my] = get_marker_point(); - double a = 0.0; - guint32 color_at_marker = getRgb(); - if (luminance(color_at_marker) < 0.5) a = 1.0; + double a = luminance(getColor()) < 0.5 ? 1.0 : 0.0; cr->set_source_rgb(a, a, a); cr->begin_new_path(); cr->arc(mx, my, marker_radius, 0, 2 * M_PI); @@ -567,10 +462,11 @@ bool ColorWheelHSL::_set_from_xy(double const x, double const y) double yt = lerp(0.0, 1.0, -dy, dy, yp); yt = std::clamp(yt, 0.0, 1.0); - ColorPoint c0(0, 0, yt, yt, yt); // Grey point along base. - ColorPoint c1(0, 0, hsv_to_rgb(_values[0], 1, 1)); // Hue point at apex + ColorPoint c0(0, 0, Color(Type::RGB, {yt, yt, yt})); // Grey point along base. + ColorPoint c1(0, 0, Color(Type::HSV, {_values[0], 1, 1})); // Hue point at apex ColorPoint c = lerp(c0, c1, 0, 1, xt); - return setRgb(c.r, c.g, c.b, false); // Don't override previous hue. + c.color.setOpacity(_values.getOpacity()); // Remember opacity + return setColor(c.color, false); // Don't override previous hue. } bool ColorWheelHSL::set_from_xy_delta(double const dx, double const dy) @@ -625,8 +521,7 @@ void ColorWheelHSL::_update_ring_color(double x, double y) } angle /= 2.0 * M_PI; - bool const changed = std::exchange(_values[0], angle) != angle; - if (changed) { + if (_values.set(0, angle)) { _triangle_corners.reset(); color_changed(); } @@ -689,7 +584,6 @@ bool ColorWheelHSL::on_key_pressed(GtkEventControllerKey const * /*controller*/, nullptr); static constexpr double delta_hue = 2.0 / MAX_HUE; - auto const old_hue = _values[0]; auto dx = 0.0, dy = 0.0; switch (key) { @@ -717,21 +611,12 @@ bool ColorWheelHSL::on_key_pressed(GtkEventControllerKey const * /*controller*/, bool changed = false; if (_focus_on_ring) { - _values[0] += -(dx != 0 ? dx : dy) * delta_hue; + changed = _values.set(0, _values[0] - ((dx != 0 ? dx : dy) * delta_hue)); } else { changed = set_from_xy_delta(dx, dy); } - if (_values[0] >= 1.0) { - _values[0] -= 1.0; - } else if (_values[0] < 0.0) { - _values[0] += 1.0; - } - - if (_values[0] != old_hue) { - _triangle_corners.reset(); - changed = true; - } + _values.normalize(); if (changed) { color_changed(); @@ -780,9 +665,9 @@ std::array const &ColorWheelHSL::get_triangle_corners() auto const y1 = cy - std::sin(angle2) * r_min; auto const x2 = cx + std::cos(angle4) * r_min; auto const y2 = cy - std::sin(angle4) * r_min; - p0 = {x0, y0, hsv_to_rgb(_values[0], 1.0, 1.0)}; - p1 = {x1, y1, hsv_to_rgb(_values[0], 1.0, 0.0)}; - p2 = {x2, y2, hsv_to_rgb(_values[0], 0.0, 1.0)}; + p0 = {x0, y0, Color(Type::HSV, {_values[0], 1.0, 1.0})}; + p1 = {x1, y1, Color(Type::HSV, {_values[0], 1.0, 0.0})}; + p2 = {x2, y2, Color(Type::HSV, {_values[0], 0.0, 1.0})}; return *_triangle_corners; } @@ -803,61 +688,36 @@ Geom::Point const &ColorWheelHSL::get_marker_point() return *_marker_point; } +ColorWheelHSL::ColorWheelHSL() + : Glib::ObjectBase{"ColorWheelHSL"} + , WidgetVfuncsClassInit{} + // All the calculations are based on HSV, not HSL + , ColorWheel(Type::HSV, {0, 0, 0, 1}) +{} /* HSLuv Color Wheel */ ColorWheelHSLuv::ColorWheelHSLuv() + : ColorWheel(Type::HSLUV, {0, 1, 0.5, 1}) { - _picker_geometry = std::make_unique(); - setHsluv(MIN_HUE, MAX_SATURATION, 0.5 * MAX_LIGHTNESS); + _picker_geometry = std::make_unique(); } -bool ColorWheelHSLuv::setRgb(double r, double g, double b, +bool ColorWheelHSLuv::setColor(Color const &color, bool /*overrideHue*/, bool const emit) { - auto hsl = Hsluv::rgb_to_hsluv(r, g, b); - bool changed = false; - changed |= setHue (hsl[0], false); - changed |= setSaturation(hsl[1], false); - changed |= setLightness (hsl[2], false); - if (changed && emit) color_changed(); - return changed; -} - -void ColorWheelHSLuv::getRgb(double *r, double *g, double *b) const -{ - auto rgb = Hsluv::hsluv_to_rgb(_values[0], _values[1], _values[2]); - *r = rgb[0]; - *g = rgb[1]; - *b = rgb[2]; -} - -void ColorWheelHSLuv::getRgbV(double *rgb) const -{ - auto converted = Hsluv::hsluv_to_rgb(_values[0], _values[1], _values[2]); - for (size_t i : {0, 1, 2}) { - rgb[i] = converted[i]; + if (_values.set(color, true)) { + assert(_values.getSpace()->getType() == Type::HSLUV); + updateGeometry(); + _scale = OUTER_CIRCLE_RADIUS / _picker_geometry->outer_circle_radius; + _updatePolygon(); + if (emit) + color_changed(); + else + queue_drawing_area_draw(); + return true; } -} - -guint32 ColorWheelHSLuv::getRgb() const -{ - auto rgb = Hsluv::hsluv_to_rgb(_values[0], _values[1], _values[2]); - return ( - (static_cast(rgb[0] * 255.0) << 16) | - (static_cast(rgb[1] * 255.0) << 8) | - (static_cast(rgb[2] * 255.0) ) - ); -} - -bool ColorWheelHSLuv::setHsluv(double h, double s, double l) -{ - bool changed = false; - changed |= setHue (h, false); - changed |= setSaturation(s, false); - changed |= setLightness (l, false); - if (changed) color_changed(); - return changed; + return false; } /** @@ -866,10 +726,10 @@ bool ColorWheelHSLuv::setHsluv(double h, double s, double l) void ColorWheelHSLuv::updateGeometry() { // Separate from the extremes to avoid overlapping intersections - double lightness = std::clamp(_values[2] + 0.01, 0.1, 99.9); + double lightness = std::clamp((_values[2] * 100) + 0.01, 0.1, 99.9); // Find the lines bounding the gamut polygon - auto const lines = Hsluv::get_bounds(lightness); + auto const lines = Space::HSLuv::get_bounds(lightness); // Find the line closest to origin Geom::Line const *closest_line = nullptr; @@ -929,22 +789,6 @@ void ColorWheelHSLuv::updateGeometry() _picker_geometry->inner_circle_radius = closest_distance; } -bool ColorWheelHSLuv::setLightness(double const l, bool const emit) -{ - bool const changed = ColorWheel::setLightness(l, emit); - if (changed) { - updateGeometry(); - _scale = OUTER_CIRCLE_RADIUS / _picker_geometry->outer_circle_radius; - _updatePolygon(); - } - return changed; -} - -void ColorWheelHSLuv::getHsluv(double *h, double *s, double *l) const -{ - getValues(h, s, l); -} - static Geom::IntPoint _getMargin(Gtk::Allocation const &allocation) { int const width = allocation.get_width(); @@ -967,7 +811,7 @@ inline static int _getAllocationSize(Gtk::Allocation const &allocation) /// Detect whether we're at the top or bottom vertex of the color space. bool ColorWheelHSLuv::_vertex() const { - return _values[2] < VERTEX_EPSILON || _values[2] > MAX_LIGHTNESS - VERTEX_EPSILON; + return _values[2] < VERTEX_EPSILON || _values[2] > 1.0 - VERTEX_EPSILON; } void ColorWheelHSLuv::on_drawing_area_draw(::Cairo::RefPtr<::Cairo::Context> const &cr, int, int) @@ -1027,7 +871,7 @@ void ColorWheelHSLuv::on_drawing_area_draw(::Cairo::RefPtr<::Cairo::Context> con cr->unset_dash(); // Contrast - auto [gray, alpha] = Hsluv::get_contrasting_color(Hsluv::perceptual_lightness(_values[2])); + auto [gray, alpha] = get_contrasting_color(perceptual_lightness(_values[2])); cr->set_source_rgba(gray, gray, gray, alpha); // Draw inscribed circle @@ -1044,7 +888,7 @@ void ColorWheelHSLuv::on_drawing_area_draw(::Cairo::RefPtr<::Cairo::Context> con cr->fill(); // Draw marker - auto luv = Hsluv::hsluv_to_luv(_values.data()); + auto luv = Luv::toCoordinates(_values.converted(Type::LUV)->getValues()); auto mp = to_pixel_coordinate({luv[1], luv[2]}, _scale, resize) + margin; cr->set_line_width(inner_stroke_width); @@ -1072,12 +916,11 @@ bool ColorWheelHSLuv::_set_from_xy(double const x, double const y) double const resize = std::min(width, height) / static_cast(SIZE); auto const p = from_pixel_coordinate(Geom::Point(x, y) - _getMargin(allocation), _scale, resize); - auto hsluv = Hsluv::luv_to_hsluv(_values[2], p[Geom::X], p[Geom::Y]); - bool changed = false; - changed |= setHue (hsluv[0], false); - changed |= setSaturation(hsluv[1], false); - if (changed) color_changed(); - return changed; + if (_values.set(Color(Type::LUV, Luv::fromCoordinates({_values[2] * 100, p[Geom::X], p[Geom::Y]})), true)) { + color_changed(); + return true; + } + return false; } void ColorWheelHSLuv::_updatePolygon() @@ -1115,21 +958,21 @@ void ColorWheelHSLuv::_updatePolygon() _buffer_polygon.resize(_cache_size.y() * stride / 4); std::vector buffer_line(stride / 4); - ColorPoint clr; auto const square_center = Geom::IntPoint(_square_size / 2, _square_size / 2); + std::vector color_vals = {_values[2] * 100, 0, 0}; // Set the color of each pixel/square for (int y = bounding_min[Geom::Y]; y < bounding_max[Geom::Y]; y++) { for (int x = bounding_min[Geom::X]; x < bounding_max[Geom::X]; x++) { auto pos = Geom::IntPoint(x * _square_size, y * _square_size); auto point = from_pixel_coordinate(pos + square_center - margin, _scale, resize); + color_vals[1] = point[Geom::X]; + color_vals[2] = point[Geom::Y]; - auto rgb = Hsluv::luv_to_rgb(_values[2], point[Geom::X], point[Geom::Y]); // safe with _values[2] == 0 - clr.set_color(rgb); - + auto color = Color(Type::LUV, Luv::fromCoordinates(color_vals)); guint32 *p = buffer_line.data() + (x * _square_size); for (int i = 0; i < _square_size; i++) { - p[i] = clr.get_color(); + p[i] = color.toARGB(); } } @@ -1195,29 +1038,29 @@ bool ColorWheelHSLuv::on_key_pressed(GtkEventControllerKey const * /*controller* nullptr); // Get current point - auto luv = Hsluv::hsluv_to_luv(_values.data()); + auto luv = *_values.converted(Type::LUV); double const marker_move = 1.0 / _scale; switch (key) { case GDK_KEY_Up: case GDK_KEY_KP_Up: - luv[2] += marker_move; + luv.set(2, luv[2] + marker_move); consumed = true; break; case GDK_KEY_Down: case GDK_KEY_KP_Down: - luv[2] -= marker_move; + luv.set(2, luv[2] - marker_move); consumed = true; break; case GDK_KEY_Left: case GDK_KEY_KP_Left: - luv[1] -= marker_move; + luv.set(1, luv[1] - marker_move); consumed = true; break; case GDK_KEY_Right: case GDK_KEY_KP_Right: - luv[1] += marker_move; + luv.set(1, luv[1] + marker_move); consumed = true; } @@ -1225,45 +1068,31 @@ bool ColorWheelHSLuv::on_key_pressed(GtkEventControllerKey const * /*controller* _adjusting = true; - auto const hsluv = Hsluv::luv_to_hsluv(luv[0], luv[1], luv[2]); - bool changed = false; - changed |= setHue (hsluv[0], false); - changed |= setSaturation(hsluv[1], false); - if (changed) color_changed(); + if (_values.set(luv, true)) + color_changed(); return true; } /* ColorPoint */ ColorPoint::ColorPoint() - : x(0), y(0), r(0), g(0), b(0) + : x(0) + , y(0) + , color(0x0) {} -ColorPoint::ColorPoint(double x, double y, double r, double g, double b) - : x(x), y(y), r(r), g(g), b(b) +ColorPoint::ColorPoint(double x, double y, Color c) + : x(x) + , y(y) + , color(std::move(c)) {} -ColorPoint::ColorPoint(double x, double y, guint color) +ColorPoint::ColorPoint(double x, double y, guint c) : x(x) , y(y) - , r(((color & 0xff0000) >> 16) / 255.0) - , g(((color & 0x00ff00) >> 8) / 255.0) - , b(((color & 0x0000ff) ) / 255.0) + , color(c) {} -guint32 ColorPoint::get_color() const -{ - return (static_cast(r * 255) << 16 | - static_cast(g * 255) << 8 | - static_cast(b * 255) - ); -}; - -std::pair ColorPoint::get_xy() const -{ - return {x, y}; -} - static double lerp(double v0, double v1, double t0, double t1, double t) { double const s = (t0 != t1) ? (t - t0) / (t1 - t0) : 0.0; @@ -1275,61 +1104,21 @@ static ColorPoint lerp(ColorPoint const &v0, ColorPoint const &v1, double t0, do { double x = lerp(v0.x, v1.x, t0, t1, t); double y = lerp(v0.y, v1.y, t0, t1, t); - double r = lerp(v0.r, v1.r, t0, t1, t); - double g = lerp(v0.g, v1.g, t0, t1, t); - double b = lerp(v0.b, v1.b, t0, t1, t); - - return ColorPoint(x, y, r, g, b); -} -/** - * @param h Hue. Between 0 and 1. - * @param s Saturation. Between 0 and 1. - * @param v Value. Between 0 and 1. - */ -static guint32 hsv_to_rgb(double h, double s, double v) -{ - h = std::clamp(h, 0.0, 1.0); - s = std::clamp(s, 0.0, 1.0); - v = std::clamp(v, 0.0, 1.0); - - double r = v; - double g = v; - double b = v; - - if (s != 0.0) { - if (h == 1.0) h = 0.0; - h *= 6.0; - - double f = h - (int)h; - double p = v * (1.0 - s); - double q = v * (1.0 - s * f); - double t = v * (1.0 - s * (1.0 - f)); - - switch (static_cast(h)) { - case 0: r = v; g = t; b = p; break; - case 1: r = q; g = v; b = p; break; - case 2: r = p; g = v; b = t; break; - case 3: r = p; g = q; b = v; break; - case 4: r = t; g = p; b = v; break; - case 5: r = v; g = p; b = q; break; - default: g_assert_not_reached(); - } - } + auto r0 = *v0.color.converted(Type::RGB); + auto r1 = *v1.color.converted(Type::RGB); + double r = lerp(r0[0], r1[0], t0, t1, t); + double g = lerp(r0[1], r1[1], t0, t1, t); + double b = lerp(r0[2], r1[2], t0, t1, t); - guint32 rgb = (static_cast(floor(r * 255 + 0.5)) << 16) | - (static_cast(floor(g * 255 + 0.5)) << 8) | - (static_cast(floor(b * 255 + 0.5)) ); - return rgb; + return ColorPoint(x, y, Color(Type::RGB, {r, g, b})); } -// N.B. We also have util:get_luminance(), but that uses different weightings..! -double luminance(guint32 color) +// N.B. We also have Color:get_perceptual_lightness(), but that uses different weightings..! +double luminance(Color const &color) { - double r = ((color & 0xff0000) >> 16) / 255.0; - double g = ((color & 0xff00) >> 8) / 255.0; - double b = ((color & 0xff) ) / 255.0; - return (r * 0.2125 + g * 0.7154 + b * 0.0721); + auto c = *color.converted(Type::RGB); + return (c[0] * 0.2125 + c[1] * 0.7154 + c[2] * 0.0721); } /** @@ -1427,9 +1216,9 @@ void draw_vertical_padding(ColorPoint p0, ColorPoint p1, int padding, bool pad_u // paint the padding vertically above or below this point for (int offset = 0; offset <= padding; ++offset) { if (pad_upwards && (point.y - offset) >= 0) { - *(p - (offset * stride)) = point.get_color(); + *(p - (offset * stride)) = point.color.toARGB(); } else if (!pad_upwards && (point.y + offset) < height) { - *(p + (offset * stride)) = point.get_color(); + *(p + (offset * stride)) = point.color.toARGB(); } } ++p; diff --git a/src/ui/widget/ink-color-wheel.h b/src/ui/widget/ink-color-wheel.h index 57aa0135edf34fcfe954a044057c3c1714f1e35d..3949e96ab547da74744745341603f9f632f1b784 100644 --- a/src/ui/widget/ink-color-wheel.h +++ b/src/ui/widget/ink-color-wheel.h @@ -28,8 +28,7 @@ #include #include // Gtk::EventSequenceState -#include "hsluv.h" - +#include "colors/color.h" #include "ui/widget/widget-vfuncs-class-init.h" // for focus namespace Gtk { @@ -37,6 +36,10 @@ class DrawingArea; class GestureClick; } // namespace Gtk +namespace Inkscape::Colors { +class Color; +} // namespace Inkscape::Colors + namespace Inkscape::UI::Widget { class Bin; @@ -44,25 +47,15 @@ class Bin; struct ColorPoint final { ColorPoint(); - ColorPoint(double x, double y, double r, double g, double b); + ColorPoint(double x, double y, Colors::Color color); ColorPoint(double x, double y, guint color); - guint32 get_color() const; - std::pair get_xy() const; - - void set_color(Hsluv::Triplet const &rgb) - { - r = rgb[0]; - g = rgb[1]; - b = rgb[2]; - } + std::pair get_xy() const { return {x, y}; } // eurgh! double x; double y; - double r; - double g; - double b; + Colors::Color color; }; /** @@ -72,38 +65,23 @@ struct ColorPoint final class ColorWheel : public Gtk::AspectFrame { public: - ColorWheel(); + ColorWheel(Colors::Space::Type type, std::vector initial_color); /// Set the RGB of the wheel. If @a emit is true & hue changes, we call color_changed() for you - /// @param r the red component, from 0.0 to 1.0 - /// @param g the green component, from 0.0 to 1.0 - /// @param b the blue component, from 0.0 to 1.0 /// @param overrideHue whether to set hue to 0 if min==max(r,g,b) – only used by ColorwheelHSL… /// @param emit false if you want to manually call color_changed() e.g. to avoid multiple emits /// @return whether or not the value actually changed, to enable avoiding redraw if it does not - virtual bool setRgb(double r, double g, double b, + virtual bool setColor(Colors::Color const &color, bool overrideHue = true, bool emit = true) = 0; - virtual void getRgb(double *r, double *g, double *b) const = 0; - virtual void getRgbV(double *rgb) const = 0; - virtual guint32 getRgb() const = 0; - - /// Set the hue of the wheel. If @a emit is true & hue changes, we call color_changed() for you - /// @param emit false if you want to manually call color_changed() e.g. to avoid multiple emits - /// @return whether or not the value actually changed, to enable avoiding redraw if it does not - virtual bool setHue (double h, bool emit = true); - /// Ditto setHue(), but changes the saturation instead. - virtual bool setSaturation(double s, bool emit = true); - /// Ditto setHue(), but changes the lightness instead. - virtual bool setLightness (double l, bool emit = true); + virtual Colors::Color getColor() const { return _values; } - void getValues(double *a, double *b, double *c) const; bool isAdjusting() const { return _adjusting; } /// Connect a slot to be called after the color has changed. sigc::connection connect_color_changed(sigc::slot); protected: - std::array _values = {}; + Colors::Color _values; bool _adjusting = false; /// Call when color has changed! Emits signal_color_changed & calls _drawing_area->queue_draw() @@ -143,18 +121,9 @@ class ColorWheelHSL , public ColorWheel { public: - ColorWheelHSL() - : Glib::ObjectBase{"ColorWheelHSL"} - , WidgetVfuncsClassInit{} {} - bool setHue (double h, bool emit = true) final; - bool setSaturation(double s, bool emit = true) final; - bool setLightness (double l, bool emit = true) final; - bool setRgb(double r, double g, double b, + ColorWheelHSL(); + bool setColor(Colors::Color const &color, bool overrideHue = true, bool emit = true) override; - void getRgb(double *r, double *g, double *b) const override; - void getRgbV(double *rgb) const override; - guint32 getRgb() const override; - void getHsl(double *h, double *s, double *l) const; private: void on_drawing_area_size(int width, int height, int baseline) override; @@ -201,6 +170,16 @@ private: [[nodiscard]] TriangleCorners update_triangle_source(); }; +/** + * Used to represent the in RGB gamut colors polygon of the HSLuv color wheel. + */ +struct PickerGeometry { + std::vector vertices; ///< Vertices, in counter-clockwise order. + double outer_circle_radius; ///< Smallest circle with center at origin such that polygon fits inside. + double inner_circle_radius; ///< Largest circle with center at origin such that it fits inside polygon. +}; + + /** * @class ColorWheelHSLuv */ @@ -210,16 +189,9 @@ public: ColorWheelHSLuv(); /// See base doc & N.B. that overrideHue is unused by this class - bool setRgb(double r, double g, double b, + bool setColor(Colors::Color const &color, bool overrideHue = true, bool emit = true) override; - void getRgb(double *r, double *g, double *b) const override; - void getRgbV(double *rgb) const override; - guint32 getRgb() const override; - - bool setHsluv(double h, double s, double l); - bool setLightness(double l, bool emit) final; - void getHsluv(double *h, double *s, double *l) const; void updateGeometry(); private: @@ -239,7 +211,7 @@ private: unsigned keyval, unsigned keycode, GdkModifierType state) final; double _scale = 1.0; - std::unique_ptr _picker_geometry; + std::unique_ptr _picker_geometry; std::vector _buffer_polygon; Cairo::RefPtr<::Cairo::ImageSurface> _surface_polygon; Geom::IntPoint _cache_size; diff --git a/src/ui/widget/layer-selector.cpp b/src/ui/widget/layer-selector.cpp index f96d680c415550aa5f921783da819819df3fa7db..fbe6092d34615c126beaf6bced9a002a15d70303 100644 --- a/src/ui/widget/layer-selector.cpp +++ b/src/ui/widget/layer-selector.cpp @@ -157,14 +157,14 @@ void LayerSelector::_layerModified() if (active) { _layer_label.set_text(_layer->defaultLabel()); - color_str = SPColor(_layer->highlight_color()).toString(); + color_str = _layer->highlight_color().converted(Colors::Space::Type::RGB)->toString(false); } else { _layer_label.set_markup(_layer ? "[root]" : "nothing"); } + auto css = Glib::ustring::compose("#%1.%2 label { border-color: %3; }", cssName, getThisCssClass(), color_str); // Other border properties are set in share/ui/style.css - _label_style->load_from_data(Glib::ustring::compose("#%1.%2 label { border-color: %3; }", - cssName, getThisCssClass(), color_str)); + _label_style->load_from_data(css); _hide_layer_connection.block(); _lock_layer_connection.block(); diff --git a/src/ui/widget/objects-dialog-cells.cpp b/src/ui/widget/objects-dialog-cells.cpp index 7e6e921cab8d034e5935f971fcfc030376153a98..abeb49ec12a2f56a2088c51403f8a1ac661dfbbc 100644 --- a/src/ui/widget/objects-dialog-cells.cpp +++ b/src/ui/widget/objects-dialog-cells.cpp @@ -9,11 +9,12 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ +#include "colors/color.h" +#include "display/cairo-utils.h" #include "ui/widget/objects-dialog-cells.h" #include -#include "color-rgba.h" #include "preferences.h" namespace Inkscape::UI::Widget { @@ -46,8 +47,8 @@ void ColorTagRenderer::snapshot_vfunc(Glib::RefPtr const &snapsho { auto const cr = snapshot->append_cairo(cell_area); cr->rectangle(cell_area.get_x(), cell_area.get_y(), cell_area.get_width(), cell_area.get_height()); - ColorRGBA color(_property_color.get_value()); - cr->set_source_rgb(color[0], color[1], color[2]); + auto color = Colors::Color(_property_color.get_value()); // RGBA + ink_cairo_set_source_color(cr, color); cr->fill(); if (_property_hover.get_value()) { diff --git a/src/ui/widget/oklab-color-wheel.cpp b/src/ui/widget/oklab-color-wheel.cpp index d4b15383913ec79fd2cbaad57793bc7fa6eb0c22..81174e5d60c4ffc6980829f7722adcc8bae49cd9 100644 --- a/src/ui/widget/oklab-color-wheel.cpp +++ b/src/ui/widget/oklab-color-wheel.cpp @@ -17,66 +17,36 @@ #include #include +#include "colors/spaces/enum.h" +#include "colors/spaces/oklch.h" +#include "colors/utils.h" #include "display/cairo-utils.h" -#include "oklab.h" + +using namespace Inkscape::Colors; namespace Inkscape::UI::Widget { OKWheel::OKWheel() -{ - // Set to black - _values[H] = 0; - _values[S] = 0; - _values[L] = 0; -} + : ColorWheel(Space::Type::OKHSL, {0, 0, 0, 1}) +{} -bool OKWheel::setRgb(double r, double g, double b, +bool OKWheel::setColor(Color const &color, bool /*overrideHue*/, bool const emit) { - using namespace Oklab; - - auto [h, s, l] = oklab_to_okhsl(rgb_to_oklab({ r, g, b })); - - auto const h2 = h * 2.0 * M_PI; - bool const changed_hue = _values[H] != h2; - _values[H] = h2; - - bool const changed_saturation = _values[S] != s; - _values[S] = s; - - bool const changed_lightness = _values[L] != l; - _values[L] = l; - - if (changed_lightness) { + if (_values.set(color, true)) { _updateChromaBounds(); _redrawDisc(); queue_drawing_area_draw(); + if (emit) + color_changed(); + return true; } - - bool const changed = changed_hue || changed_saturation || changed_lightness; - if (changed && emit) color_changed(); - return changed; + return false; } -void OKWheel::getRgb(double *red, double *green, double *blue) const +Color OKWheel::getColor() const { - using namespace Oklab; - auto [r, g, b] = oklab_to_rgb(okhsl_to_oklab({ _values[H] / (2.0 * M_PI), _values[S], _values[L] })); - *red = r; - *green = g; - *blue = b; -} - -guint32 OKWheel::getRgb() const -{ - guint32 result = 0x0; - double rgb[3]; - getRgbV(rgb); - for (auto component : rgb) { - result <<= 8; - result |= SP_COLOR_F_TO_U(component); - } - return result; + return _values; } /** @brief Compute the chroma bounds around the picker disc. @@ -90,7 +60,7 @@ void OKWheel::_updateChromaBounds() double const angle_step = 360.0 / CHROMA_BOUND_SAMPLES; double hue_angle_deg = 0.0; for (unsigned i = 0; i < CHROMA_BOUND_SAMPLES; i++) { - _bounds[i] = Oklab::max_chroma(_values[L], hue_angle_deg); + _bounds[i] = Space::OkLch::max_chroma(_values[L], hue_angle_deg); hue_angle_deg += angle_step; } } @@ -127,13 +97,9 @@ bool OKWheel::_updateDimensions() */ uint32_t OKWheel::_discColor(Geom::Point const &point) const { - using namespace Oklab; - using Display::AssembleARGB32; - double saturation = point.length(); if (saturation == 0.0) { - auto [r, g, b] = oklab_to_rgb({ _values[L], 0.0, 0.0 }); - return AssembleARGB32(0xFF, (guint)(r * 255.5), (guint)(g * 255.5), (guint)(b * 255.5)); + return Color(Space::Type::OKLCH, {_values[L], 0, 0}).toARGB(); } else if (saturation > 1.0) { saturation = 1.0; } @@ -152,8 +118,7 @@ uint32_t OKWheel::_discColor(Geom::Point const &point) const double const chroma_bound_estimate = Geom::lerp(t, _bounds[previous_sample], _bounds[next_sample]); double const absolute_chroma = chroma_bound_estimate * saturation; - auto [r, g, b] = oklab_to_rgb(oklch_radians_to_oklab({ _values[L], absolute_chroma, hue_radians })); - return AssembleARGB32(0xFF, (guint)(r * 255.5), (guint)(g * 255.5), (guint)(b * 255.5)); + return Color(Space::Type::OKLCH, {_values[L], absolute_chroma, Geom::deg_from_rad(hue_radians) / 360}).toARGB(); } /** @brief Returns the position of the current color in the coordinates @@ -165,7 +130,7 @@ uint32_t OKWheel::_discColor(Geom::Point const &point) const Geom::Point OKWheel::_curColorWheelCoords() const { Geom::Point result; - Geom::sincos(_values[H], result.y(), result.x()); + Geom::sincos(Geom::Angle::from_degrees(_values[H] * 360), result.y(), result.x()); result *= _values[S]; return result * Geom::Scale(_disc_radius, -_disc_radius); } @@ -198,15 +163,13 @@ void OKWheel::on_drawing_area_draw(Cairo::RefPtr const &cr, int, cr->close_path(); // Fill the halo with the current color. { - double r, g, b; - getRgb(&r, &g, &b); - cr->set_source_rgba(r, g, b, 1.0); + ink_cairo_set_source_color(cr, getColor()); } cr->fill_preserve(); // Stroke the border of the halo. { - auto [gray, alpha] = Hsluv::get_contrasting_color(_values[L]); + auto [gray, alpha] = get_contrasting_color(_values[L]); cr->set_source_rgba(gray, gray, gray, alpha); } cr->set_line_width(HALO_STROKE); @@ -257,18 +220,17 @@ Geom::Point OKWheel::_event2abstract(Geom::Point const &event_pt) const */ bool OKWheel::_setColor(Geom::Point const &pt, bool const emit) { - auto const s = std::clamp(pt.length(), 0.0, 1.0); - Geom::Angle clicked_hue = _values[S] ? Geom::atan2(pt) : 0.0; - auto const h = clicked_hue.radians0(); - bool const changed = _values[S] != s || _values[H] != h; - if (changed) { - _values[S] = s; - _values[H] = h; - if (emit) color_changed(); + bool s = _values.set(S, pt.length()); + bool h = _values.set(H, Geom::deg_from_rad(clicked_hue.radians0()) / 360); + if (s || h) { + _values.normalize(); + if (emit) + color_changed(); + return true; } - return changed; + return false; } /** @brief Handle a left mouse click on the widget. diff --git a/src/ui/widget/oklab-color-wheel.h b/src/ui/widget/oklab-color-wheel.h index db91196d693d10fc700f6ea2178950a2aa016e51..0662e58380c5629ba12e73385c4f6a7c65be8780 100644 --- a/src/ui/widget/oklab-color-wheel.h +++ b/src/ui/widget/oklab-color-wheel.h @@ -29,13 +29,10 @@ public: /** @brief Set the displayed color to the specified gamma-compressed sRGB color. */ /// See base doc & N.B. that overrideHue is unused by this class - bool setRgb(double r, double g, double b, + bool setColor(Colors::Color const &color, bool overrideHue = true, bool emit = true) override; - /** @brief Get the gamma-compressed sRGB color from the picker wheel. */ - void getRgb(double *r, double *g, double *b) const override; - void getRgbV(double *rgb) const override { getRgb(rgb, rgb + 1, rgb + 2); } - guint32 getRgb() const override; + Colors::Color getColor() const override; private: void on_drawing_area_draw(Cairo::RefPtr const &cr, int, int) override; diff --git a/src/ui/widget/page-properties.cpp b/src/ui/widget/page-properties.cpp index d896889708549c45f51cc0e12468a6c4b9e49aff..e4736770255504ce0adaa69878bd98db8277c2f3 100644 --- a/src/ui/widget/page-properties.cpp +++ b/src/ui/widget/page-properties.cpp @@ -157,27 +157,16 @@ public: , _linked_viewbox_scale (get_widget (_builder, "linked-scale-img")) , _display_units (get_derived_widget (_builder, "display-units")) , _page_units (get_derived_widget (_builder, "page-units")) + , _backgnd_color_picker (get_derived_widget (_builder, "background-color", _("Background color"), false)) + , _border_color_picker (get_derived_widget (_builder, "border-color", _("Border and shadow color"), true)) + , _desk_color_picker (get_derived_widget (_builder, "desk-color", _("Desk color"), false)) // clang-format-on { - _backgnd_color_picker = std::make_unique( - _("Background color"), "", 0xffffff00, true, - &get_widget(_builder, "background-color")); - _backgnd_color_picker->use_transparency(false); - - _border_color_picker = std::make_unique( - _("Border and shadow color"), "", 0x0000001f, true, - &get_widget(_builder, "border-color")); - - _desk_color_picker = std::make_unique( - _("Desk color"), "", 0xd0d0d0ff, true, - &get_widget(_builder, "desk-color")); - _desk_color_picker->use_transparency(false); - for (auto element : {Color::Background, Color::Border, Color::Desk}) { - get_color_picker(element).connectChanged([=](unsigned const rgba) { - update_preview_color(element, rgba); + get_color_picker(element).connectChanged([=](Colors::Color const &color) { + update_preview_color(element, color); if (_update.pending()) return; - _signal_color_changed.emit(rgba, element); + _signal_color_changed.emit(color, element); }); } @@ -275,11 +264,11 @@ private: } } - void update_preview_color(Color const element, unsigned const rgba) { + void update_preview_color(Color const element, Colors::Color const &color) { switch (element) { - case Color::Desk: _preview->set_desk_color(rgba); break; - case Color::Border: _preview->set_border_color(rgba); break; - case Color::Background: _preview->set_page_color(rgba); break; + case Color::Desk: _preview->set_desk_color(color.toRGBA()); break; + case Color::Border: _preview->set_border_color(color.toRGBA()); break; + case Color::Background: _preview->set_page_color(color.toRGBA()); break; } } @@ -423,10 +412,10 @@ private: _signal_unit_changed.emit(new_unit, Units::Document); } - void set_color(Color element, unsigned int color) override { + void set_color(Color element, Colors::Color const &color) override { auto scoped(_update.block()); - get_color_picker(element).setRgba32(color); + get_color_picker(element).setColor(color); update_preview_color(element, color); } @@ -481,9 +470,9 @@ private: ColorPicker& get_color_picker(Color element) { switch (element) { - case Color::Background: return *_backgnd_color_picker; - case Color::Desk: return *_desk_color_picker; - case Color::Border: return *_border_color_picker; + case Color::Background: return _backgnd_color_picker; + case Color::Desk: return _desk_color_picker; + case Color::Border: return _border_color_picker; default: throw std::runtime_error("missing case in get_color_picker"); @@ -566,9 +555,9 @@ private: MathSpinButton &_viewbox_y; MathSpinButton &_viewbox_width; MathSpinButton &_viewbox_height; - std::unique_ptr _backgnd_color_picker; - std::unique_ptr _border_color_picker; - std::unique_ptr _desk_color_picker; + ColorPicker &_backgnd_color_picker; + ColorPicker &_border_color_picker; + ColorPicker &_desk_color_picker; std::vector _page_sizes; Glib::RefPtr _template_action; Gtk::MenuButton &_templates_menu_button; diff --git a/src/ui/widget/page-properties.h b/src/ui/widget/page-properties.h index ed9a3cbe0f0df05eca3f475f9423b9a118028be1..0b7c0595eafbde7a2c3c08824a808aaa6f45f1a3 100644 --- a/src/ui/widget/page-properties.h +++ b/src/ui/widget/page-properties.h @@ -20,6 +20,7 @@ namespace Inkscape { +namespace Colors { class Color; } namespace Util { class Unit; } namespace UI::Widget { @@ -29,7 +30,7 @@ public: static PageProperties* create(); enum class Color { Background, Desk, Border }; - virtual void set_color(Color element, unsigned int rgba) = 0; + virtual void set_color(Color element, Colors::Color const &) = 0; enum class Check { Checkerboard, Border, Shadow, BorderOnTop, AntiAlias, NonuniformScale, DisabledScale, UnsupportedSize, ClipToPage, PageLabelStyle }; @@ -48,7 +49,7 @@ public: auto signal_resize_to_fit () { return _signal_resize_to_fit ; } protected: - sigc::signal _signal_color_changed; + sigc::signal _signal_color_changed; sigc::signal _signal_check_toggled; sigc::signal _signal_dimension_changed; sigc::signal _signal_unit_changed; diff --git a/src/ui/widget/paint-selector.cpp b/src/ui/widget/paint-selector.cpp index 8f9577d151aefee7665649657ee53b4f55569a83..ef1c6a89c8b379cbee01bf083ddf4811465de052 100644 --- a/src/ui/widget/paint-selector.cpp +++ b/src/ui/widget/paint-selector.cpp @@ -61,12 +61,6 @@ #include "widgets/widget-sizes.h" #include "xml/repr.h" -#ifdef SP_PS_VERBOSE -#include "svg/svg-icc-color.h" -#endif // SP_PS_VERBOSE - -using Inkscape::UI::SelectedColor; - #ifdef SP_PS_VERBOSE static gchar const *modeStrings[] = { "MODE_EMPTY", @@ -139,7 +133,8 @@ GradientSelectorInterface *PaintSelector::getGradientFromData() const #define XPAD 4 #define YPAD 1 -PaintSelector::PaintSelector(FillOrStroke kind) +PaintSelector::PaintSelector(FillOrStroke kind, std::shared_ptr colors) + : _selected_colors(std::move(colors)) { set_orientation(Gtk::Orientation::VERTICAL); @@ -206,14 +201,9 @@ PaintSelector::PaintSelector(FillOrStroke kind) _frame->set_visible(true); UI::pack_start(*this, *_frame, true, true); - /* Last used color */ - _selected_color = std::make_unique(); - _updating_color = false; - - _selected_color->signal_grabbed.connect(sigc::mem_fun(*this, &PaintSelector::onSelectedColorGrabbed)); - _selected_color->signal_dragged.connect(sigc::mem_fun(*this, &PaintSelector::onSelectedColorDragged)); - _selected_color->signal_released.connect(sigc::mem_fun(*this, &PaintSelector::onSelectedColorReleased)); - _selected_color->signal_changed.connect(sigc::mem_fun(*this, &PaintSelector::onSelectedColorChanged)); + _selected_colors->signal_grabbed.connect(sigc::mem_fun(*this, &PaintSelector::onSelectedColorGrabbed)); + _selected_colors->signal_released.connect(sigc::mem_fun(*this, &PaintSelector::onSelectedColorReleased)); + _selected_colors->signal_changed.connect(sigc::mem_fun(*this, &PaintSelector::onSelectedColorChanged)); // from _new function setMode(PaintSelector::MODE_MULTIPLE); @@ -275,7 +265,7 @@ void PaintSelector::set_mode_ex(Mode mode, bool switch_style) { set_mode_none(); break; case MODE_SOLID_COLOR: - set_mode_color(mode); + set_mode_color(); break; case MODE_GRADIENT_LINEAR: case MODE_GRADIENT_RADIAL: @@ -317,20 +307,6 @@ void PaintSelector::setFillrule(FillRule fillrule) } } -void PaintSelector::setColorAlpha(SPColor const &color, float alpha) -{ - g_return_if_fail((0.0 <= alpha) && (alpha <= 1.0)); - { -#ifdef SP_PS_VERBOSE - g_print("PaintSelector set RGBA\n"); -#endif - setMode(MODE_SOLID_COLOR); - } - _updating_color = true; - _selected_color->setColorAlpha(color, alpha); - _updating_color = false; -} - void PaintSelector::setSwatch(SPGradient *vector) { #ifdef SP_PS_VERBOSE @@ -407,16 +383,6 @@ void PaintSelector::getGradientProperties(SPGradientUnits &units, SPGradientSpre } -/** - * \post (alpha == NULL) || (*alpha in [0.0, 1.0]). - */ -void PaintSelector::getColorAlpha(SPColor &color, gfloat &alpha) const -{ - _selected_color->colorAlpha(color, alpha); - - g_assert((0.0 <= alpha) && (alpha <= 1.0)); -} - SPGradient *PaintSelector::getGradientVector() { SPGradient *vect = nullptr; @@ -494,32 +460,25 @@ void PaintSelector::set_mode_none() /* Color paint */ void PaintSelector::onSelectedColorGrabbed() { _signal_grabbed.emit(); } - -void PaintSelector::onSelectedColorDragged() -{ - if (_updating_color) { - return; - } - - _signal_dragged.emit(); -} - void PaintSelector::onSelectedColorReleased() { _signal_released.emit(); } void PaintSelector::onSelectedColorChanged() { - if (_updating_color) { + if (_updating_color) return; - } if (_mode == MODE_SOLID_COLOR) { - _signal_changed.emit(); + if (_selected_colors->isGrabbed()) { + _signal_dragged.emit(); + } else { + _signal_changed.emit(); + } } else { g_warning("PaintSelector::onSelectedColorChanged(): selected color changed while not in color selection mode"); } } -void PaintSelector::set_mode_color(PaintSelector::Mode /*mode*/) +void PaintSelector::set_mode_color() { using Inkscape::UI::Widget::ColorNotebook; @@ -530,9 +489,10 @@ void PaintSelector::set_mode_color(PaintSelector::Mode /*mode*/) // Gradient can be null if object paint is changed externally (ie. with a color picker tool) if (gradient) { - SPColor color = gradient->getFirstStop()->getColor(); - float alpha = gradient->getFirstStop()->getOpacity(); - _selected_color->setColorAlpha(color, alpha, false); + _selected_colors->block(); + _selected_colors->clear(); + _selected_colors->set(gradient->getFirstStop()->getId(), gradient->getFirstStop()->getColor()); + _selected_colors->unblock(); } } } @@ -552,7 +512,7 @@ void PaintSelector::set_mode_color(PaintSelector::Mode /*mode*/) _selector_solid_color = Gtk::make_managed(Gtk::Orientation::VERTICAL, 4); /* Color selector */ - auto const color_selector = Gtk::make_managed(*_selected_color); + auto const color_selector = Gtk::make_managed(_selected_colors); color_selector->set_visible(true); UI::pack_start(*_selector_solid_color, *color_selector, true, true); /* Pack everything to frame */ @@ -989,7 +949,7 @@ void PaintSelector::set_mode_pattern(PaintSelector::Mode mode) if (!_selector_pattern) { _selector_pattern = Gtk::make_managed("/pattern-edit", PatternManager::get()); _selector_pattern->signal_changed().connect([=](){ _signal_changed.emit(); }); - _selector_pattern->signal_color_changed().connect([=](unsigned){ _signal_changed.emit(); }); + _selector_pattern->signal_color_changed().connect([=](Colors::Color const &){ _signal_changed.emit(); }); _selector_pattern->signal_edit().connect([=](){ _signal_edit_pattern.emit(); }); _frame->append(*_selector_pattern); } @@ -1031,8 +991,8 @@ gboolean PaintSelector::isSeparator(GtkTreeModel *model, GtkTreeIter *iter, gpoi return sep; } -std::optional PaintSelector::get_pattern_color() { - if (!_selector_pattern) return 0; +std::optional PaintSelector::get_pattern_color() { + if (!_selector_pattern) return Colors::Color(0x000000ff); return _selector_pattern->get_selected_color(); } @@ -1133,33 +1093,6 @@ void PaintSelector::set_mode_swatch(PaintSelector::Mode mode) #endif } -// TODO this seems very bad to be taking in a desktop pointer to muck with. Logic probably belongs elsewhere -void PaintSelector::setFlatColor(SPDesktop *desktop, gchar const *color_property, gchar const *opacity_property) -{ - SPCSSAttr *css = sp_repr_css_attr_new(); - - SPColor color; - gfloat alpha = 0; - getColorAlpha(color, alpha); - - std::string colorStr = color.toString(); - -#ifdef SP_PS_VERBOSE - guint32 rgba = color.toRGBA32(alpha); - g_message("sp_paint_selector_set_flat_color() to '%s' from 0x%08x::%s", colorStr.c_str(), rgba, - (color.icc ? color.icc->colorProfile.c_str() : "")); -#endif // SP_PS_VERBOSE - - sp_repr_css_set_property(css, color_property, colorStr.c_str()); - Inkscape::CSSOStringStream osalpha; - osalpha << alpha; - sp_repr_css_set_property(css, opacity_property, osalpha.str().c_str()); - - sp_desktop_set_style(desktop, css); - - sp_repr_css_attr_unref(css); -} - PaintSelector::Mode PaintSelector::getModeForStyle(SPStyle const &style, FillOrStroke kind) { Mode mode = MODE_UNSET; diff --git a/src/ui/widget/paint-selector.h b/src/ui/widget/paint-selector.h index 285002f678800541999eaa911a790caccb93303e..f76eda68f6cfa78a5620674edaf59896fcf29e63 100644 --- a/src/ui/widget/paint-selector.h +++ b/src/ui/widget/paint-selector.h @@ -21,12 +21,11 @@ #include <2geom/forward.h> #include -#include "color.h" +#include "colors/color-set.h" #include "fill-or-stroke.h" #include "gradient-selector-interface.h" #include "object/sp-gradient-spread.h" #include "object/sp-gradient-units.h" -#include "ui/selected-color.h" #include "ui/widget/gradient-selector.h" #include "ui/widget/swatch-selector.h" @@ -112,10 +111,8 @@ class PaintSelector : public Gtk::Box { bool _meshmenu_update = false; #endif - std::unique_ptr _selected_color; - bool _updating_color; - - void getColorAlpha(SPColor &color, gfloat &alpha) const; + std::shared_ptr _selected_colors; + bool _updating_color = false; static gboolean isSeparator(GtkTreeModel *model, GtkTreeIter *iter, gpointer data); @@ -133,7 +130,6 @@ class PaintSelector : public Gtk::Box { void style_button_toggled(StyleToggleButton *tb); void fillrule_toggled(FillRuleRadioButton *tb); void onSelectedColorGrabbed(); - void onSelectedColorDragged(); void onSelectedColorReleased(); void onSelectedColorChanged(); void set_mode_empty(); @@ -143,7 +139,7 @@ class PaintSelector : public Gtk::Box { GradientSelectorInterface *getGradientFromData() const; void clear_frame(); void set_mode_unset(); - void set_mode_color(PaintSelector::Mode mode); + void set_mode_color(); void set_mode_gradient(PaintSelector::Mode mode); #ifdef WITH_MESH void set_mode_mesh(PaintSelector::Mode mode); @@ -165,7 +161,7 @@ class PaintSelector : public Gtk::Box { static void pattern_destroy(GtkWidget *widget, PaintSelector *psel); public: - PaintSelector(FillOrStroke kind); + PaintSelector(FillOrStroke kind, std::shared_ptr colors); inline decltype(_signal_fillrule_changed) signal_fillrule_changed() const { return _signal_fillrule_changed; } inline decltype(_signal_dragged) signal_dragged() const { return _signal_dragged; } @@ -179,7 +175,6 @@ class PaintSelector : public Gtk::Box { void setMode(Mode mode); static Mode getModeForStyle(SPStyle const &style, FillOrStroke kind); void setFillrule(FillRule fillrule); - void setColorAlpha(SPColor const &color, float alpha); void setSwatch(SPGradient *vector); void setGradientLinear(SPGradient *vector, SPLinearGradient* gradient, SPStop* selected); void setGradientRadial(SPGradient *vector, SPRadialGradient* gradient, SPStop* selected); @@ -197,13 +192,10 @@ class PaintSelector : public Gtk::Box { void updatePatternList(SPPattern *pat); inline decltype(_mode) get_mode() const { return _mode; } - // TODO move this elsewhere: - void setFlatColor(SPDesktop *desktop, const gchar *color_property, const gchar *opacity_property); - SPGradient *getGradientVector(); void pushAttrsToGradient(SPGradient *gr) const; SPPattern *getPattern(); - std::optional get_pattern_color(); + std::optional get_pattern_color(); Geom::Affine get_pattern_transform(); Geom::Point get_pattern_offset(); Geom::Scale get_pattern_gap(); diff --git a/src/ui/widget/pattern-editor.cpp b/src/ui/widget/pattern-editor.cpp index 385fbc40699fe216c9f30a6b7cef780caf393b5d..c312cca9025a69c8920983845899fb6c202ef2c6 100644 --- a/src/ui/widget/pattern-editor.cpp +++ b/src/ui/widget/pattern-editor.cpp @@ -91,7 +91,6 @@ PatternEditor::PatternEditor(const char* prefs, Inkscape::PatternManager& manage _edit_btn(get_widget(_builder, "edit-pattern")), _preview_img(get_widget(_builder, "preview")), _preview(get_widget(_builder, "preview-box")), - _color_btn(get_widget(_builder, "color-btn")), _color_label(get_widget(_builder, "color-label")), _paned(get_widget(_builder, "paned")), _main_grid(get_widget(_builder, "main-box")), @@ -104,13 +103,10 @@ PatternEditor::PatternEditor(const char* prefs, Inkscape::PatternManager& manage _search_box(get_widget(_builder, "search")), _tile_slider(get_widget(_builder, "tile-slider")), _show_names(get_widget(_builder, "show-names")), + _color_picker(get_derived_widget(_builder, "color-btn", _("Pattern color"), false)), _prefs(prefs) { - _color_picker = std::make_unique( - _("Pattern color"), "", 0x7f7f7f00, true, - &get_widget(_builder, "color-btn")); - _color_picker->use_transparency(false); - _color_picker->connectChanged([=](guint color){ + _color_picker.connectChanged([=](Colors::Color const &color){ if (_update.pending()) return; _signal_color_changed.emit(color); }); @@ -396,15 +392,15 @@ void PatternEditor::update_widgets_from_pattern(Glib::RefPtr& patte _gap_y_spin.set_value(item.gap[Geom::Y]); if (item.color.has_value()) { - _color_picker->setRgba32(item.color->toRGBA32(1.0)); - _color_btn.set_sensitive(); + _color_picker.setColor(*item.color); + _color_picker.set_sensitive(); _color_label.set_opacity(1.0); // hack: sensitivity doesn't change appearance, so using opacity directly } else { - _color_picker->setRgba32(0); - _color_btn.set_sensitive(false); + _color_picker.setColor(Colors::Color(0x0)); + _color_picker.set_sensitive(false); _color_label.set_opacity(0.6); - _color_picker->closeWindow(); + _color_picker.closeWindow(); } } @@ -656,12 +652,12 @@ std::pair PatternEditor::get_selected() { } } -std::optional PatternEditor::get_selected_color() { +std::optional PatternEditor::get_selected_color() { auto pat = get_active(); if (pat.first && pat.first->color.has_value()) { - return _color_picker->get_current_color(); + return _color_picker.get_current_color(); } - return std::optional(); // color not supported + return {}; // color not supported } Geom::Point PatternEditor::get_selected_offset() { diff --git a/src/ui/widget/pattern-editor.h b/src/ui/widget/pattern-editor.h index 45581d5d7188602b8b3b44956acd45c13de3836b..08a06a2a44c251edc791b3c9a8c5886808755d0b 100644 --- a/src/ui/widget/pattern-editor.h +++ b/src/ui/widget/pattern-editor.h @@ -18,7 +18,6 @@ #include <2geom/point.h> #include <2geom/transforms.h> -#include "color.h" #include "object/sp-pattern.h" #include "pattern-manager.h" #include "spin-scale.h" @@ -47,6 +46,10 @@ class Viewport; class SPDocument; class ColorPicker; +namespace Inkscape::Colors { +class Color; +} + namespace Inkscape::UI::Widget { class PatternEditor : public Gtk::Box { @@ -60,7 +63,7 @@ public: // selected pattern ID if any plus stock pattern collection document (or null) std::pair get_selected(); // and its color - std::optional get_selected_color(); + std::optional get_selected_color(); // return combined scale and rotation Geom::Affine get_selected_transform(); // return pattern offset @@ -74,7 +77,7 @@ public: private: sigc::signal _signal_changed; - sigc::signal _signal_color_changed; + sigc::signal _signal_color_changed; sigc::signal _signal_edit; public: @@ -114,7 +117,6 @@ private: bool _precise_gap_control = false; Gtk::Button& _edit_btn; Gtk::Label& _color_label; - Gtk::Button& _color_btn; Gtk::Button& _link_scale; Gtk::Picture& _preview_img; Gtk::Viewport& _preview; @@ -130,7 +132,7 @@ private: Glib::ustring _prefs; PatternStore _doc_pattern_store; PatternStore _stock_pattern_store; - std::unique_ptr _color_picker; + ColorPicker& _color_picker; OperationBlocker _update; std::unordered_map> _cached_items; // cached current document patterns Inkscape::PatternManager& _manager; diff --git a/src/ui/widget/pattern-store.h b/src/ui/widget/pattern-store.h index 501f893b8187ac458a382e27b498c14d6aa69674..62ac04457c4d35f0f12eb9d1cff15394c613a267 100644 --- a/src/ui/widget/pattern-store.h +++ b/src/ui/widget/pattern-store.h @@ -10,9 +10,9 @@ #include #include <2geom/transforms.h> +#include "colors/color.h" #include #include -#include "color.h" #include "ui/filtered-store.h" class SPDocument; @@ -32,7 +32,7 @@ public: bool uniform_scale = false; Geom::Affine transform; Geom::Point offset; - std::optional color; + std::optional color; Geom::Scale gap; SPDocument* collection = nullptr; diff --git a/src/ui/widget/preferences-widget.cpp b/src/ui/widget/preferences-widget.cpp index 7d0ecf0d12b26bdba7b0b66a04b7f2e41a14080f..dbe390ee527b64230f7bbe63d9c3170a1abbe6b9 100644 --- a/src/ui/widget/preferences-widget.cpp +++ b/src/ui/widget/preferences-widget.cpp @@ -918,20 +918,19 @@ void PrefMultiEntry::on_changed() } void PrefColorPicker::init(Glib::ustring const &label, Glib::ustring const &prefs_path, - std::uint32_t const default_rgba) + std::string const &default_color) { _prefs_path = prefs_path; _title = label; Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - this->setRgba32( prefs->getInt(_prefs_path, (int)default_rgba) ); + this->setColor(prefs->getColor(_prefs_path, default_color)); } -void PrefColorPicker::on_changed(std::uint32_t const rgba) +void PrefColorPicker::on_changed(Inkscape::Colors::Color const &color) { - if (this->get_visible()) //only take action if the user toggled it - { + if (this->get_visible()) { //only take action if the user toggled it Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setInt(_prefs_path, static_cast(rgba)); + prefs->setColor(_prefs_path, color); } } diff --git a/src/ui/widget/preferences-widget.h b/src/ui/widget/preferences-widget.h index 6b565e6388c5bfd6cf1ba55dc4c870cbdbd9508b..99df6d1ab4cb8633ac620e9e316f1a405ffee2b7 100644 --- a/src/ui/widget/preferences-widget.h +++ b/src/ui/widget/preferences-widget.h @@ -284,15 +284,15 @@ private: class PrefColorPicker : public ColorPicker { public: - PrefColorPicker() : ColorPicker("", "", 0, false) {}; + PrefColorPicker() : ColorPicker("", "", Colors::Color(0x000000ff), false) {}; ~PrefColorPicker() override = default;; void init(Glib::ustring const &abel, Glib::ustring const &prefs_path, - std::uint32_t default_rgba); + std::string const &default_color); private: Glib::ustring _prefs_path; - void on_changed(std::uint32_t rgba) override; + void on_changed(Inkscape::Colors::Color const &color) override; }; class PrefUnit : public UnitMenu diff --git a/src/ui/widget/registered-widget.cpp b/src/ui/widget/registered-widget.cpp index 1e41e67ccd8be758e5b19d9bb659b52a5e43f613..5a1f84f5e37276e74ca6271cc05c9187c8378ecf 100644 --- a/src/ui/widget/registered-widget.cpp +++ b/src/ui/widget/registered-widget.cpp @@ -21,7 +21,6 @@ #include #include "object/sp-root.h" -#include "svg/svg-color.h" #include "svg/stringstream.h" #include "util/safe-printf.h" @@ -403,7 +402,7 @@ RegisteredColorPicker::RegisteredColorPicker(Glib::ustring const &label, Registry &wr, Inkscape::XML::Node *repr_in, SPDocument *doc_in) - : RegisteredWidget{label, title, tip, 0u, true} + : RegisteredWidget{label, title, tip, Colors::Color(0x000000ff), true} { init_parent("", wr, repr_in, doc_in); @@ -413,9 +412,9 @@ RegisteredColorPicker::RegisteredColorPicker(Glib::ustring const &label, } void -RegisteredColorPicker::setRgba32(std::uint32_t const rgba) +RegisteredColorPicker::setColor(Colors::Color const &color) { - LabelledColorPicker::setRgba32(rgba); + LabelledColorPicker::setColor(color); } void @@ -425,7 +424,7 @@ RegisteredColorPicker::closeWindow() } void -RegisteredColorPicker::on_changed(std::uint32_t const rgba) +RegisteredColorPicker::on_changed(Colors::Color const &color) { if (_wr->isUpdating()) return; @@ -445,21 +444,13 @@ RegisteredColorPicker::on_changed(std::uint32_t const rgba) local_repr = dt->getNamedView()->getRepr(); local_doc = dt->getDocument(); } - gchar c[32]; - if (_akey == _ckey + "_opacity_LPE") { //For LPE parameter we want stored with alpha - safeprintf(c, "#%08x", rgba); - } else { - sp_svg_write_color(c, sizeof(c), rgba); - } + bool alpha = _akey == _ckey + "_opacity_LPE"; //For LPE parameter we want stored with alpha + auto c = color.toString(alpha); + { DocumentUndo::ScopedInsensitive _no_undo(local_doc); - if (_setter) { - _setter(local_repr, rgba); - } - else { - local_repr->setAttribute(_ckey, c); - local_repr->setAttributeCssDouble(_akey.c_str(), (rgba & 0xff) / 255.0); - } + local_repr->setAttribute(_ckey, c); + local_repr->setAttributeCssDouble(_akey.c_str(), color.getOpacity()); } local_doc->setModifiedSinceSave(); DocumentUndo::done(local_doc, "registered-widget.cpp: RegisteredColorPicker::on_changed", ""); // TODO Fix description. diff --git a/src/ui/widget/registered-widget.h b/src/ui/widget/registered-widget.h index 40ade53d21cfc884fe9472e581203bf302a6a830..2de4958cf13f53a146dd3dbb8579205aee904441 100644 --- a/src/ui/widget/registered-widget.h +++ b/src/ui/widget/registered-widget.h @@ -287,15 +287,15 @@ public: Inkscape::XML::Node *repr_in = nullptr, SPDocument *doc_in = nullptr); - void setRgba32(std::uint32_t); + void setColor(Colors::Color const &); void closeWindow(); - void setCustomSetter(std::function setter) { _setter = std::move(setter); } + void setCustomSetter(std::function setter) { _setter = std::move(setter); } private: Glib::ustring _ckey, _akey; auto_connection _changed_connection; - std::function _setter; - void on_changed(std::uint32_t); + std::function _setter; + void on_changed(Colors::Color const &); }; class RegisteredInteger : public RegisteredWidget { diff --git a/src/ui/widget/selected-style.cpp b/src/ui/widget/selected-style.cpp index e0781d1005a76db78fbdb58efc4f6ba053ce1a44..49b881bfaa40fabb4609edfa73338850e8b690a8 100644 --- a/src/ui/widget/selected-style.cpp +++ b/src/ui/widget/selected-style.cpp @@ -24,6 +24,8 @@ #include "selected-style.h" +#include "colors/dragndrop.h" +#include "colors/manager.h" #include "desktop-style.h" #include "document-undo.h" #include "gradient-chemistry.h" @@ -38,7 +40,6 @@ #include "object/sp-pattern.h" #include "object/sp-radial-gradient.h" #include "svg/css-ostringstream.h" -#include "svg/svg-color.h" #include "ui/controller.h" #include "ui/clipboard.h" #include "ui/cursor-utils.h" @@ -54,7 +55,6 @@ #include "util/safe-printf.h" #include "util/units.cpp" #include "util-string/ustring-format.h" -#include "widgets/paintdef.h" static constexpr int SELECTED_STYLE_SB_WIDTH = 48; static constexpr int SELECTED_STYLE_PLACE_WIDTH = 50; @@ -171,28 +171,18 @@ SelectedStyle::SelectedStyle() drop[i] = std::make_unique(); drop[i]->parent = this; drop[i]->item = i; - auto target = Gtk::DropTarget::create(Glib::Value::value_type(), Gdk::DragAction::COPY | Gdk::DragAction::MOVE); + auto target = Gtk::DropTarget::create(Glib::Value>::value_type(), Gdk::DragAction::COPY | Gdk::DragAction::MOVE); target->signal_drop().connect([this, i] (Glib::ValueBase const &value, double, double) { if (!dropEnabled[i]) { return false; } auto const &tracker = *drop[i]; - auto const &paintdef = *reinterpret_cast(g_value_get_boxed(value.gobj())); - - // copied from drag-and-drop.cpp, case PaintDef - std::string colorspec; - if (paintdef.get_type() == PaintDef::NONE) { - colorspec = "none"; - } else { - auto const [r, g, b] = paintdef.get_rgb(); - colorspec.resize(63); - sp_svg_write_color(colorspec.data(), colorspec.size() + 1, SP_RGBA32_U_COMPOSE(r, g, b, 0xff)); - colorspec.resize(std::strlen(colorspec.c_str())); - } + auto const &color = *reinterpret_cast*>(g_value_get_boxed(value.gobj())); + std::string colorspec = color ? color->toString(false) : "none"; auto const css = sp_repr_css_attr_new(); - sp_repr_css_set_property(css, tracker.item == SS_FILL ? "fill" : "stroke", colorspec.c_str()); + sp_repr_css_set_property_string(css, tracker.item == SS_FILL ? "fill" : "stroke", colorspec); sp_desktop_set_style(tracker.parent->_desktop, css); sp_repr_css_attr_unref(css); @@ -325,10 +315,8 @@ void SelectedStyle::on_stroke_opaque() { void SelectedStyle::on_fill_lastused() { SPCSSAttr *css = sp_repr_css_attr_new (); - guint32 color = sp_desktop_get_color(_desktop, true); - gchar c[64]; - sp_svg_write_color (c, sizeof(c), color); - sp_repr_css_set_property (css, "fill", c); + auto color = sp_desktop_get_color(_desktop, true); + sp_repr_css_set_property_string(css, "fill", color ? color->toString() : "none"); sp_desktop_set_style (_desktop, css); sp_repr_css_attr_unref (css); DocumentUndo::done(_desktop->getDocument(), _("Apply last set color to fill"), INKSCAPE_ICON("dialog-fill-and-stroke")); @@ -336,10 +324,8 @@ void SelectedStyle::on_fill_lastused() { void SelectedStyle::on_stroke_lastused() { SPCSSAttr *css = sp_repr_css_attr_new (); - guint32 color = sp_desktop_get_color(_desktop, false); - gchar c[64]; - sp_svg_write_color (c, sizeof(c), color); - sp_repr_css_set_property (css, "stroke", c); + auto color = sp_desktop_get_color(_desktop, false); + sp_repr_css_set_property_string(css, "fill", color ? color->toString() : "none"); sp_desktop_set_style (_desktop, css); sp_repr_css_attr_unref (css); DocumentUndo::done(_desktop->getDocument(), _("Apply last set color to stroke"), INKSCAPE_ICON("dialog-fill-and-stroke")); @@ -347,9 +333,7 @@ void SelectedStyle::on_stroke_lastused() { void SelectedStyle::on_fill_lastselected() { SPCSSAttr *css = sp_repr_css_attr_new (); - gchar c[64]; - sp_svg_write_color (c, sizeof(c), _lastselected[SS_FILL]); - sp_repr_css_set_property (css, "fill", c); + sp_repr_css_set_property_string(css, "fill", _lastselected[SS_FILL] ? _lastselected[SS_FILL]->toString() : ""); sp_desktop_set_style (_desktop, css); sp_repr_css_attr_unref (css); DocumentUndo::done(_desktop->getDocument(), _("Apply last selected color to fill"), INKSCAPE_ICON("dialog-fill-and-stroke")); @@ -357,9 +341,7 @@ void SelectedStyle::on_fill_lastselected() { void SelectedStyle::on_stroke_lastselected() { SPCSSAttr *css = sp_repr_css_attr_new (); - gchar c[64]; - sp_svg_write_color (c, sizeof(c), _lastselected[SS_STROKE]); - sp_repr_css_set_property (css, "stroke", c); + sp_repr_css_set_property_string(css, "stroke", _lastselected[SS_STROKE] ? _lastselected[SS_STROKE]->toString() : ""); sp_desktop_set_style (_desktop, css); sp_repr_css_attr_unref (css); DocumentUndo::done(_desktop->getDocument(), _("Apply last selected color to stroke"), INKSCAPE_ICON("dialog-fill-and-stroke")); @@ -367,8 +349,7 @@ void SelectedStyle::on_stroke_lastselected() { void SelectedStyle::on_fill_invert() { SPCSSAttr *css = sp_repr_css_attr_new (); - guint32 color = _thisselected[SS_FILL]; - gchar c[64]; + auto color = _thisselected[SS_FILL]; if (_mode[SS_FILL] == SS_LGRADIENT || _mode[SS_FILL] == SS_RGRADIENT) { sp_gradient_invert_selected_gradients(_desktop, Inkscape::FOR_FILL); return; @@ -376,15 +357,8 @@ void SelectedStyle::on_fill_invert() { } if (_mode[SS_FILL] != SS_COLOR) return; - sp_svg_write_color (c, sizeof(c), - SP_RGBA32_U_COMPOSE( - (255 - SP_RGBA32_R_U(color)), - (255 - SP_RGBA32_G_U(color)), - (255 - SP_RGBA32_B_U(color)), - SP_RGBA32_A_U(color) - ) - ); - sp_repr_css_set_property (css, "fill", c); + color->invert(); + sp_repr_css_set_property_string(css, "fill", color->toString()); sp_desktop_set_style (_desktop, css); sp_repr_css_attr_unref (css); DocumentUndo::done(_desktop->getDocument(), _("Invert fill"), INKSCAPE_ICON("dialog-fill-and-stroke")); @@ -392,22 +366,14 @@ void SelectedStyle::on_fill_invert() { void SelectedStyle::on_stroke_invert() { SPCSSAttr *css = sp_repr_css_attr_new (); - guint32 color = _thisselected[SS_STROKE]; - gchar c[64]; + auto color = _thisselected[SS_STROKE]; if (_mode[SS_STROKE] == SS_LGRADIENT || _mode[SS_STROKE] == SS_RGRADIENT) { sp_gradient_invert_selected_gradients(_desktop, Inkscape::FOR_STROKE); return; } if (_mode[SS_STROKE] != SS_COLOR) return; - sp_svg_write_color (c, sizeof(c), - SP_RGBA32_U_COMPOSE( - (255 - SP_RGBA32_R_U(color)), - (255 - SP_RGBA32_G_U(color)), - (255 - SP_RGBA32_B_U(color)), - SP_RGBA32_A_U(color) - ) - ); - sp_repr_css_set_property (css, "stroke", c); + color->invert(); + sp_repr_css_set_property_string(css, "stroke", color->toString()); sp_desktop_set_style (_desktop, css); sp_repr_css_attr_unref (css); DocumentUndo::done(_desktop->getDocument(), _("Invert stroke"), INKSCAPE_ICON("dialog-fill-and-stroke")); @@ -415,9 +381,7 @@ void SelectedStyle::on_stroke_invert() { void SelectedStyle::on_fill_white() { SPCSSAttr *css = sp_repr_css_attr_new (); - gchar c[64]; - sp_svg_write_color (c, sizeof(c), 0xffffffff); - sp_repr_css_set_property (css, "fill", c); + sp_repr_css_set_property (css, "fill", "#ffffff"); sp_repr_css_set_property (css, "fill-opacity", "1"); sp_desktop_set_style (_desktop, css); sp_repr_css_attr_unref (css); @@ -426,9 +390,7 @@ void SelectedStyle::on_fill_white() { void SelectedStyle::on_stroke_white() { SPCSSAttr *css = sp_repr_css_attr_new (); - gchar c[64]; - sp_svg_write_color (c, sizeof(c), 0xffffffff); - sp_repr_css_set_property (css, "stroke", c); + sp_repr_css_set_property (css, "stroke", "#ffffff"); sp_repr_css_set_property (css, "stroke-opacity", "1"); sp_desktop_set_style (_desktop, css); sp_repr_css_attr_unref (css); @@ -437,9 +399,7 @@ void SelectedStyle::on_stroke_white() { void SelectedStyle::on_fill_black() { SPCSSAttr *css = sp_repr_css_attr_new (); - gchar c[64]; - sp_svg_write_color (c, sizeof(c), 0x000000ff); - sp_repr_css_set_property (css, "fill", c); + sp_repr_css_set_property (css, "fill", "#000000"); sp_repr_css_set_property (css, "fill-opacity", "1.0"); sp_desktop_set_style (_desktop, css); sp_repr_css_attr_unref (css); @@ -448,9 +408,7 @@ void SelectedStyle::on_fill_black() { void SelectedStyle::on_stroke_black() { SPCSSAttr *css = sp_repr_css_attr_new (); - gchar c[64]; - sp_svg_write_color (c, sizeof(c), 0x000000ff); - sp_repr_css_set_property (css, "stroke", c); + sp_repr_css_set_property (css, "stroke", "#000000"); sp_repr_css_set_property (css, "stroke-opacity", "1.0"); sp_desktop_set_style (_desktop, css); sp_repr_css_attr_unref (css); @@ -459,10 +417,7 @@ void SelectedStyle::on_stroke_black() { void SelectedStyle::on_fill_copy() { if (_mode[SS_FILL] == SS_COLOR) { - gchar c[64]; - sp_svg_write_color (c, sizeof(c), _thisselected[SS_FILL]); - Glib::ustring text; - text += c; + auto text = _thisselected[SS_FILL]->toString(); if (!text.empty()) { auto const display = Gdk::Display::get_default(); display->get_primary_clipboard()->set_text(text); @@ -472,10 +427,7 @@ void SelectedStyle::on_fill_copy() { void SelectedStyle::on_stroke_copy() { if (_mode[SS_STROKE] == SS_COLOR) { - gchar c[64]; - sp_svg_write_color (c, sizeof(c), _thisselected[SS_STROKE]); - Glib::ustring text; - text += c; + auto text = _thisselected[SS_STROKE]->toString(); if (!text.empty()) { auto const display = Gdk::Display::get_default(); display->get_primary_clipboard()->set_text(text); @@ -495,13 +447,9 @@ void SelectedStyle::_on_paste_callback(Glib::RefPtr& result, G std::cout << "Pasting text failed: " << err.what() << std::endl; return; } - if (!text.empty()) { - guint32 color = sp_svg_read_color(text.c_str(), 0x000000ff); // impossible value, as SVG color cannot have opacity - if (color == 0x000000ff) // failed to parse color string - return; - + if (auto color = Inkscape::Colors::Color::parse(text)) { SPCSSAttr *css = sp_repr_css_attr_new (); - sp_repr_css_set_property (css, typepaste.c_str(), text.c_str()); + sp_repr_css_set_property_string(css, "fill", color->toString()); sp_desktop_set_style (_desktop, css); sp_repr_css_attr_unref (css); DocumentUndo::done(_desktop->getDocument(), typepaste == "fill" ? _("Paste fill") : _("Paste stroke"), INKSCAPE_ICON("dialog-fill-and-stroke")); @@ -785,6 +733,7 @@ SelectedStyle::update() dropEnabled[i] = true; auto paint = i == SS_FILL ? query.fill.upcast() : query.stroke.upcast(); + double opacity = i == SS_FILL ? query.fill_opacity : query.stroke_opacity; if (paint->set && paint->isPaintserver()) { SPPaintServer *server = (i == SS_FILL)? SP_STYLE_FILL_SERVER (&query) : SP_STYLE_STROKE_SERVER (&query); if ( server ) { @@ -835,21 +784,17 @@ SelectedStyle::update() g_warning ("file %s: line %d: Unknown paint server", __FILE__, __LINE__); } } else if (paint->set && paint->isColor()) { - guint32 color = paint->value.color.toRGBA32( - SP_SCALE24_TO_FLOAT ((i == SS_FILL) ? - query.fill_opacity.value : - query.stroke_opacity.value)); + auto color = paint->getColor(); + color.addOpacity(opacity); + _lastselected[i] = _thisselected[i]; _thisselected[i] = color; // include opacity - gchar c_string[64]; - safeprintf (c_string, "%06x/%.3g", color >> 8, SP_RGBA32_A_F(color)); - // No type_label. - swatch[i]->set_tooltip_text(type_strings[SS_COLOR][i][1] + ": " + c_string + + swatch[i]->set_tooltip_text(type_strings[SS_COLOR][i][1] + ": " + color.toString() + _(", drag to adjust, middle-click to remove")); type_label[i]->set_visible(false); - color_preview[i]->setRgba32(color); + color_preview[i]->setRgba32(color.toRGBA()); color_preview[i]->show(); _mode[i] = SS_COLOR; @@ -1021,72 +966,27 @@ RotateableSwatch::RotateableSwatch(SelectedStyle *parent, guint mode) RotateableSwatch::~RotateableSwatch() = default; -double -RotateableSwatch::color_adjust(float *hsla, double by, guint32 cc, guint modifier) +std::pair RotateableSwatch::color_adjust(Colors::Color const &cc, double by, guint modifier) { - SPColor::rgb_to_hsl_floatv (hsla, SP_RGBA32_R_F(cc), SP_RGBA32_G_F(cc), SP_RGBA32_B_F(cc)); - hsla[3] = SP_RGBA32_A_F(cc); - double diff = 0; - if (modifier == 2) { // saturation - double old = hsla[1]; - if (by > 0) { - hsla[1] += by * (1 - hsla[1]); - } else { - hsla[1] += by * (hsla[1]); - } - diff = hsla[1] - old; - } else if (modifier == 1) { // lightness - double old = hsla[2]; - if (by > 0) { - hsla[2] += by * (1 - hsla[2]); - } else { - hsla[2] += by * (hsla[2]); - } - diff = hsla[2] - old; - } else if (modifier == 3) { // alpha - double old = hsla[3]; - hsla[3] += by/2; - if (hsla[3] < 0) { - hsla[3] = 0; - } else if (hsla[3] > 1) { - hsla[3] = 1; - } - diff = hsla[3] - old; - } else { // hue - double old = hsla[0]; - hsla[0] += by/2; - while (hsla[0] < 0) - hsla[0] += 1; - while (hsla[0] > 1) - hsla[0] -= 1; - diff = hsla[0] - old; - } + static int map[4] = {0,2,1,3}; + auto hsl = *cc.converted(Colors::Space::Type::HSL); + int ch = map[modifier]; + double old = hsl[ch]; - float rgb[3]; - SPColor::hsl_to_rgb_floatv (rgb, hsla[0], hsla[1], hsla[2]); - - gchar c[64]; - sp_svg_write_color (c, sizeof(c), - SP_RGBA32_U_COMPOSE( - (SP_COLOR_F_TO_U(rgb[0])), - (SP_COLOR_F_TO_U(rgb[1])), - (SP_COLOR_F_TO_U(rgb[2])), - 0xff - ) - ); + hsl.set(ch, old + by * (by > 0 ? (1 - hsl[ch]) : hsl[ch])); + hsl.normalize(); + double diff = hsl[ch] - old; + hsl.convert(cc.getSpace()); SPCSSAttr *css = sp_repr_css_attr_new (); - if (modifier == 3) { // alpha - Inkscape::CSSOStringStream osalpha; - osalpha << hsla[3]; - sp_repr_css_set_property(css, (fillstroke == SS_FILL) ? "fill-opacity" : "stroke-opacity", osalpha.str().c_str()); + sp_repr_css_set_property_double(css, (fillstroke == SS_FILL) ? "fill-opacity" : "stroke-opacity", hsl.getOpacity()); } else { - sp_repr_css_set_property (css, (fillstroke == SS_FILL) ? "fill" : "stroke", c); + sp_repr_css_set_property_string(css, (fillstroke == SS_FILL) ? "fill" : "stroke", hsl.toString(false)); } sp_desktop_set_style (parent->getDesktop(), css); sp_repr_css_attr_unref (css); - return diff; + return {old, diff}; } void RotateableSwatch::do_motion(double by, guint modifier) @@ -1110,50 +1010,39 @@ void RotateableSwatch::do_motion(double by, guint modifier) cursor_state = modifier; } - guint32 cc; - if (!startcolor_set) { - cc = startcolor = parent->_thisselected[fillstroke]; - startcolor_set = true; - } else { - cc = startcolor; + if (!startcolor) { + startcolor = parent->_thisselected[fillstroke]; } - float hsla[4]; - double diff = 0; - - diff = color_adjust(hsla, by, cc, modifier); + auto ret = color_adjust(*startcolor, by, modifier); if (modifier == 3) { // alpha DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, (_("Adjust alpha")), INKSCAPE_ICON("dialog-fill-and-stroke")); - double ch = hsla[3]; parent->getDesktop()->getTool()->message_context->setF( Inkscape::IMMEDIATE_MESSAGE, _("Adjusting alpha: was %.3g, now %.3g (diff %.3g); with Ctrl to adjust lightness, with Shift to adjust saturation, without modifiers to adjust hue"), - ch - diff, ch, diff); + ret.first - ret.second, ret.first, ret.second); } else if (modifier == 2) { // saturation DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, (_("Adjust saturation")), INKSCAPE_ICON("dialog-fill-and-stroke")); - double ch = hsla[1]; parent->getDesktop()->getTool()->message_context->setF( Inkscape::IMMEDIATE_MESSAGE, _("Adjusting saturation: was %.3g, now %.3g (diff %.3g); with Ctrl to adjust lightness, with Alt to adjust alpha, without modifiers to adjust hue"), - ch - diff, ch, diff); + ret.first - ret.second, ret.first, ret.second); } else if (modifier == 1) { // lightness DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, (_("Adjust lightness")), INKSCAPE_ICON("dialog-fill-and-stroke")); - double ch = hsla[2]; parent->getDesktop()->getTool()->message_context->setF( Inkscape::IMMEDIATE_MESSAGE, _("Adjusting lightness: was %.3g, now %.3g (diff %.3g); with Shift to adjust saturation, with Alt to adjust alpha, without modifiers to adjust hue"), - ch - diff, ch, diff); + ret.first - ret.second, ret.first, ret.second); } else { // hue DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, (_("Adjust hue")), INKSCAPE_ICON("dialog-fill-and-stroke")); - double ch = hsla[0]; parent->getDesktop()->getTool()->message_context->setF( Inkscape::IMMEDIATE_MESSAGE, _("Adjusting hue: was %.3g, now %.3g (diff %.3g); with Shift to adjust saturation, with Alt to adjust alpha, with Ctrl to adjust lightness"), - ch - diff, ch, diff); + ret.first - ret.second, ret.first, ret.second); } } @@ -1169,8 +1058,7 @@ void RotateableSwatch::do_release(double by, guint modifier) return; parent->dragging = false; - float hsla[4]; - color_adjust(hsla, by, startcolor, modifier); + color_adjust(*startcolor, by, modifier); if (cursor_state != -1) { if (auto window = dynamic_cast(get_root())) { @@ -1199,7 +1087,7 @@ void RotateableSwatch::do_release(double by, guint modifier) } parent->getDesktop()->getTool()->message_context->clear(); - startcolor_set = false; + startcolor.reset(); } /* ============================================= RotateableStrokeWidth */ diff --git a/src/ui/widget/selected-style.h b/src/ui/widget/selected-style.h index 2b08cf519727255807b22a46ec66d3d97df42dda..15bcad52838504d062e742cb4851bcca03e2ac5d 100644 --- a/src/ui/widget/selected-style.h +++ b/src/ui/widget/selected-style.h @@ -21,6 +21,7 @@ #include #include +#include "colors/color.h" #include "helper/auto-connection.h" #include "rotateable.h" #include "ui/popup-menu.h" @@ -74,7 +75,7 @@ class RotateableSwatch : public Rotateable { RotateableSwatch(SelectedStyle *parent, guint mode); ~RotateableSwatch() override; - double color_adjust (float *hsl, double by, guint32 cc, guint state); + std::pair color_adjust(Colors::Color const &cc, double by, guint state); void do_motion (double by, guint state) override; void do_release (double by, guint state) override; @@ -85,8 +86,7 @@ private: SelectedStyle *parent; - guint32 startcolor = 0; - bool startcolor_set = false; + std::optional startcolor; gchar const *undokey = "ssrot1"; @@ -125,8 +125,8 @@ public: SPDesktop *getDesktop() {return _desktop;} void update(); - guint32 _lastselected[2]; - guint32 _thisselected[2]; + std::optional _lastselected[2]; + std::optional _thisselected[2]; guint _mode[2]; diff --git a/src/ui/widget/shapeicon.cpp b/src/ui/widget/shapeicon.cpp index 401c695b3fe02bb9034eca03221bb1094ab73a59..d371fa997bcd2ac4951946d825cf309bdd72b5b6 100644 --- a/src/ui/widget/shapeicon.cpp +++ b/src/ui/widget/shapeicon.cpp @@ -18,7 +18,6 @@ #include #include -#include "color.h" #include "ui/icon-loader.h" #include "ui/util.h" diff --git a/src/ui/widget/stroke-style.cpp b/src/ui/widget/stroke-style.cpp index f493d93b9901b14712ed963f8386d55fdd54ef33..c7a7604fc968f40ff5b94c3045a8619395424b71 100644 --- a/src/ui/widget/stroke-style.cpp +++ b/src/ui/widget/stroke-style.cpp @@ -40,7 +40,6 @@ #include "object/sp-stop.h" #include "object/sp-text.h" #include "svg/css-ostringstream.h" -#include "svg/svg-color.h" #include "ui/icon-loader.h" #include "ui/icon-names.h" #include "ui/pack.h" diff --git a/src/ui/widget/style-swatch.cpp b/src/ui/widget/style-swatch.cpp index 49958e0b76a7d1fa29afd093d1a2552d6027aeac..9de9ecef344d44afdc17ef19f81f185f91d747b4 100644 --- a/src/ui/widget/style-swatch.cpp +++ b/src/ui/widget/style-swatch.cpp @@ -265,14 +265,15 @@ void StyleSwatch::setStyle(SPStyle *query) place->set_tooltip_text((i == SS_FILL)? (_("Pattern (fill)")) : (_("Pattern (stroke)"))); } } else if (paint->set && paint->isColor()) { - guint32 color = paint->value.color.toRGBA32( SP_SCALE24_TO_FLOAT ((i == SS_FILL)? query->fill_opacity.value : query->stroke_opacity.value) ); - _color_preview[i]->setRgba32(color); + auto color = paint->getColor(); + color.addOpacity(i == SS_FILL ? query->fill_opacity : query->stroke_opacity); + _color_preview[i]->setRgba32(color.toRGBA()); place->append(*_color_preview[i]); gchar *tip; if (i == SS_FILL) { - tip = g_strdup_printf (_("Fill: %06x/%.3g"), color >> 8, SP_RGBA32_A_F(color)); + tip = g_strdup_printf (_("Fill: %s"), color.toString().c_str()); } else { - tip = g_strdup_printf (_("Stroke: %06x/%.3g"), color >> 8, SP_RGBA32_A_F(color)); + tip = g_strdup_printf (_("Stroke: %s"), color.toString().c_str()); } place->set_tooltip_text(tip); g_free (tip); diff --git a/src/ui/widget/swatch-selector.cpp b/src/ui/widget/swatch-selector.cpp index 16878ed388a30d0dd3d744f7c44770df2a7f77bb..c3404be01e6a8ab6af0a9f025deaf59623fc0d97 100644 --- a/src/ui/widget/swatch-selector.cpp +++ b/src/ui/widget/swatch-selector.cpp @@ -27,21 +27,20 @@ namespace Widget { SwatchSelector::SwatchSelector() : Gtk::Box(Gtk::Orientation::VERTICAL) + , _colors(new Colors::ColorSet()) { _gsel = Gtk::make_managed(); _gsel->setMode(GradientSelector::MODE_SWATCH); _gsel->set_visible(true); UI::pack_start(*this, *_gsel); - auto const color_selector = Gtk::make_managed(_selected_color); + auto const color_selector = Gtk::make_managed(_colors); color_selector->set_label(_("Swatch color")); color_selector->set_visible(true); UI::pack_start(*this, *color_selector); - _selected_color.signal_dragged.connect(sigc::mem_fun(*this, &SwatchSelector::_changedCb)); - _selected_color.signal_released.connect(sigc::mem_fun(*this, &SwatchSelector::_changedCb)); - // signal_changed doesn't get called if updating shape with colour. - _selected_color.signal_changed.connect(sigc::mem_fun(*this, &SwatchSelector::_changedCb)); + _colors->signal_released.connect(sigc::mem_fun(*this, &SwatchSelector::_changedCb)); + _colors->signal_changed.connect(sigc::mem_fun(*this, &SwatchSelector::_changedCb)); } void SwatchSelector::_changedCb() @@ -62,7 +61,7 @@ void SwatchSelector::_changedCb() ngr->ensureVector(); if (auto stop = ngr->getFirstStop()) { - stop->setColor(_selected_color.color(), _selected_color.alpha()); + stop->setColor(_colors->getAverage()); DocumentUndo::done(ngr->document, _("Change swatch color"), INKSCAPE_ICON("color-gradient")); } } @@ -71,11 +70,12 @@ void SwatchSelector::_changedCb() void SwatchSelector::setVector(SPDocument */*doc*/, SPGradient *vector) { _gsel->setVector(vector ? vector->document : nullptr, vector); + _colors->clear(); if (vector && vector->isSolid()) { _updating_color = true; auto stop = vector->getFirstStop(); - _selected_color.setColorAlpha(stop->getColor(), stop->getOpacity(), true); + _colors->set(stop->getId(), stop->getColor()); _updating_color = false; } } diff --git a/src/ui/widget/swatch-selector.h b/src/ui/widget/swatch-selector.h index c67b0132cf73dbcfc6da6d3d428318a9d3eb7ee9..082383087ee05c2a97c5044a3cb37463754128f7 100644 --- a/src/ui/widget/swatch-selector.h +++ b/src/ui/widget/swatch-selector.h @@ -11,11 +11,11 @@ #define SEEN_SP_SWATCH_SELECTOR_H #include -#include "ui/selected-color.h" + +#include "colors/color-set.h" class SPDocument; class SPGradient; -struct SPColorSelector; namespace Inkscape { namespace UI { @@ -35,7 +35,7 @@ private: void _changedCb(); GradientSelector *_gsel = nullptr; - Inkscape::UI::SelectedColor _selected_color; + std::shared_ptr _colors; bool _updating_color = false; }; diff --git a/src/util/object-renderer.cpp b/src/util/object-renderer.cpp index a827fa0922df2218aa6aa641d22e0db4ea3d77f1..bef875d28798ad7ce10aa13e728829e78d613791 100644 --- a/src/util/object-renderer.cpp +++ b/src/util/object-renderer.cpp @@ -15,7 +15,7 @@ #include #include #include -#include "color.h" +#include "colors/color.h" #include "display/cairo-utils.h" #include "document.h" #include "gradient-chemistry.h" @@ -36,6 +36,7 @@ #include "display/drawing.h" #include "util/scope_exit.h" #include "ui/cache/svg_preview_cache.h" +#include "ui/util.h" #include "xml/href-attribute-helper.h" using namespace std::literals; @@ -229,7 +230,7 @@ Cairo::RefPtr draw_gradient(SPGradient* gradient, double width, double py = h + 2 * radius; double px = std::round(stop.offset * width); ctx->arc(px, py, radius, 0, 2 * M_PI); - ctx->set_source_rgba(stop.color.v.c[0], stop.color.v.c[1], stop.color.v.c[2], stop.opacity); + ink_cairo_set_source_color(ctx, *stop.color); ctx->fill_preserve(); ctx->set_source_rgb(0.5, 0.5, 0.5); ctx->stroke(); @@ -239,7 +240,6 @@ Cairo::RefPtr draw_gradient(SPGradient* gradient, double width, return surface; } - std::unique_ptr ink_markers_preview_doc(const Glib::ustring& group_id) { constexpr auto buffer = R"A( @@ -406,11 +406,11 @@ Cairo::RefPtr create_marker_image( } Gdk::RGBA fg = marker_color; - auto fgcolor = rgba_to_css_color(fg); + auto fgcolor = gdk_to_css_color(fg); fg.set_red(1 - fg.get_red()); fg.set_green(1 - fg.get_green()); fg.set_blue(1 - fg.get_blue()); - auto bgcolor = rgba_to_css_color(fg); + auto bgcolor = gdk_to_css_color(fg); auto objects = _sandbox->getObjectsBySelector(".colors"); for (auto el : objects) { if (SPCSSAttr* css = sp_repr_css_attr(el->getRepr(), "style")) { diff --git a/src/util/object-renderer.h b/src/util/object-renderer.h index 9317c4dc01eca7383105ba75f010033701e3f6fe..a1173d179b31c9c6dd202196ec63ebc6dc8a5ac0 100644 --- a/src/util/object-renderer.h +++ b/src/util/object-renderer.h @@ -2,6 +2,7 @@ #ifndef SEEN_OBJECT_RENDERER_H #define SEEN_OBJECT_RENDERER_H +#include #include #include #include @@ -14,6 +15,9 @@ #include "document.h" namespace Inkscape { + namespace Colors { + class Color; + }; class object_renderer { public: @@ -117,4 +121,6 @@ void draw_gradient(const Cairo::RefPtr& cr, SPGradient* gradient } // namespace +void set_source_inkscape_color(Cairo::RefPtr context, Inkscape::Colors::Color const &color, double opacity); + #endif // SEEN_OBJECT_RENDERER_H diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt index 1718cc469fbf9f386aeac12a2815c594adb99cee..3a7d6e7e4a3beb825f13d216b7e5a68d434171a0 100644 --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -1,13 +1,11 @@ # SPDX-License-Identifier: GPL-2.0-or-later set(widgets_SRC - paintdef.cpp sp-attribute-widget.cpp spw-utilities.cpp # ------- # Headers - paintdef.h sp-attribute-widget.h spw-utilities.h widget-sizes.h diff --git a/src/widgets/paintdef.cpp b/src/widgets/paintdef.cpp deleted file mode 100644 index 3685da6bb8b7ec3cc67b47ce1f1d29edb949f9c4..0000000000000000000000000000000000000000 --- a/src/widgets/paintdef.cpp +++ /dev/null @@ -1,191 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later OR MPL-1.1 OR LGPL-2.1-or-later -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Eek Color Definition. - * - * The Initial Developer of the Original Code is - * Jon A. Cruz. - * Portions created by the Initial Developer are Copyright (C) 2006 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "paintdef.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -PaintDef::PaintDef() - : description(C_("Paint", "None")) - , type(NONE) - , rgb({0, 0, 0}) -{ -} - -PaintDef::PaintDef(std::array const &rgb, std::string description, Glib::ustring tooltip) - : description(std::move(description)), tooltip(std::move(tooltip)) - , type(RGB) - , rgb(rgb) -{ -} - -std::string PaintDef::get_color_id() const -{ - if (type == NONE) { - return "none"; - } - if (!description.empty() && description[0] != '#') { - auto name = Glib::ustring(std::move(description)); - // Convert description to ascii, strip out symbols, remove duplicate dashes and prefixes - static auto const reg1 = Glib::Regex::create("[^[:alnum:]]"); - name = reg1->replace(name, 0, "-", static_cast(0)); - static auto const reg2 = Glib::Regex::create("-{2,}"); - name = reg2->replace(name, 0, "-", static_cast(0)); - static auto const reg3 = Glib::Regex::create("(^-|-$)"); - name = reg3->replace(name, 0, "", static_cast(0)); - // Move important numbers from the start where they are invalid xml, to the end. - static auto const reg4 = Glib::Regex::create("^(\\d+)(-?)([^\\d]*)"); - name = reg4->replace(name, 0, "\\3\\2\\1", static_cast(0)); - return name.lowercase(); - } - auto [r, g, b] = rgb; - char buf[12]; - std::snprintf(buf, 12, "rgb%02x%02x%02x", r, g, b); - return std::string(buf); -} - -const Glib::ustring& PaintDef::get_tooltip() const { - return tooltip; -} - -std::vector PaintDef::getMIMEData(char const *mime_type) const -{ - auto from_data = [] (void const *p, int len) { - std::vector v(len); - std::memcpy(v.data(), p, len); - return v; - }; - - auto const [r, g, b] = rgb; - - if (std::strcmp(mime_type, mimeTEXT) == 0) { - std::array tmp; - std::snprintf(tmp.data(), 8, "#%02x%02x%02x", r, g, b); - return from_data(tmp.data(), tmp.size()); - } else if (std::strcmp(mime_type, mimeX_COLOR) == 0) { - auto const tmp = std::to_array({(uint16_t)((r << 8) | r), (uint16_t)((g << 8) | g), (uint16_t)((b << 8) | b), uint16_t{0xffff}}); - return from_data(tmp.data(), tmp.size() * sizeof(decltype(tmp)::value_type)); - } else if (std::strcmp(mime_type, mimeOSWB_COLOR) == 0) { - std::string tmp(""); - switch (get_type()) { - case PaintDef::NONE: - tmp += ""; - break; - default: - tmp += std::string(""; - tmp += ""; - tmp += ""; - } - tmp += ""; - return from_data(tmp.c_str(), tmp.size()); - } else { - return {}; - } -} - -bool PaintDef::fromMIMEData(char const *mime_type, std::span data) -{ - if (std::strcmp(mime_type, mimeX_COLOR) == 0) { - if (data.size() == 8) { - // Careful about endian issues. - type = PaintDef::RGB; - auto const vals = reinterpret_cast(data.data()); - rgb[0] = 0x0ff & (vals[0] >> 8); - rgb[1] = 0x0ff & (vals[1] >> 8); - rgb[2] = 0x0ff & (vals[2] >> 8); - return true; - } - } else if (std::strcmp(mime_type, mimeOSWB_COLOR) == 0) { - std::string xml(data.data(), data.size()); - if (xml.find("") != std::string::npos) { - type = PaintDef::NONE; - rgb = {0, 0, 0}; - return true; - } else if (auto pos = xml.find("", pos)); - type = PaintDef::RGB; - if (auto numPos = srgb.find("r="); numPos != std::string::npos) { - double dbl = Glib::Ascii::strtod(srgb.substr(numPos + 3)); - rgb[0] = static_cast(255 * dbl); - } - if (auto numPos = srgb.find("g="); numPos != std::string::npos) { - double dbl = Glib::Ascii::strtod(srgb.substr(numPos + 3)); - rgb[1] = static_cast(255 * dbl); - } - if (auto numPos = srgb.find("b="); numPos != std::string::npos) { - double dbl = Glib::Ascii::strtod(srgb.substr(numPos + 3)); - rgb[2] = static_cast(255 * dbl); - } - if (auto pos = xml.find("", pos)); - if (auto namePos = colorTag.find("name="); namePos != std::string::npos) { - char quote = colorTag[namePos + 5]; - auto endPos = colorTag.find(quote, namePos + 6); - description = colorTag.substr(namePos + 6, endPos - (namePos + 6)); - } - } - return true; - } - } - - return false; -} - -/* - 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 : diff --git a/src/widgets/paintdef.h b/src/widgets/paintdef.h deleted file mode 100644 index 68da249943954cc815be5d9dc019b5ba1b8959c6..0000000000000000000000000000000000000000 --- a/src/widgets/paintdef.h +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later OR MPL-1.1 OR LGPL-2.1-or-later -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Eek Color Definition. - * - * The Initial Developer of the Original Code is - * Jon A. Cruz. - * Portions created by the Initial Developer are Copyright (C) 2006 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifndef INKSCAPE_WIDGETS_PAINTDEF_H -#define INKSCAPE_WIDGETS_PAINTDEF_H - -#include -#include -#include -#include -#include -#include - -inline constexpr auto mimeOSWB_COLOR = "application/x-oswb-color"; -inline constexpr auto mimeX_COLOR = "application/x-color"; -inline constexpr auto mimeTEXT = "text/plain"; - -/** - * Pure data representation of a color definition. - */ -class PaintDef -{ -public: - enum ColorType - { - NONE, - RGB - }; - - /// Create a color of type NONE - PaintDef(); - - /// Create a color of type RGB - PaintDef(std::array const &rgb, std::string description, Glib::ustring tooltip); - - std::string get_color_id() const; - const Glib::ustring& get_tooltip() const; - - std::string const &get_description() const { return description; } - ColorType get_type() const { return type; } - std::array const &get_rgb() const { return rgb; } - - std::vector getMIMEData(char const *mime_type) const; - bool fromMIMEData(char const *mime_type, std::span data); - -protected: - std::string description; - Glib::ustring tooltip; - ColorType type; - std::array rgb; -}; - -#endif // INKSCAPE_WIDGETS_PAINTDEF_H - -/* - 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 : diff --git a/src/xml/repr-css.cpp b/src/xml/repr-css.cpp index 5043db44fb16fe09dbe097d0355b259cae2c71e1..b19be97a4e599db627fec21b5c2c47d4bb88ee76 100644 --- a/src/xml/repr-css.cpp +++ b/src/xml/repr-css.cpp @@ -229,6 +229,14 @@ void sp_repr_css_set_property_double(SPCSSAttr *css, gchar const *name, double v ((Node *) css)->setAttributeCssDouble(name, value); } +/** + * Set a style property to a standard string. + */ +void sp_repr_css_set_property_string(SPCSSAttr *css, char const *name, std::string const &value) +{ + sp_repr_css_set_property(css, name, value.c_str()); +} + /** * Write a style attribute string from a list of properties stored in an SPCSAttr object. */ diff --git a/src/xml/repr.h b/src/xml/repr.h index 9cfc68791aab9079d8221b153b52bc5e22d34b07..9b11c248e487a4e937085b7070dfeda5c28dc004 100644 --- a/src/xml/repr.h +++ b/src/xml/repr.h @@ -89,6 +89,7 @@ void sp_repr_css_unset_property(SPCSSAttr *css, char const *name); bool sp_repr_css_property_is_unset(SPCSSAttr *css, char const *name); double sp_repr_css_double_property(SPCSSAttr *css, char const *name, double defval); void sp_repr_css_set_property_double(SPCSSAttr *css, char const *name, double value); +void sp_repr_css_set_property_string(SPCSSAttr *css, char const *name, std::string const &value); void sp_repr_css_write_string(SPCSSAttr *css, Glib::ustring &str); void sp_repr_css_set(Inkscape::XML::Node *repr, SPCSSAttr *css, char const *key); diff --git a/testfiles/CMakeLists.txt b/testfiles/CMakeLists.txt index d83e6fd77c1d34659606878160695f5d804650c8..a9a975e44cf99408a0c090caa1a19925be2725cc 100644 --- a/testfiles/CMakeLists.txt +++ b/testfiles/CMakeLists.txt @@ -60,15 +60,39 @@ set(TEST_SOURCES async_channel-test async_funclog-test async_progress-test + colors/cms-test + colors/color-test + colors/document-cms-test + colors/dragndrop-test + colors/manager-test + colors/parser-test + colors/printer-test + colors/color-set-test + colors/spaces-cms-test + colors/spaces-cmyk-test + colors/spaces-gray-test + colors/spaces-hsl-test + colors/spaces-hsluv-test + colors/spaces-hsv-test + colors/spaces-lab-test + colors/spaces-lch-test + colors/spaces-linear-rgb-test + colors/spaces-luv-test + colors/spaces-named-test + colors/spaces-okhsl-test + colors/spaces-oklab-test + colors/spaces-oklch-test + colors/spaces-rgb-test + colors/spaces-xyz-test + colors/utils-test + colors/xml-color-test uri-test util-test drag-and-drop-svgz drawing-pattern-test extract-uri-test attributes-test - color-profile-test dir-util-test - oklab-color-test sp-item-test sp-object-test sp-object-tags-test @@ -85,7 +109,6 @@ set(TEST_SOURCES style-test svg-affine-test svg-box-test - svg-color-test svg-length-test svg-stringstream-test sp-gradient-test @@ -103,6 +126,7 @@ set(TEST_SOURCES sp-item-group-test store-test lpe-test + ui-util-test ${LPE_TESTS_64bit} ) @@ -112,6 +136,7 @@ target_link_libraries(cpp_test_static_library PUBLIC ${GTEST_LIBRARIES} inkscape add_custom_target(tests) foreach(test_source ${TEST_SOURCES}) string(REPLACE "-test" "" testname "test_${test_source}") + string(REPLACE "/" "_" testname "${testname}") add_executable(${testname} src/${test_source}.cpp) target_include_directories(${testname} SYSTEM PRIVATE ${GTEST_INCLUDE_DIRS}) target_link_libraries(${testname} cpp_test_static_library 2Geom::2geom) diff --git a/testfiles/cli_tests/testcases/export-area-drawing_expected.eps b/testfiles/cli_tests/testcases/export-area-drawing_expected.eps index f22557757c0a4cab38afc3e4a949e8f03b7e7cd5..7da25894b3ff7792a05718b41bfa1c57bb9b629b 100644 --- a/testfiles/cli_tests/testcases/export-area-drawing_expected.eps +++ b/testfiles/cli_tests/testcases/export-area-drawing_expected.eps @@ -1,6 +1,6 @@ %!PS-Adobe-3.0 EPSF-3.0 -%%Creator: cairo 1.16.0 (https://cairographics.org) -%%CreationDate: Thu Feb 27 23:52:53 2020 +%%Creator: cairo 1.18.0 (https://cairographics.org) +%%CreationDate: Tue May 7 07:52:50 2024 %%Pages: 1 %%DocumentData: Clean7Bit %%LanguageLevel: 3 @@ -122,319 +122,320 @@ q /ImageMatrix [ 630 0 0 -630 0 630 ] >> cairo_image - Gb"0WGFVoNIH^]DMs@>:hKuG,0.X(r,f,]L#qUH]rg8Q\:I]joW(", - UT5GOfW;#Y*)i!>1tE8h,\O.4@G&jg$c)%$beRNlaq3MXV8gH@>\(UFekStW@gX? - $.>Co=LtPM<#'4hP%tkD9FtKkDeb!,IV_T%-_@a8<&7bW2mqr'uX$_SS)C(4\9_G0atVltr - 63+5F1fr249KKnX.f/-'IGmJ?4e7NHbiNqa.9"ptaa:fm_K*.NCV]nLU!fK!g;d=C*N6t,$ - l$4FY%u=\1nfi>Dt1^ - m\2+-c]BBi2i`IYj`'T"^oUP>:.?sfTqd:L\ArYrL#)c4@H":H/kFSG,bE^)@6Kk - Deb!,G>JSmYCI?`g+`OtH5\;cn:!_?-t(bbjoBKtY0_@`tR;K*!PP-nIJJ@0'J?SC\OS2iU - &+DkYKA1PLNq-nG?[p)IAsZEPp.?:dFki>Dt1^lH)uZE4i7N5$kl-nIJJ@0,#cp[E3-Klrp - @k0lp0$4G0V=9*O241*4@@F%>C;XE;Uf]sWK_SS)C(4[Dc#;$bc[S>cm^$"cT$470e:fm^@ - j;_9:^T^SUG0f_s,mB!VB.,;248/,Mi_a,)-['aBP(LQf1DCUT*W'mnHWSiU^KO46eG>5#@ - Gr(2$c%c)q\FH\fJC!<;G9'SfCDN@$4G0V=9*M(mIRon/WRJ$a5W.S@Gr(2$c%dTHPZBT=N - Bs`Xf\ClYj`'T"^oBEpZD?R=N@CXPP6*'n>$$k$dVXO!-T?.GFkF\Fe%<)@3l)2KkDeb!': - \(qiK-X]ku5CVsJ;MC^LQr:fm^@*.DP'5[gGCXJDMnn-k!S@)nD=k"$6$/iB>:/JP=H_m/9juuu$4FY0UMnGD/aSnh8rA6-Y,%_pn-k!S@)oP.ZOE:I;%Knf - fctF]NW7%rYj`'T"^oBU2eEuXYYf%>f3S"?(qYnn^2nUj.k)kt4@G?o*NU5oS'DCV-=@keei>Dt1^lB'iV\ - qnleuoJIE,&U[qq6tqR8)]P^[k8@5;n4LB.,;24&5.0RV;\@p"7j.[]7d3Sc?CZc5t_'F3h - ,gIk46$i>Dt1^lFW5V^*1p4?_i`_E)FSVgLY]DIairP(LQf1DCUT*N-=l>Z`AKpFWJA;.g[ - /@Dt1^lJ.rS![mRcKX:B$gXbB=k6YPSCfi=Dfd9/K - nX.f/-%b,Dk<&(k)/f8GREce8rBa[=&MjsG_X':YiGAQnVK0,=:^CtYhlLX\6Q=EH4D0&;- - 3gA(^/,5B)I@"=\3U9pTY[jgeHh4jtDl=`CNT8(4ZR8Nik(7>H&[6:gq%A8TE_9m,fYo\eo - Tf=9'-'2(Iqr4ij;#/Lr7WCp0W_IV3t*h)aegN+>7`!EcT#8@Ch60'WILL%T+*7DY!U2_.s - %O(BRG$4FY$I>=+-'Wk('2_Jpia0O8LA]Mq6j@&PjFsgeA0,1E.c"/YTW,E&qc'AkWW#Sl - _rG&cfu*c,?qZnErD!JdSh(C7:?4!Y^c[^K7mOX]keG7dB(eiojGCoc.b"I\h%'3:a;ba/- - $(Ok:ACPf2^YU@UZ$68V,fmYQ-46Y]$4$]IJ%7gP*?.]'a<_-,i/N;BSgN(4ZPnpF\/@A/3bSVFRRH9#4?n+)^m6;$]_k_f-!u!\s#@4FYr<*p/b/)5.UYR. - k0s1'Gc3&8)B\E:oKkpUMkk"'jXCN?4X=\ft8mMDHpF!4in:0'Gc3&8)KhROJb.q_9^!,W6 - i6F7b1FcC7b&O7fZCg!#?R14j+sA%D'7QKtS4,8Wh%c$@%TJ!jLV8)`%r'e>P/l$@=;7P+k - d6Zl@\>Dbb/J@3l(\;-3f6*8. - jSM"$s>LA]MA_'p\C`6Z+G-U[U7hAL!!#?R33R&[9:>fXjgEO4l_XbaQXJ>iM"^o<+*@?IT - DelCWh9#LcMW/iX2qG*r*qBk1@0'JLEr4?S"-f_(LkkXu%D(@UlROE-:;0l=;L6VU$AAXk_ - 9$Om'!749bpZoK]3RRI5kY^>2]rrrtQef`SM6o')CLSH;FHtapF3XM=9*7+3OZdI0r93#GWOmJd7T:n\olF9J9NCm - !4VYA2(@m;4\35T$#.nW7=(?Wa;4-0<*-J"3X"pXKa$DchBU\XEOi"QMFTI"&J%o?jDH#g] - l]Za$fD0q$c&mXLR/sEV+_C20;]Uf;BS.;(4]Wd%P9JMasGk>]TpL:FdD]c'W(\=ZB?EB:f - m^8jW%N?En.&aMMKUh031sbm[@C1A$pCp?jhYli)\-S,mH6;XS[fTdNpPI;WM\c^e0Jp6"' - f([=A3cK9T@!hVsB60;?pc?PVosQB`0+K#.S.COaVRK8s3a]u9e]M\dlm/f^>9gr.7=TgiG - k\922O8RX,l:[p2DjN1#OG?@EKC(qG6>6jmT1rtGnh'\d_]0'7gEO0\^F=CUP_?+]Bi1:)V - mC9%*M)WhnLebR\#M>#PmcO&k8sW)i!X:&(!4NHO2r>_tf6(0b+F:tiPnYV.h27"9!st[*! - 8KX-mQ?,4gsB?F;s55T<`51KS^Gs&a0HjT@0*l\pmm0KH;UHD`&m1^HJSS%*aq"5XJ?+:$4 - FYO,B(mcLdeH6/07*\21)Tk@`sGiZ>AcK+A>!I_dVMNgPbo]l#JE5!M'Zlih^"h8gAF26EQqNH@\[+;>T - A>K:p51oNcoDC8aRT_W=\02j'Gc4'a'YHId"Q)&0&AZ= - d3f+MN3&f@kZh.d":Ttfi7@GMV]->d\XF0bSY0_gQ#D!q7HrZsbmiJ*'H@f&J6OCE:,BtBG - iQ_"k]YNY;OlQ#V>`M+.0>+Jc(Gd%eRl(+@dWf0!)3,T@'u^o^O>-RU - tQ$h['\W!,ndmSISD-,mDRV`*,/Pn$O2U9FXQH@O,V6mr";4[Qep!S!o$MY0fXOj2cAZ*rY - #l.2XW$4FYOe#t'&fQ2AKGg:miU=77I=@l@iZpZr6.>YhBE9m/Y$&Qt*,!AW4AO.);8R_F) - =]SEs=9,%9B%-NCVgYfk.UKB1X3mL0ok:t"/_MqTWICC[G>\[""lP]_W%Z\R`=j#7?EBqNg - c>1B'i,10YQ.!tcdFCkEm\5n=Zj=,:,/^$J5>;1 - <7d,dK_I6-5,'!cV`>iT1:(rLt>USulsgY,&<"Uk&%sZ^Mh98O6bc"Ot.Qb7&!0RI2`bXO) - g,U_mf)ZKL)7g/,+F\jXYt_?+[&kFbCTbB71^@"hIAc7%(-lp`m_gVfQ(/#6[>PUJ'_=)nd - %243ZqFW^-WKiCIpoF4KUjJ>R-nG@"p?u?t0[_InDt-e\RR\kW,0ep?6t$*! - `VuCH#/g@rmNipRYcHZIPE^I-=@mN+N@FNZ<_K - >?UaRboK$4FWs - 8)KhR0W=;[Hgq*4!fpA[goOa0*Nr;4YQ.og[X4=VP]"k8`9WU2,!,+c*agq4\XUWH.p4@XY - Mc1[!*hPX`_;!sm9EkPV^Jr)nDF-Fl\O%u_F@O4IbhAK#sHn - 7p!!I3W!ldH:iF$up_(FKC:lh4ua#eQ3NgbkLb'GM=5(Ja=eAh]Nh;-5!5XJFK(Tt2dZ3bJ - I]@0*lY@`Qtd#BHsg.it//`_$RF$QoRIDP**]-'')lSJ\:(XWJ^u%9) - \G4>Y.4i!OuSmE_H$9nW_rtRWWqU*c*d@[oZI48thLa9b$EkT3nH[ql^\LJ?_7-=.&p1TcP - cA&[][Z8d\)-R0DU;o2J*%R``Ld-$s7bDSH&c4+Gr^b7k_BZ+WV\]q>U%(Bh!Kf3RuaYGA[ - IE-@DHUi?cFl6GZ$*6o)-q88 - =0%4ES3J\XDp&7OoV8Vjgt6ga\\X4Ve,@8?JN,OON++s"DIi@J9rtH&iC]!K`nDpRQ_0^jm - ]F!BEDos>DX9Hlb[A4mdf%,%JbVZU>+#UG.@b0=q.RdAZ#8s:P,3#&Y#8@%f&[lVC+rr:F^ - OZR+Al.M]CdNT"El([,dO,HC=Ul47E+_a4T:_b!cTgU^_2:A;ka)EpAtP6Ff0UMg2:GlKK0 - j*i^n"7gp$dtL#l)($aqFq$^AL0Y\X:<`h)!V9*&PI3rQ]4A2gf>>:/.8\D5]ZeR*gLVJp1b%u6m`!.8`nha2t; - ;lP,"B*'\kdClV/WVk8Y87[@N@K"`cCiX-q/;R"1'4HQ - 6R>_hJi@4p!gWq]AraR%>=rs":+\F2jc_ZF>r_N^l%>JMNN)"FRIDQcBY:(YVt%61]*IIK` - r"jLYeM50R91(+7V2'Q6r/"ik\4okGluVXHAKb"R+@\*c2a/N%CD0$CuB'OliOV%==V5[YUXCQ?6l - \Z%ZJ[f@I`d-]-CiHA."Y]E_8Vi?LMciI`)\Mc - 1VclM9A>*a,ZCf'(BkSbAO;!=B4MKk%`,(lY4:SKU2pi=@SB;?TCj*`oFu?I?IO*ct`iKa$ - DOn^gL,T%U3o`@!lt'4"0a6p-:6jQk20omlA>C[@BBKa$DOnX\,;B%1bt9c?o[o5FQ4mNWc - C2i@73$JkT1Q`KjL@0*lW@Y[N``JAMA,iT.Er#1j%eMct$c%Hp> - :1dZapRbhnKAEGcF)U74]&ek/XNO4q3I!l'Gc3,Vm[k`)n&S('VFk#Jh+F!86'a(fFg?cq/ - $j`C(R$)#`*5djkEB7k?#8Nh*8&\g"8W"!P!)pF2EV_d_j$/4+bA\OE> - s9nh.!jhm0M@_?+]8oVR9`G[7D+M,/6sMY35K/`3kEVRm0'2PI>T>W@rBUVr[*.R)8m4;Ls - q[!^%D5YiK!YQ.Aa3OZe4%$p1pntQ_HFBs)`Fa\pJ"'R?ZrVi+B/JJtNWI9Pf)&2e-mO(6q&-b(8(XJF7G`'?>0U6]uWB - jt9F^Osl#P`hd!\fm\@#70X0C(gmUCZhpK`tk!s8KtAKrr_8j[UEN!t#FOeG^GO_HtX'j@: - n0J(E+!r8p?UDI=MhV/-&TRpFJ!Hlo!TnIe3Y)TWeD - i*I;Y<6"-t_Y4A=un,\4H@-Z+No*btQ6[+Y7I=MhV/-&V(hg*;9YVeI7Ie3Y)TWeDi*I3"7 - 6"+POBDKp[pjg3_0QlHY3.3Kl%+=j0JcQ-nG?sa**jb[S^\[O$:>a'Gc4uj0-c_-u?4bQQLX+%k&t/;o - `7;DG\uA:6QcJJ.F%Z!)k7g:,=<"r7hDP6"'e]3k8a8U3Y#6A*)kpi"$/&^spKn;\CZu^)e - L`pAf\>=9,3nD+hCC"a[QRg]RI?@0)`V[<6MaKe:e!jl[]C%gi][eGEGs-/GtVVh`IA4hr-R(4`C5RjTIbfHNIPIdR79TWeDiS_5h`dD?[bU%378n-=XN?n0R: - 3_K'Qq7-b*K>I[DZKLEYK>O53KB22:i"$/&^rY@Wf`m/DTEiWc4hr-R(4_O\LR/qop7`TN5 - PG/-:fm]ep)R=K:tRPZ?[hDDrh-IqN8ts8.;ZQ_.mNb.Io'5I7WJti2(7g&h+)q^!WJar!0 - $:%^VleL'tUmPg]RI?@0%4uTI[DEa[cKK>LsF4ltiJi"$/&^rV)?$Om - GIWI)_44hr-R(4_O"HPRG4[Rtb1T6l)Q-nG?CLYeM=:tY*FX8L`Tr'=`l(ch6V4G1N-cs>_ - q*oIP9$c'iWG98RgCphQOT6l)Q-nG?CLYnM8:tQ]B=aoc>rh-IqN#[+YN^7)hFXR!q#P`qg - !\eZEW3CEUKDaL,r'=`l(chHTCkKUUlh9mj&+BgX"^pEDmTBaJD:%\2k8rOl$4FYjYr?85' - XgkOb0N5d*u#K@OlO&c(_jDMB?8C^^`O7+JnBbYDs5Y7"4>#^7hb2'"@&f$A63]lG<$Y - &+C$^"d$7-7YSOK#K[K>^c)rC5iVJYaYkR676NnbkQ/DkYQ,=tI\ - :'-u)&2e-mO(6k!8gUT!/\lZ\C@1UjgqYnci\Y_@0%W+kJ.^__H,kanj=tI\Gc:@)\ - i"'mNk*i!8gUT!/]I&ZcJfD=*7=7Ta1@I_?*9`JUIn8$@m$4@trLYag9luh,aIcpgHl)t$^hl\sUrsG^MT]b?6+1"KDa'uHp_@m$4Ge$eR!Orll/mi[^AOlVo]H4^c)rC^ooUSXtN!o06`WA9@m - 58'+aRGK`r$1j5TY-mB40^e$_A%7Xj=tI\O&3b%n"]GOb/Bn(R%dVh`m/D??el_?-^]r>pg - (jm'*PFK\nr!fIa\@=_BhCQCk&MY5>B[S.fT;-4qE75e0^0$\miX#laiq0G`"mOg``!'q;7 - 828]i8DL7F%q^5'5[f5n&S^+[`?'jt($JLRFBA:["p=s\"]BtsHR)d#e-"?]#lFm\"d5G.H - R'SbH=W$'/3hL35bYDI;S,f7]Q!%En(O52a-Gt>X@!-YK>I[]MEMT&0FA15UDk#8ZUY'.7U - ^l/dSR9IFRMn!04!6U!Q9]/!_FWJ7j;lp"Rk'e*XNqi'Ga4Hm#_8LLVa*9kK=p'dF[%r!@3 - Q>!b'(i#p?`+/.bSCOT9un_?(l[I4\_X8.\+,6jGZD"LQ2"jH?abdk9EnDT;,Voc88 - FU8Q7&B<#5@f;/7:?5SYt"jd#o"0EP:q8.Y3BipZNU>_"oNE4lH41[Vg<[RarUj35U\2A^# - l3bjep`U!MgP>$[@Un&jV>KY^gAT)qL>rSa5NI$aW>)#VHGlG_8-l5cUT`J8G`QCJ+O`6QLrlsel!5aZ2!XW=s"G"!Y=+:db#!0AlA$abaR&c`>;=PuKt,6>Rh$4GW"HR&U$7:Cc)`Z>EdG#6gn)B(M=] - XXu7@)[s9b75Loin1YS_2]Q-[r.c2lDq635PicdJ,"e5NdpuIBeB@S?i4Ano,hr9Z`;)V^GOFE/2qEldj.L6HjXU[]:@e&*Ps#aFW^uNo2E,P&.9ZK?gC&N-e(NXUhUFdioC: - -h1\.fkL9+b>Dr/,JUITOreh'g'4WQSZ%iWr;lI;u[QhULXh]MXHcOPHp`NfI?Io$'1J!mQ - eo0Z9kCMMFWs08@Hp.,1f/7f08p$*IYEmhM6r78>(54QRqN>qc@T%uO[/&Q6iXdktGgGqLQ - UQ5n4qWX-f:[@r&c;2aYYGZd#,B6l\@?1PQ>*?7sV55]OCmk%bDbp;p7gDhZeu`H&FQmVQ4 - _\VFaH2Etf(=CY\^CHGs33ZHA8-JjHLX5_,etH-!+&6S_E+eZEFRU^IPg)=(IgJ&Vu?L:OR - Aea-SmPqe>U[<5OZcKO)qIFSNhZr7/l(4\?JI)J;6Hqf^n9-3a$b)l&gl>okN)29fdKL38M - /WY$BZ#$1hgZL$r7_7V9I/8)hQ/rr4\oZ'1f - =](=0[B7.=Yh+4I0'WK(OT9?)+:DC[^A?A]B0Yq)YQ4i(iY,5W==:17s7r/qIs=dL_O@/t=/Ga - B2+":!8ki?$G18lO/WO#.eB4^#7'6F\s0s?Ns1@dPs7tp,s4aeN%9BL,;BbjPH.K(bhu<>f - _SR&ITet+m];B]-9M[QJ.=6F0KLj?>J-lK-*m&5mR",O86jbPjkmBE_SUJ) - :S(hbbVLJjpMTHoh!Z2qelrshHe0(0;s9:G8$Bl2+SqMp.&Hk?,BnT\>-XgJ:OY(Bq/&_dY - ;`L_(;2TMdnA:#H6@P[!$oQ^@;k"-s+;Ce?b!>fC*;V - _u]PB;#=hkn-WKi(bB*rE?8,"qp;$6XFq(\[F]e_g/A\:4C(q4ol@W^mVC3'#Wo$Yk+TESgS[:+`3A7\LL'&MWb@70HR- - U;dCjF;kmG4l=G:U0^4"fuIXDr-L2UFqQ7MW@dYU8JQ_HEN:\SD'@nUIZY%R^e,LK9crD5:mli4NNH.?&hkM0=C#n.H?=n*-LMoWd++Rfm#B(Ok4q* - WcWI\G9VRuo74^GfJ.mLm-^1[CWGZ+XIkX##NGKCkg&6tb3mBob'Gfpn`f(TgJ*f)`=m'`DA*-tVr@lIG;ahjJlTpLe9FYX\a[;MZ*sU8$ - XCs7B6XRJ+AP3[4DpOQ>''O*a$de9m`E6qs6+Fm%'G54"CkX%1\)cLUEc!3)X'qX/RBgT+C - /4+f:OH@*jLddnR[pi:J/V7hrU="ijIiu^d;e5l96!:ZJ[n^p&%W46K9B0Q7fEqDFW6^3k" - R@3d=&+hjNmpk_H27c;PKDaI55Y?$'fIkS?['JEtO"3(_=ZJXVB"[mdI8Bgi6rd797H"E$>J3\q*Yha"DN;RC3f''>Sr,`$fA5]?,NINc)"=32Z:=s]Yd@]g:=4j>d8no - jKMo^QbHl54eQpGC(,Jaj-p].=3G/=ETbrNl..#l - sDS'_\kF@Ge'gHQrV/D;2\Z8!l7P5!J\*nVo(>i!kHSi'qd7ci.fdp1(4l+P;N& - fO5g:4LcLNp6!5q`:"P.4M)Q:kTT5JE@T1cr0!2MR]M]hnc;V?fN_TU/"s5T:mo&%l60`WY - \_BScHE%XbJg%h1aWp8K@6\4KHt6AT0EQeT[XII_,VO.23Pko5aK&!L_*g4T,c-+q)15$B&Hf-'N\JSm8IfH/K\ - C.jus3fN4=VV\2-7b>sV;OFC[@8-0J@amJCdbu'=hO"=jj0`]fR/5j;fV:&=0kHSk&\eorZ - 'G`.WObCKgo@/($+gM*,;-9WSO!OYI]4ZRoqAfnc]I5=/asWEgY7`nRpo*hS'L52d=JVB]. - b-g2FM@^oddL*7@74OdJt>f=Z\ndYV5`oH.K*9N&3:L@WTbk.\uPOd'ArJYV:PX+@Ig9ggF.$6:Cl+-nGqY,: - `]aa?gX;Ds@!RJ0:Mh5Wf\Trm(Ok>l&jX+:'AH&T.AFl>j - =mr8>*-nLIYQ]rDii]O8So&Ul7sTs0&oT3&%FMW]*-;Wl_!!Mfu.$Wupb($ - BjaZ1PXLJ3PG5@*JE;U6h,`Wo?&+1Y%o^9>CE+G`'?>G!-`3%Z9/3-iN`Di$f!@=LV8KH+&/hH3+*9%YY( - fHV(3N.\8G1W+u6aoFM#\&IJnjiKGb:o63u&%flYOTs0&;VQ4r:YHFnba3o#/J/9Ubi/q[4 - ?)@>g#GgM_!&%/N^cN(-GYYkEFm=>%UB#ge'Gg2`91nkTnXL+@aGf=>q1/O]+pn^&l9d%mP - Er(pp:l?Hl2J*%PPeM_?j25f]J`*qF-b.s3hs_U$MDK`\kdrr00^NY\3XO(YC0HNF;<-tO1 - uUa'%n$hH<@$3fq^Fb#6b)0'Gg2SC_b9!1o'Z5Kl'haLKreJtLq:E - A$HJce)o(8<-H9en+_ECa]3)E_m+3RVGB@b!r%k;Cr!^3PgpZ%hn+Qt4)i/]NAeOd`$IdlM - f@RdfF=ZrKGpMS@FjN6"'edX]=n]%kR23^9Dh<'6Qb*0i(8.g) - L'9Pf:_Ph@inc]/R(/!q65E"oPi9`H*+#?r-*BD7q=^Jf`#($G]j[(@^4>ESXTaeL>qH^Q7 - n5Z7YY?BHH@>G_O!9,2uhso\:hkHrioahMM/3P@kQ=p - S@0%hI4`KS6IW9n1Kl,5C]H[2;+$QV,9MnnWb3,!];_cV*E?5!2Co`j,qQRU60L9+J6/ts- - k83$`Y^OBbQ3[99kmm8_$O@+I$OHT=(!l^.`ZF,nD8eY85eJKr+DZdjH+IUa+8J]>mrsWob - :A)[-nHM,%ob[Sn,I.hff,da=7ol^cj+e<$4FrORSm9McmQ@8;mS@mI*GY]$s/ - !/g?/.`@gWd)f"IP5aPQBeK_6 - \?B%!EIV&ZOUTd"VHpGY5)&[.T0!2E#fbR&R - 8X>Xg@)D-P-rF"TiA-MKqDbnKB*1D:Pr)6KE\]jgqYnn-!3pYWr3eSr.gip[?Ja]KnH]/aB - LBnp,N"3B^VrGQKTs=ENrC[C=cE#/jB$l0U1i]>m]IYn1;6=G - Ef>HD;*c[9?7GTts">7#FPpQV;E^k5Hc+NQaH@\,#I\r-70]s=DS%16=>#Kon?i'AbB&7f. - .]fn/&9nhS2Z#.+%I[NK1oCU[k:6Qcq^jT'^+Q3*qQtQ$QLVskj;H*NTZcD%Tl;$o&'Y#e' - $O#6)(%ZhGX(0']l(6SVV[D6aIZOX*7:Catqk/glQ7_DShFI=_:X1aECp>$.5=gA&U9HnuI - 9slTSN:?3f<5udUeB7)Z4./1X2d*`FST&Hgl'#N_:M]+4T"h((/L`!OerS"&%nso=n8m5\/ - 0i&Jo?g0H2crEVMHVJ4gRZ7Q0Z0TZP9ncKa!'HNJ`"QG[d%b+5NtWZbW6HdA:fnk8?AuRA+tG1=\TrgP,2J,m1jc%SFkp&HS-!o - .YWsW/<\[C!IcqlZj!`_Be"EpVH@YoQ"n"]cJP_0_AuD:"ClW`^h7HjpaIobkHBe=e"n"]c - JP]IO@\.LcA"7cch5e/rb3YnU#2N%'U&oP5YWsW7To+-pi"$$LpX+kMm?7qc>G\sT:K67I' - G^t.VcCqsDHrKP*3H_qBT>Ae0Bb^m',A"jK`sg%AYn+][SM3q>'9L!nNONcDXu6))[SD%.+0%RV$4DO+C_cR>gV:uIn"UVOY>7&)lkS>LI]* - =M-nGoFlVu+"c#8UYO/!GVN]goLfMXk#I]*=M-nGoFJ#$PF:ObDC[M(`?>Zbl?fK;;TU=82 - d.aWda!b(Vs.csmEZB'5MX_=_AQb1B#8Tlu&Zi"&3>#8P3-ltl/R[_mkDDq;=p4huZ5,]&_=* - ;gor7X';5`[t,7hk9_-$i+]*dgfq.+eDIFRMiXo:#)j?$EF%?#"M[HJP:bc7W$t+@I!GR-0 - 5hS08qD>>!bA@8PTL\tAc!7d)g[F@p<;HFpAngJK$jrU-;?*uY.=Zr+e)YIp#,:Y9B$-$Q' - !E-&0>`5aaPV+G;\=/iY!g?L?'Ip%56!/T*6.d$QF-\dGDd?^8'=.2jl7\(tlZk4&cSQ;hI - >0e^Q"[_@t<6V[\9jdEG=qfSV[q10-A@n_NM9g$l7ChjP7:%Do&2ITm1!c@M4lje"chX)6# - *\GK*[!YU&ILI=(Q\,Z@=do^VqOe%"14Gst\3&KAt\=M6(`+M%2bP;=clb$+t+FGN:R-3,,A`?k"kZ[LSKAi- - "a5#ZGG.=06\td7+&el*P*ck?*o>V^W`3X,Sj3=uDo?A/Kikla%e"ec,ogg1q8n!;?=fPr& - TDir@GS]Zrgbmbm^0T."`90.4VGn)r-a+4"#bSLSk5##NVi!F\Y-MPY%H^lO*[3eo&L`ug+ - 5cW]@=don7)_:Gr8r7o-JEV&ic9\IQE/';IJOeD8@=f-,q(=+$pL\#T!*NQN-H$'C2-k;]? - 0,*Fu?k;pg?T'2!B,YH8L*0loF'^:H&3c - /UR-/O_)^-7jlcN#J1Dk+#HC4JPLU`7gJBM!UD4TTUoIj)j/.aRdU10]ASerdq7-eS?Y:uH - *brS4>_F%.bmd;j6"ktC^hH\h+3c&\=n,0rdDlgZU\^GathRKf&WbBN)JjgDe(6kG]=*A]R - q#drqpWb==BDT0p;u_%#?cZ"O!:frfMKtBL&[AG^bo:[^RsYos6['+anrQbI41JNJiEWimQ - Y2f%DQDhG#(ZJs:;o=;rpV'#+F9_lcO>F4GtI`DTCWWACh(#oebhig?F1C^#aN`:R-0T%#) - LmX$Wlm7)GJ5YHA)'lLPShkfO\C8kKIu@U8>(ZW-hI]4<$3N'87H6TXLDsIOY;0bl['-I#=;iU5319Yu,9t4?6de/N - a_?-G%'c$Fo)J!RpX]Vl'kc[>Mb;.mII/oe"(WJst1K`sg),o-$qTANR0Kc[3U - ]@^Lp(?eWT@it]+/sdF;19[%)5@j!n94@L+=N_'FbZVpN/+9(c`2;QMg?LtX#CoCWLn)[/MJP^Vu`B - 4Yqgdf#0hRRlUNksd=Y6Tp1Is@tgGK=j=>b\*CL"2M?K`sg)9.K(,[k9\UXjc95a\mcMRA\ - *p,eG>QXOGuYQ`.u2@/K%X@=_7#7`@L)pNt#mEM>n^c*_0To;:q(*WgTI]H6,hYGM,.7O#9 - )I\j863;o8&Gs:kojP63VlKfHg$qBFqh+Kj5n:L7Men&.qG"ueP?g#7@Q,3Trnt_.g*d]FE - 2uhe"dd`)$VJ(f-.c(5/kC0s0mcO$tQ:k%V+T.g-1!ca^SV0eB]@2atb=r2?*ZNMki^JAdN - B4D]ol>nhU*ZDE<[fR2.\BTncVmUci=jl4eC:]Td:ZmsE3=E'UB"LB3iuKpFd^jeUN=+rX\ - r7D<'I!bShGJ"P^!>E.AXZ2p3Kq/.d8/P]8KZY - DPo2Jfgp6T1(cL7B:0P3!'!TLdX6ED`)\9M\Imk7NF$\V+_eb87[0jQnh^8X6,fO3.ild=_ - 65XNV9?^H%$]6fFUb9\Pf&:PX!gLp$6NoK$h9geRlZ[#cQ4,?r?EOqjU\BJaFQ3m4C<_L(n - .4c#O?W/]*o]b2M_rmqLSWaIODPcbM*Y--+c>naL4tN%nYX$9X;8kZNftGA*kGPU1Et(TVW - ?*VI%`B'dZ!?;Zqt&oUT+i%HO2k8EPOsoC^WZ;uAL;nDW5h;JRWq-F[YoH^bIj.RkM("`$4 - DOB#?B">r:A%FpA[l^FmI7`DlrFQ?4aK;;YP-uf7NCRS - e>15RVcM'%ld'B&RD[Q99=uU]b@TMMAR<]s0u\E0LqbJi/.a.8="7L`I0L2J+?`V5'YcH[? - kesn^>_GpSXeS_>imn^bCmImX2JA,'G^tBc`3d-^C00: - 01tI[Tu3Ohq1Yo*H0'r6ZSqQ6%J1C+C>`)ot`76bFt[(#A7aUkcS+C\c)Fh<%49JWTY4(%Z - hGX(.qqMMt*tL>:D#Rp>OJY2XH;/K;kZMVjii]F;_cMSG.f![4g6`B=1m:Xc+0MO9R?1CV9 - DRI2^L(KBYe0(W8NI8]+M[GijVHVdfe,Y"i%kOmF1=0N%DR_QpO1)K5%cg'&/V<<]>o*8kFN9 - CXqTQk-J;haZ,19ZDD-3n.n`-kKb+:M;OR-3.8;sUDDpSp?bfO.>7ld=^o8O55FZcg#D]f+ - h]eOo@5?(35n&-apibk%SOc&eME_Wn1'S'C4Zh*J\5.A]mS3ks=W/F9d-V;a/===>^3"p7l - LF80_D#EIsS?_;-gX0:f[imC=it_-nV>%iCiQ,P5i@&rWHP_@#:P\5iD<81!aX=L#U)_5Eq - e1B/$oTohb/_-+EJlONY;.c^>N)K];%Do2'G^t_5>j5^Zt\?l-V8hF2m,BOa^g--lR_Q=,piF2_K`VC3u,?sei*CVi)Ec\CZ\$\#%fo!$e$G:pDDX- - ?:1l$b8VHLB%Wbt[/&V*\UA!;i"qafc]I(rE,`8%Ge]mVMFT//C3?FZQb32TO2"i.;Z+LD$&&P7WgATgrq - :9.8C*CUW[_1]0]DWrB^niO<0PTYd%aiP^fI%2/H3esLVa+DoQ_1taj9.>YRf5cC\T0e3o* - *77:C6W5M>O$ - O5Tl%d1!f#Is4FGXVpL/W^Abg\SbT+>dM"iaCSF/n$7:cgpiF0u5L0)AeUi"a?SJoGa\NNL - GBlcZp+#4XZ=V32:7LFb^Roa>]oo>/:bO`+bZd[_*Yk^L?`L:Hr&OdHg+0*#l]h%"`KMN29 - EZA`D6Qs`6/l&or@uJWM^1(K^Z;>.m'S7.:+%j@0 - 5@>ZW,/TG3rJti=V+Ar>FLB-1RD<94a%%.hmH05%sH+H`%rts00&==o)^hebT4G@3)gQ~> + Gb"0WH$!;8H2.E#,k(WEmu+jINgJDg*q*u>=t!U*"pcKN+d=O>";8GFVsnJK$3s0rQNOSuq + @2=KH0j1?*@FVA7,DIt9>"rC,[4Sc9=?CpZE.X8)iqcO>P?9gmdf-poDY93+'mftR$m->la + cWB`K-9:I]W/P^3nk`4_k'@h,I\Zj6P;]"F/kQio\*A[K!oBb5`[@<7e4cIXN6pJLV!7+Iu + A3G_X':88AnTStW@gdMdgnB.,<]ZpnK_)%$`oX\i2FL;F!_CD7PUTs+OF)MNlX-nKnU7Ub6 + n$4@uY;-8't_?*8D'Gd:6YQ3)iKnX.f/:b)s@Gr(2$QuQK=EoS;!_@)@(7q5(!7Dd>KkDeb + J@%dSi>Dt1+K\LCG_X':88AnTStW@gdMdgnB.,<]ZpnK_)%$`oX\i2FL;F!_CD7PUTs+OF) + MNlX-nGAmlsHdm1F.go_D;lAJ8=kl1Fl)ra%-PkKkDeb!8$Ralb6jaB)e@L6Zb%U@Gr(2$c + "WFgL`n_)g1AP8NHY`X8b;=TWmnoKa$D']PTHn$QF6LQ1;J002V\2ihE[H%#"lCRf./"^KK + 3fU[,t_^,K-lVn+eo$dVXO!3S>dFkWsO]b+BbPPDt1^blA*:;*d6bmnW + ZP*<@CrPr[cKnX.f/-+9AI(eXY7NL>B@;#!poq1_K'Gd:6YQ.pLBZlXP(&#Y\:E.=@hDnsA + LV+(MKkDeb!7r4E4S^%PD>mWqBIbM[/3H:.!P%:jf-]=cYg!YoSX,HUPa)ps(7q5(!*o-pW + tQ-qD"C8V*PkiL6"/^i$4FX,LGqpuYctrH8O6eD)%$be2+KQb:V=l)Dq]$tJ2MHit-nG@Bm!WN4s+P=PA0hD + 5KkDeb!-ddVq`_.!*kS\?@WKaXS!=[%(7q5(!8N_2Q=aQdh,m6'Icp,&ao9p,(7q5(!8N^G + D5M2ZO#=,:a@U7q"bhYC-nG@Bm!j)272+!<^LNGoAn5Js^:3Q(r5Y\=n-k!Si/[bS9Xb::K + =%YEcM96Lh2Bs=>7u?^!/6,>-nG@B*8!F^7\3Zn7FQmRHoi8rDF!.k)kt4@kmsR7jS!=[>5:M-"_D;lAJ3+7S2C[>Kjicj + L$QE(rm0dL$]9/*PGV-(oTWmnoKa$CppKsJYD"XM?D6^W,9+\l\mm#iKnX.f + /-'n,^M-$?aX`MdG:RK/P@U8J!U`GNkk_Qpn'/Q=a/6*1%#"lCNl)gV-el8n1Ai2ID,r)K> + [L'bpX[QX:C:\I0i;4@Rop.Rq*Ts+N%OL*91Y\0cH?"'4(;O#<8G9^>!H,A*i%r./: + U^RS<^]cgn0"*1^=m9R)f]J6+@h3O&1U>%ZN2\kC5^P722,IBT0%cT(a,C=h(`+t]@BMW>q(PN%NJmGu0YXhlMoQ7.P+soHFR)0(fjNW3%-,ra`mVR!N`(^ngH$h([T8 + Sh]c9)daa8m7U;:&7;b==T]E5s%CN:55iO1C!#O_12qOd,d>L_[$@CU8Pn^VBQ>+Z^KE[&5 + i.Ba.k?%kf=uq"3f^%CrUaTe#"VO+0!OuP+p=U4hWf`=5(:IA2FEN!X`S(3r,#*G9MgM5'! + *lc&SQQ`!]&H@kYC!U=iag?AVYW1(1E8\M@0*lGhqRm.7&j;Aqbu.GH>-Y9_q^7k%`i#t\L]a[l*#[]!)MGd3DU'&d?RF@0I;\DZkrU^MNS7]=B"DS0 + (C;7:Ol;Zff&81PAiJ.%CM1-XCN'[&3'P2:g-.k+*!9kG\?3h`bX>]=5k\>40j6e*u=D]Z$ + V\8m%tp>gun-nG?7*8N;,g")D1&G:dt'p4Gql/QYlO0'L&=YNWupqm=N(4ZdQY"q)8-Y9N. + 5SM(.$^(U9,)GG)/?SD'0:;qNSaVOg5#2+XcG)<,OE=\03;'Gc3t\N0Cg[,[HZ:fm]mmG0tH+r + Q`DPBmSL'3e$_b2(.)m?l0kMGU"N!1_cTIC;QtU((#hTVnqaRpm9'DIcmA&LnJ1J5c@%Sn/ + Ll>3V\$6*E.kZ'&SV\=0b*U^.,2$c&`iDr#&'MQ>0#]n!h),t6O^YQ139gG[M)'6V`(]7'[ + h,[KusISp.r.0>RI!cTW-gC0!*>a%)2iU^"?RbdY%'I@ZlDQ$j;h%AEOKa$Dar\p`Ze=g2P + X&mV[?EBqNg,u)&_D:MCTLBP1J5Z]]::!JY1cO^Ao:JN'.5%/nW*L(lM8B=5lG1uM(0tU:9 + _I7VWKOEeYG=.iIhjO`!f"PnE9cBI2MI,sc)I%)OV]Ms:?G5cWaDNl(#22c!B>48bEV(/OK + IA!FnO1o=GF@*fc)FjW`_n*&/5n1;hm8,:,W7N10Qh[6-pmFAMVWga;/S3GPTHlmC6OEKa$ + Diql`^d\cOWZ]l(Y`+[+Z[=SA\(T0GA<].]h]!1\B8eVVBpTMS+XG:cXkV6g%L.JC!/K#.Q + p\&4[^,Wkp^A;#9YJu2;s7fQTGNGS-n8Z\_\YQ2Q+aN!aA:OX1a?F0]IPcPNRf\OUa^,j!: + Js@#a(-j8gH"C6dA%tEdWokq#]N@af^L:qYklU8-U`g'Q^o=(J`[3e=94`p*bC[s5Pn]?-5 + *i%L!5dg\qP:%f#oTgNZ%0G1*Sb!nn_UXmCTFkTP!06.$/[^u;AMR)q?_PglRAXSG]9+\t33N,M,456&!t#FOOds'1(.8).oN.)TLf? + tWpK(64SfWKPu4%:ZU_?+[ef,gEuSs/$r[0imh + A41o3EAt2DerQd]R&Ac8e3dXF33m>o!fN1I+[+@S'l'hG#QkDA^l_p7*j?LDQ-r#_;INWWm + 0dL$]9,7]I;374.k)ktJ3_c#F2#U=^ED@_$^Cc3UihM&FZ6U[S&\mLpn2f";?MUVi5hK1o. + 7oV7Hp\@VpsYD8r@*t``tR8:chH%JAMA,A5CML(,NTq\uN4gQ*?DqO=2X[7rmLVCGEUX8Jh + %LV3i1c=Q&mFMN^-!2:hB(/;48#,PU"S(8Lc4FAbn4lLA=07I_=\Y$GA-SZ7dGYQ,Zr]7%- + ,gl>][WV7ee2+lFk)<>[G$T=!F3O_Dg%t0HU=<3i?1ZlY8T?e8Au(Q9T-u5jTB*[s;qS/!:;&qBc\n&&V=.)d,mAu+i + /bNf1G&h(2//g,TQJO'X\*/\]9-YN"bd-_Ock*l8:kclD8X.%!+A52c`gUVrpYZ$4FW]S*L + t1Un/4S9p9+RaZt%p>Hi,&jg0lQHfq$@qUO,o(9RW7Io0;Jp@^/IR((HDY20cGPPZjZN_9$ + @UN=u?kGe;..k_U]5FF;Z5PELHhfSf(K`tj9-Z=j\YWX97ZjVK\Whm&f.k+6\R?:r/.AHi% + 2m[;-3fnjQs3%ll#&kSe!*@:>-H)#^7Lt]PUORXJCYYY$NZ)i:NV/"WQoV0 + 0P!mi5CYhM_/Q-=m;j@%O@uMZ;/JKG9^>elM0(j&?_gbXK/I.G5h!dP.)(X`D'bA33a]PTu + DO6QTj]n5iTFY8WgbrCfT^$rFdONTl/DWP7);6Y&6l"X5=U,J.k$=fG+%LoN&."P#Wf6]i[ + iaS%iN5[WD;M%A$Oa_?+]"St,[0-;TnK<=]5&o40/dDYI417%/!uY"&"gr8o9.X%He$\0 + ^m_\JCClP&sR)"'e\ND-^L?;6k%ZoLdSX=<$R,s5mNDG@(\C@0$L3m^mNk-;X9f7m.<#Mao + 9HY+/j6'@t79\uOlTO50mRN0UO!a7U53?E9-9K.[=1e\_7W/h=0LG + )QHe[-8M>djYQ+`?mlRj,-I3D6Ru7,N8TF?+2UNMFpQWMrE7_h5n$ABD[F/AE#Om<].k+50 + TA!DVqY.^8F*>ZbV%B,%=:.?&!,t64I$(&sj^/-fh`4Js#H+?OU! + Osb#gJ!OI;^ZG46NHSn<`0?&&"L4\n+p`*$c#s&RklTB:VF,4Y5l49+['-2=>osFATX=KXH + K^qde\8k6X^!tg],98M^`aXph8!'pu&H"Ed]6&0HHM=jjL%r3^tCTGCKZ.(4ZJl[SF.G7u` + \.55a@M031sR7Wta>WK\HJ]rut'!c[)5lVfB);j1al:O`'cq?thF[oZHU.[6'.UgLNONrLl + 3YQ,Y&\7LQ:N;C6&8OFDPr\o:$HE5*6Ja=!e=s6%m]+(@K!c[)Ek/eCV8q#Vb7?qu>&(S?h + "Z#A-%#E`JeB6nqRDK#Ei6Z725HH,;7]VSrB^oOXL$OV-=S.LJMKEh6QRrmeJ-58T3mlru5 + ^gpF;=o1=o[_A:6aocXiO9p0c_(b7%mA[jggX"C'M!$ + !u2EC-remC8*OTC<,<9'N%TOs54_H;Xr9s3n/s^%m>u!c[)=.nOYd0:$&3ZQ0'D,97/&PWr + S.OqD?'DaN?eE=7I(i4tHa8O&6A7#%]PFp(>-6F'^JHPTY_DqVXS_?+]7k_?@a%_YaXU-@* + >.']$OK3Sh#@I.2VQ_]Q1DA*?b^k&9&5>(:<$s"J7#Tad_;D`mH-9t?KVQ^'%m9aT_#0UPM + roc]l7B\b9I8YSChm.50K`tj7-Z=i1@8FuFrR$Cn\sh8DMW6Id>l&^DI9<(i87;POg9Gk,7:FM55Q/ub@#J?Z7UIOf0,TC]fslaQ!j + K'WGEG=&UXN+joH=Vq9./&P6?nC)!jK(RcU*Q,Ss/%-UCZhpK`tk"fBreXKfq*&oLC"]!t# + FOeui9h=;821HKpPT^]tPhJ?@Whk?AY/;L7Pf5/7C;(4`OC46BZ[p4@D<++8YM-nG@&HZEp + 0>:&+t7j$Xk$4FXMDU/1rKtSa;oLC"]!t#FOa'Gc5$I;dH;-u?#-cl[[d%k&t/>Pm9gc_@XFV4IVL"+3@B! + SGQL2UeD)Fja_U6=9,er$JQhoqP+Sj!JL\bleq)-/IEGZ%/nQYpjg3_Y]XX&PiN4AXZuO[Io'5I[LR + /GS8;X*A[MHl"8kGn!SD_u[7SIU[Qm=HcTT&c'Gc5$pKsJYCgd]_V#K-Or'=`lfK-ZZ6F): + sOqIP3&+BgX"^pZ;`pq.d[TJ:McTT&c'Gc5$,Hm&![]h&5<.F<:rh-IqC`V=c2VGs5aq4N^ + #P`qg!jHfhIBm47*HOOOld%`0*u#K@X^q#AF]&G.cTT&c'Gc4aS@k[6Ke8L>qK!2:#DE=h, + q!Vf7:AtbJ,Qn_pk-EbYd.cr@Z/*pOad.J&+BgX"^kn3mNRS+Ss@%dEt@aFK`tjqBC8*E_N + mh:rOE1.K>I\oZ>5Lq@F%M`rOE1.K>I\oZ>919Mo&Lc:]7g/pk-EbYd/&r!u?TmPTK[B&+B + gX"^kp9cM860]cc/(k8rOl$4FYlSfRDR$@>X]l-DN.*u#K@Ps%/%>84M<]'@U)^`O7+J;:lfOW'C"VIo'5IGkU<[Sa@&k8rOl$4FYl_: + PSg'XgRgp#dCS%gi][b/#Z./.-`01A'`Ni"$/&^ri@qPERY9[f4VUHp_.d"Y_=i1J*6pITs+Nu]YA/IZ@sln&c+j*n-t'T@--mtn];R?\.$t2H@To + ^/-%1j?5])G@>e]arm1ap6X^!t3O3(0Z@slq+o4P:n-t'T@--VKU^MN9#3ud_^c)rCJ=tI]\P[4>84MDHKYl&^c)rC5k9*eQtK + :KMpb=LkQ/DkYQ0iq-g,r:_H'60rLjJkL;F!_?LE/]/IHhr]''Yf^c)rC5k8sQSRYOLC?_f + aoDmcj21PYTb,BgfIt^=&.fgb)"oE-8QFKCFl46%n2dO]$hjhjTf@L$g+taS`ge3$K_BQ*Y + =9(pm3P2)+o6?21ek1;fTa1@I_?*81A)6EK]4Y6Q[FnPoVa'eRhb[DTM7I_K@H"7FQ``9t8 + R).e#Pa%j!f3cRb0#o.e"!eQ=hV2i+*rHU-nKmqLodX.R$[iq=QashC67$C6P&YqK`uE3Fd + JD\]KOr5[Phoc/H)7IpkHWe\3m'3mJ3Muo/F1#l$?9lAEj[g^c)rC_!e>+>ZUdB,^8"s/0Y + u.6P&YqKa$sMNV0?q&'dAIhYjn&AF!#R^eYX[5YUk877E[nGgk4_nn#3-_uop*@=bf;=L\/ + !hO36.26''!Z>K_XLVa,Wc1rE;\i-cGV_T.B+Kb;!)k`,+!bin-jN/I">8><%bL0OCZ2m6V@ + =e(/h&_F1b!%'KfS^%5%YY'aS]?(&DE'8dY=JosJHc5aK`tB.jg0l'[Wo;P,'rKW/q$h]%( + (Q%!\'!i&qD_E$`'N^;iB9Q!@X_I5\a_g":u7](.]=J4V@o;a*H9;p.,0TTWg[R@#kF%3#! + ,63Ig`n<3o'r8/?m__MgVj5]BS&+Iq(/!QHpnC1Jk + 49!5f0/$Va,6<%M_?*$Oe?Hsi4VB!l,6@Rj_?,9c;ndoJ&jY`TYWr1Fe?/:jO2NaG8-$AuY + ^eY*8-#WG_?,&9,6=Sq$4CP:B90t7l)m6]J@%aR!T.1`?;^RE'MgAffcpO]UTf90r7N<78\ + 4-3('#$an4RW[ZJmejH$SYs*XNr[-nO;i8E8FqLZbs5!_=sY(@W/f,_.^?@0+\2eZV!FJ!Q + F + mgYcLEfcpNN%tt1WnU?P;Qr>bj4VC$T:ftN!%soRZDXS;l!;r=VR7dI55Z_lWS;^c^Id:4u*dr.og9l?AbimccA!X_-Fld`6hE(M>IIMjkSp4^ho + V[]JbgE&QlX*G5]gj8ml$?*NZUY'f0.C)WZEa]oD4:FjDiaeJ`s,;1gne;g9^QXW4W,,Ld% + GP4)s*7t!0fs]P)uUG+7.gMk#T*2*!:Wfu$[_0gK@-b_n\jpBo6Rc]*VrE.=&,)7Xd1R + )*=_G&/T\($jU1`gl>'FlG^$0cQ`amTYb!uj5WT9K@2g-9mb[oXGJ5ll6Q[4#E*tcpRK`Ys + CR5q;h;ao97fE28pROO`6N+[5i*_TK!1sD+Y`4WXF)pD50?P%`mM3sXP/;7ZNpZJ[,=?qRZ + fr2>BjKKI$d63>,B63LYt"l2%=p(s\T99-IX/r.Nt1R/0.f@bp%)^+B$?Xip"FPj6"VP&ZpS,Lsk;Tm+LNR*usdf+&5"Bl9Y(EHAXiu!#0 + )_[\d[0c[S,A3p4+tCTSVIoKCT/BkGZWp5O!L3HAZ8H4A]\]LUt[A:'YEp.,0sR8RIs?CC6`iG7I:]l[hH"G",T\$48arp!SZIJC\^_\(HN + @lbRXa-^;TN&,'t0f*9N))FKhM)!1_f#=`gmJm#Jm/MG4Du]K/@fP<4`XF]$].kK)CjGmLY + 2B6P\DtVrWe]"h6g^nZS^Y8g.siNA`8aL!lg&IoZ$uW2K3VdTlh#:X>a0.&DdEDEkAar%o9VT2bUEE99@7AEMd;+`NKs.2Z+<*]AOE#5d?lR)o4Q'j+BI?5Z&@h + +0)q%ZHa9q4G0a)67l(VSa#i6U!-aiqt2\$#2.D>3pqf@l*na1"h9o$Wdd&c`nj:tWY%,u6WX]L)pb + msNA:AjG]doo[lY5VF:+d;/Xj`]h*'I#gL*GL!R0?%;F/BjU7MgV=_:CV(2PT`%,Y&duE\E0k37i144EfZcJNKcqY"<]b%e1H\s1t*Lf-+GtS+42SLPHLtnLSaT7^p%u's.[IA1!^J5k't0j2im7Z&e#cf + ap\iZOZ#>5i`kHlVl#C4q\9UHkUe+iuIsnI1HR+Wpn65(Ls1SSs%_A+tIeUH;HFU=X3&>`) + bbNn`+9)%-CkJR2+4i0&dm$:9j8pZJ,qO(mAfA3Sc`/4oa2!L]>t[[^&C)J/Tehm;'p54Q + ?X\n+#%_HJM\L4CM4$?[ZRSPJ!4'+SJ>+:h*SZhQ_"VYWRR;kcY1iEFf*Bf"_-r_<:G"9> + og(A;7ldhQQiFR5>WgXEQ_q:S^#D^YUqWN5_h"`>"PQ*6[^4qB;)[Js&"D$9o+@1c&V)`-B + #%Fk$4VO5+Q#,%!n,-X7D9RgPrH?Cs3s,ZlC$UC/UYR%P<)7.ud=a7'Sbl@Dd'D]'0>7@"/'+p + 3R66\tQ>6Q`#c[D(JF_VYTXm[GPj%dL([ske%aZC-X:ZELp:QIRO6cTs+g%cPt?^=I_UGD6 + Ee'qgA\l*Dfjkf2mqLD_01uiMPs3g2B+S.?D=ie6XtDREbH'JNZ`aGI?=V!)abi71/gc0c` + uoVfR3kkp>^U+Fk.q@?DJBBX;0Q9e.:*"2H],(+J8IA#>`+:M,@Wf.UgP%0<7M$B#V)3pD] + :1G\a*/-$2W=JVNa(t6bUH_11Gm5H8?'LQ;kOrcP/qV"^8&6T!NU9I0g?adN>l*46cM$Ml< + E!:qa$@C'cCm45]p`e2V/+#8BJ.t7WOkRNebY45eq8`Wg(B\W8_BL#%1a^*cFWOl5?P`f`L + ;G-/4I#KIGTL4/,"*2s2%#96nWBW>H2$AABYQn8KGT>2>5=sKD+mjj)^!UZP6)"=$B'#tB? + %;98Y'dL9)F#D+H.^"da4c/F>/QDN"#<1a0aa(!()IX,!A),9P9RaM+73rr^-M<$6WS%/CN + )cN9Oda5HV68L9GC-@0)#eKge%L,.T5W%W0J9^XIYX!XK/@M_t0QEhCJImqtbt?2JIH+;?4 + Tn9JNtS&EYJ_<0b'[GSLpJrdbM;4gVOHa8PVR;9ReIe*UH3!p$Mqa>HDT,q.4rT47pE"j(t + kp+2YRDdE15Hata"V>2Y(:_nMP'B5W7Y^`"0581T!WVUQ#2`6,9TWg + [d&"1A^6ZYu_f@o\/!'rkYJOCdTrLL_%1[XooJ0:Mh5U*p[bXdrUBq8gYrOqo/)h9sV$RFq + MA%J$1ca^gRatL-Zh#MOo=hjp$(e% + YT`*^Pi&(iL\EH@k#+J"URo\\(5A@n2!I0L=!q(@cd2`pN0MD<1&&;*X(3&(2PMg`Z-1)!= + ^cN5GE8:oAoJVPrnUjO<:BUYq'nlh(n7O)ao8fNq*Wc=(6=BnU9JMBo*h?$E:UT?c!&%/N^ + `uW$\k;LLFkUm:J/9Ubi16\kOjcA7*<&Iq;#nCS'Gg2`$c_fmM4A^0>=Bf)Iin`1&VUBNoc + ]O?aiH19oCmm`9&EfdY)^S.,:]R^b@0%Io0Suf\ah8.>'6EQR9i(T,7P@7puE?4It[ + pGQ-oHL9qZJ)[^-J):N"M-OL,!$4HXkX2]68M + 4B'BBi$R=rJc1B>p]ZSV)'4C!eEUF!C&_\&ik"[GH`L^/b_Ym3[=U+8-f)""/I^[$i&sH@l + g#Gc7f3+W0>!\#>5V_VTT7<2K5#OB236#=opN(@]ub`cNqT:KC + N9S,Y[h`Q6oK-`TeMZFg8.",-pV\(W*J*r2QPDAW"jpI(R8!A(DS]L?60s#m"$9uS.-BC>c + pBTF@lX?]8F5Tdcl'GaN-fSZT)5LnN;_VXiLm;S@m!%t%NJ;U7aBl2k$^-,[@qIR*"O_r)? + I/qkq=?oMg&)@!8^[1E%@@K,-YMasqJ0PaDd..?M1/-IZ3P1j^KtW?)'nAFS$Rcp'&ip!hS + RID4mdELZ]Ge2I8PJG@G + h$E'"C7W4Uq4EXj0t*0"c6,dOkeEnT.EZO/UN;\/2bRNL`lcNARq$4Eg0$^8YWlD]ojKtSq + `\@27`U9^?=o/DV.&*ocg?@d2g@=]Otgk^"UudXgp$hbo3df^0'sAM="6lX4ia'P;7sU + 'hCh/('oMI9HrZ]QY3[-fUK$L??,u.guDTdC0.eH4O==ILVa*Yn:EC^:#*'uD;T[Vp85F$j + lH_[(VC[1%r+V#9-M5Xh&'%u0t'3-'b-gL@Ih@mj1I?5X$/.`ds)j>! + qSq$^j.F&0/o + P]\>@ai_aS044-nA:JYre@!gCO$?J;c4orO[NrIY#'*:nd%IU.TKEl>W/5m#l(9;L<7mZYC + 0+CNbRUWT[*o^X?S^r6r6u.B`?As4]to*Y1 + umEKZL`nErfEMOj/bnKEHo3K'"QirC-8&Im2]?_Z&-2#Poi$YGdSDqSNu&Mo#MrP\oEO:OH + W58muXQ?='>r5bE,G>CUl!ED]g\JH!X.4am49D/*7T)qehGe]WAI?;Vn6onOn+(PN"okS[k + c9\bi$]0JN_2r.SB;6fJ(hQ2<'4tNnYAr/6/JAMB(1r.$mhu)V=ortld>$mRs<(gm&q$Ggq + Ka!'BTo"-YcgRRadsGc.?ZWLIJJ6e9*gLQn$[YqNl-E/`hC'od9V44_:KCLc;NHaddSP;NS + uCXcP?]YTOc7YZT>8-;=U9K>,8q2KqpHW/<+R#bsPBQe$'Y#(h$O#.1'DHnBF$nI + kl($R%W(Q[S-ni:Jp%f-nGp??\uN]0dFShi`i\^ + G,];O7:Ge!F@7nV#:\"D$4?uA*u"C]o#beA6orhBD7/3[n7nf5rZDAJ6t(XsRq^lRkFNWpS + k/&2Q9J;>0E"0D["uR#!VZU.pFKS%;n\7;PolH3;:nbaU-D2"(/\0Vi4`maT(_QXY$G8,Sk + +WOb*Q4YB.(;_1A,9/E$ku#"CGdTp[$]2J9iC[MDVKD>OGH8*VF%oE$ku#"Q+G;pO)FV'@d + c\>j:r2(54n!IIq/$E$ku#"Q'P"pH8:c.dW_)3-N&*@>e]ar[\6@6t(Z)qi$AYk03(mhFHa + D/!nq;'\ab4oZ&D")[ZrUht-C4:3>iie^Us,4crK.f%E47mZM\>+/cnk-nGo:4\a^1=L^a` + OcG^oS8M3V1X]'KS-$0nYWsT^E3n"HIsIZ4+.^#?1uG%375@2ZS-$0nYWsTf13(W!^+i*jO + 3Z9]>&O"1m$N*5XZo[6>>\KF'`QV#4b*#MrRLf/MSdub>86e*FOQk\^o]"9&.24#\0jAMUO + !ncEB8<@7:B!6!<%`]gkrrMU%"d2D9T[hAnFM,c"co83`f=m^nS%:1.m%j!k4o.5ZeFMER1 + dnU#2CXePNm#m\AF8dS>:j!]NtY+=q,l\EBbSSZ^g>#bNOamkg*8%)2/72im1?S + ZYWc_of0OWn_38QpQn=gC+d0VWG#"Q0VB/6/>WK:Cl8q)\9p2J?=qOLedd + `3:\16n2V+m_lME#u8;-4r/-CVI-Xgd_"O/"3LNGFSlYk]^@Z?/tde-MfWdc54hckX^E@Mr + 2r6Vbds1)Y=+qNX^G$L)h%\mLUQe^Cut3n6k"CXTZM7[JUO],cS/6=dfeH[bnQWTnJFbDCG + @=qikUqUB.867:L-A!1^%f-Y^$niq?sSGpk8OJ'!.mWn&n<-Z8TZ\i8e>7A")[>+Of7^t9I + X!/GGQdIs-4/Z:V[PQ?-I;U8D/k + [Q.5CTH55e+3[pHHKLF\pDYM_5^g\!Uu#1.$D2,P^Q#9nA8o.'F=##bNa3hPYGX,/]VEVm0A>c9=Vg5,C5kka + HER3mu&#s9i7Ak3>=q%l=1-g8B,HrdmGH^U1@=dp9@,da[#8X=Pd7'Bs/@673"Z#@(:U3>\ + Y"H'i0r0mL0:,=.=AiE;I + qLjWu.d?4PYD'0>Dn*BYq/t?>YU932?bUZ%JEU<\q6aP,$brWONk + /j--V=.),IWNs3a8NFF_uPn/c^VElPo^Na;l"-pYmiVOnZm77+7dcV-Z/Hd[%kL@+*6Q!;V + Tf?Q2\]%OLQs2\ECaO*iL,J0:[E.R`0q2QTj\U*\*W6=>!HR0[IRnQGN]snjO5d4S2I"obr + .M,6*+M$!AgseOMc%PfZ1GGs/QNL9h[JK`sg)02:6.AkcY*)LA+N_D8_`fc[EHV`fE.+[$k + .p%[*pNV0>$/m2K@&q&,'&-TdM&MDVWR+r2@7R?.9*Cr,b@g3*\(us2s!*Wo]7JZkO8] + Q*6XdB>K1kqDIobWEh;2VWA][V`s_2RsJdSk]jX+eVOHI]OE<@&1T?#\7a*Wmcl7naG&[G/ + Z**AN)Unrn4,l%8nEgr]TsY,mF!MN_?-G%2&#`>G@*<%'RJiojV;qpo:!$`+9(cG=m>6+Y9 + e*_N\ml7YWt6qcGVm;kP%eKTm$i&O1,sq(T"b?n;#UJ8Jd/-/UV\g6gZAt?EOtX#aK$rgp; + *:0D,g$kOdW!(6c'u#--"!Gfl<5&iJcD>^a.\=@Pd!3goEX<'\K\p8dmWnTd60]'S< + ;FA`(/sjY$s%9I>j$LF`3Bb]gfQ0p@=dopeAOMBq90EC#^Ye,RDfqo2#kRZln[+L!#IL`X$ + R]gn,g%ue[H1oogl36h6q'0r + 4->JAGVM?5fSF5d+HR^XR#$AiE>JH6(qj2/2)i + \\q@e^3jOtpFfJQ$\ZZ;'DHnCHP=p;kd)_%UdTJ^EE2#oZMe/9n$eU?3p\XMJV + 6E4`/2hLoc[IcX]mr:+R5<)s[%7 + plAX&$u8k],Q+aP?u$9!mM(pfR)X*Q=YX@?o(jF"L+M9iqa;HP&@#+2ouVT-*["B0JYpC)s + 7HWp9HpB!pC/XI=@0X%BqBDVT?V6E4`F>NYTmFl-OLX.mqM]]0j["U854W-f53U,obY9hpM + Hblc?+[a9j!@<+JaiH'M0l*nl + (T4_58A%'?nl[T89a`G+;*5$9nA7DP1LYr,u4?A>_9fo+nq4YP/79/no\Ftc+QpQrb8`Wi`;oE9X,GRkG + 5S5:iNK!ml\m^9@Qrp=+VoF1Z4K'HQ@tbdEhX,r/H5.Cc?`>!#tA&QO6oOnu + ,@_?/]f8JCiGoV2Mr7uE>\MIffj3P/.oeBKQ\bW]V>g%t$OR8p_,D]K8_^H61lFcQ(;;id@Q + mjb=7sq9XYLdX0X%Bt"_>/3M^U+f"qR35BDM,Tg@I_ckrggS7eCEZY9'/)<$Appk4SN#M2h + 0o?Elh*JM:2Dih!'>)Q>kqPZ0RDd=^lCm[$]A[Uq:qp + dRQ\\8f]RO3.#&!qMQp+'G^teY8bsEh#"HnQ#ZJ35dm5!3h&(O@U,8A5kXQ@O0d9d@9uU`S + fGE)&hTi^5i@(,`'*/$GLdf^JP]X\aC8WAbNS/DQ$Rc)_'S@*0WtYL`mnF!WW#L_%#"idmu + L/toAL4Xksj[e1t8WgC3BPMXgD05:Wd`i\\sbS9@[B]$)=^ii(Z.McX6_F=RBN0'IDK\>E% + tWl)>_seCGn5a(;cu$eWW"H0R&eA5HANn9US6BB.")4]OmEq4ZIWU\f]Q;/]9G4!atKk-]$ + goLL5A+2?Z#@=_g5esHkemus0-EUdL?5+1usjg0lI#'b=`,"k'dWN&jB3m;BeQ3iQ&K"P?_ + 72-LC[Z?leBCb+9g63dIRMsc^^0K[ML,"6d0$tjM$O#43'DFW_fJ)\a& + hGE=:UM&"kG5S5N)EHhNTCct*F?N^Pm2`K7#NHE&8X^-h"+nQ2-kQl"O$V9F"PCVM%!>O;2 + U2^QPe^9Y@mFo;h%6hU9Hn5I4bH$M>4]6)55oa'[S+n`A5Zr + "ml&[F4FVdN_rn$W1HfK6eFPK6@,,t\*>Rt[i=],dTX=\@HW$K`/45c?B=8.XZ*G`GD`@Y3L]J[7]]B0X'G?0$tjM$O#!r'DJS6gK/`V"P9V91G$QCBc + 3QbWUQpQpeTp*qMWB?'G^tS*[^=Nrq@dSc]I(b.U?F:iBG*=ZKjhaLdR+JOX6bRgS[;>!Rd[r5VX>H9nOJpqGbC;5o,NL + ,g#q'BX?HFZB$RVDASk&H;DiEO"pe^q39.:+%n@05@FdnSW(Eofp\i=V+AcVsl>V;u:g)eQ + @$]L(SDjoZBK]XRb-iZC*:1hj]u]K6'W?Q Q Q Q showpage diff --git a/testfiles/cli_tests/testcases/export-area-drawing_expected.pdf b/testfiles/cli_tests/testcases/export-area-drawing_expected.pdf index 9662cf1f7cf6848de78bb7ec0c8a5091a8a07099..3a72577eee967d0a4458d27a6d552afa1ed02c8a 100644 Binary files a/testfiles/cli_tests/testcases/export-area-drawing_expected.pdf and b/testfiles/cli_tests/testcases/export-area-drawing_expected.pdf differ diff --git a/testfiles/cli_tests/testcases/export-area-drawing_expected.png b/testfiles/cli_tests/testcases/export-area-drawing_expected.png index 8c8ef625ee9e692b5252a1493c8442704aef0ad7..1e875ba897fc0f94f0380b74827e54be1787338f 100644 Binary files a/testfiles/cli_tests/testcases/export-area-drawing_expected.png and b/testfiles/cli_tests/testcases/export-area-drawing_expected.png differ diff --git a/testfiles/cli_tests/testcases/export-area-drawing_expected.ps b/testfiles/cli_tests/testcases/export-area-drawing_expected.ps index 130815e96cb86decac1605a2a0cf5b9c71d450e0..8551790606541fd7f07dd79789cce633f70fe74c 100644 --- a/testfiles/cli_tests/testcases/export-area-drawing_expected.ps +++ b/testfiles/cli_tests/testcases/export-area-drawing_expected.ps @@ -1,10 +1,10 @@ %!PS-Adobe-3.0 -%%Creator: cairo 1.16.0 (https://cairographics.org) -%%CreationDate: Thu Feb 27 23:52:48 2020 +%%Creator: cairo 1.18.0 (https://cairographics.org) +%%CreationDate: Tue May 7 07:52:49 2024 %%Pages: 1 %%DocumentData: Clean7Bit %%LanguageLevel: 3 -%%DocumentMedia: 87x72mm 248 206 0 () () +%%DocumentMedia: 87x73mm 248 206 0 () () %%BoundingBox: 0 0 248 206 %%EndComments %%BeginProlog @@ -103,7 +103,7 @@ %%EndSetup %%Page: 1 1 %%BeginPageSetup -%%PageMedia: 87x72mm +%%PageMedia: 87x73mm %%PageBoundingBox: 0 0 248 206 248 206 cairo_set_page_size %%EndPageSetup @@ -160,319 +160,320 @@ q /ImageMatrix [ 630 0 0 -630 0 630 ] >> cairo_image - Gb"0WGFVoNIH^]DMs@>:hKuG,0.X(r,f,]L#qUH]rg8Q\:I]joW(", - UT5GOfW;#Y*)i!>1tE8h,\O.4@G&jg$c)%$beRNlaq3MXV8gH@>\(UFekStW@gX? - $.>Co=LtPM<#'4hP%tkD9FtKkDeb!,IV_T%-_@a8<&7bW2mqr'uX$_SS)C(4\9_G0atVltr - 63+5F1fr249KKnX.f/-'IGmJ?4e7NHbiNqa.9"ptaa:fm_K*.NCV]nLU!fK!g;d=C*N6t,$ - l$4FY%u=\1nfi>Dt1^ - m\2+-c]BBi2i`IYj`'T"^oUP>:.?sfTqd:L\ArYrL#)c4@H":H/kFSG,bE^)@6Kk - Deb!,G>JSmYCI?`g+`OtH5\;cn:!_?-t(bbjoBKtY0_@`tR;K*!PP-nIJJ@0'J?SC\OS2iU - &+DkYKA1PLNq-nG?[p)IAsZEPp.?:dFki>Dt1^lH)uZE4i7N5$kl-nIJJ@0,#cp[E3-Klrp - @k0lp0$4G0V=9*O241*4@@F%>C;XE;Uf]sWK_SS)C(4[Dc#;$bc[S>cm^$"cT$470e:fm^@ - j;_9:^T^SUG0f_s,mB!VB.,;248/,Mi_a,)-['aBP(LQf1DCUT*W'mnHWSiU^KO46eG>5#@ - Gr(2$c%c)q\FH\fJC!<;G9'SfCDN@$4G0V=9*M(mIRon/WRJ$a5W.S@Gr(2$c%dTHPZBT=N - Bs`Xf\ClYj`'T"^oBEpZD?R=N@CXPP6*'n>$$k$dVXO!-T?.GFkF\Fe%<)@3l)2KkDeb!': - \(qiK-X]ku5CVsJ;MC^LQr:fm^@*.DP'5[gGCXJDMnn-k!S@)nD=k"$6$/iB>:/JP=H_m/9juuu$4FY0UMnGD/aSnh8rA6-Y,%_pn-k!S@)oP.ZOE:I;%Knf - fctF]NW7%rYj`'T"^oBU2eEuXYYf%>f3S"?(qYnn^2nUj.k)kt4@G?o*NU5oS'DCV-=@keei>Dt1^lB'iV\ - qnleuoJIE,&U[qq6tqR8)]P^[k8@5;n4LB.,;24&5.0RV;\@p"7j.[]7d3Sc?CZc5t_'F3h - ,gIk46$i>Dt1^lFW5V^*1p4?_i`_E)FSVgLY]DIairP(LQf1DCUT*N-=l>Z`AKpFWJA;.g[ - /@Dt1^lJ.rS![mRcKX:B$gXbB=k6YPSCfi=Dfd9/K - nX.f/-%b,Dk<&(k)/f8GREce8rBa[=&MjsG_X':YiGAQnVK0,=:^CtYhlLX\6Q=EH4D0&;- - 3gA(^/,5B)I@"=\3U9pTY[jgeHh4jtDl=`CNT8(4ZR8Nik(7>H&[6:gq%A8TE_9m,fYo\eo - Tf=9'-'2(Iqr4ij;#/Lr7WCp0W_IV3t*h)aegN+>7`!EcT#8@Ch60'WILL%T+*7DY!U2_.s - %O(BRG$4FY$I>=+-'Wk('2_Jpia0O8LA]Mq6j@&PjFsgeA0,1E.c"/YTW,E&qc'AkWW#Sl - _rG&cfu*c,?qZnErD!JdSh(C7:?4!Y^c[^K7mOX]keG7dB(eiojGCoc.b"I\h%'3:a;ba/- - $(Ok:ACPf2^YU@UZ$68V,fmYQ-46Y]$4$]IJ%7gP*?.]'a<_-,i/N;BSgN(4ZPnpF\/@A/3bSVFRRH9#4?n+)^m6;$]_k_f-!u!\s#@4FYr<*p/b/)5.UYR. - k0s1'Gc3&8)B\E:oKkpUMkk"'jXCN?4X=\ft8mMDHpF!4in:0'Gc3&8)KhROJb.q_9^!,W6 - i6F7b1FcC7b&O7fZCg!#?R14j+sA%D'7QKtS4,8Wh%c$@%TJ!jLV8)`%r'e>P/l$@=;7P+k - d6Zl@\>Dbb/J@3l(\;-3f6*8. - jSM"$s>LA]MA_'p\C`6Z+G-U[U7hAL!!#?R33R&[9:>fXjgEO4l_XbaQXJ>iM"^o<+*@?IT - DelCWh9#LcMW/iX2qG*r*qBk1@0'JLEr4?S"-f_(LkkXu%D(@UlROE-:;0l=;L6VU$AAXk_ - 9$Om'!749bpZoK]3RRI5kY^>2]rrrtQef`SM6o')CLSH;FHtapF3XM=9*7+3OZdI0r93#GWOmJd7T:n\olF9J9NCm - !4VYA2(@m;4\35T$#.nW7=(?Wa;4-0<*-J"3X"pXKa$DchBU\XEOi"QMFTI"&J%o?jDH#g] - l]Za$fD0q$c&mXLR/sEV+_C20;]Uf;BS.;(4]Wd%P9JMasGk>]TpL:FdD]c'W(\=ZB?EB:f - m^8jW%N?En.&aMMKUh031sbm[@C1A$pCp?jhYli)\-S,mH6;XS[fTdNpPI;WM\c^e0Jp6"' - f([=A3cK9T@!hVsB60;?pc?PVosQB`0+K#.S.COaVRK8s3a]u9e]M\dlm/f^>9gr.7=TgiG - k\922O8RX,l:[p2DjN1#OG?@EKC(qG6>6jmT1rtGnh'\d_]0'7gEO0\^F=CUP_?+]Bi1:)V - mC9%*M)WhnLebR\#M>#PmcO&k8sW)i!X:&(!4NHO2r>_tf6(0b+F:tiPnYV.h27"9!st[*! - 8KX-mQ?,4gsB?F;s55T<`51KS^Gs&a0HjT@0*l\pmm0KH;UHD`&m1^HJSS%*aq"5XJ?+:$4 - FYO,B(mcLdeH6/07*\21)Tk@`sGiZ>AcK+A>!I_dVMNgPbo]l#JE5!M'Zlih^"h8gAF26EQqNH@\[+;>T - A>K:p51oNcoDC8aRT_W=\02j'Gc4'a'YHId"Q)&0&AZ= - d3f+MN3&f@kZh.d":Ttfi7@GMV]->d\XF0bSY0_gQ#D!q7HrZsbmiJ*'H@f&J6OCE:,BtBG - iQ_"k]YNY;OlQ#V>`M+.0>+Jc(Gd%eRl(+@dWf0!)3,T@'u^o^O>-RU - tQ$h['\W!,ndmSISD-,mDRV`*,/Pn$O2U9FXQH@O,V6mr";4[Qep!S!o$MY0fXOj2cAZ*rY - #l.2XW$4FYOe#t'&fQ2AKGg:miU=77I=@l@iZpZr6.>YhBE9m/Y$&Qt*,!AW4AO.);8R_F) - =]SEs=9,%9B%-NCVgYfk.UKB1X3mL0ok:t"/_MqTWICC[G>\[""lP]_W%Z\R`=j#7?EBqNg - c>1B'i,10YQ.!tcdFCkEm\5n=Zj=,:,/^$J5>;1 - <7d,dK_I6-5,'!cV`>iT1:(rLt>USulsgY,&<"Uk&%sZ^Mh98O6bc"Ot.Qb7&!0RI2`bXO) - g,U_mf)ZKL)7g/,+F\jXYt_?+[&kFbCTbB71^@"hIAc7%(-lp`m_gVfQ(/#6[>PUJ'_=)nd - %243ZqFW^-WKiCIpoF4KUjJ>R-nG@"p?u?t0[_InDt-e\RR\kW,0ep?6t$*! - `VuCH#/g@rmNipRYcHZIPE^I-=@mN+N@FNZ<_K - >?UaRboK$4FWs - 8)KhR0W=;[Hgq*4!fpA[goOa0*Nr;4YQ.og[X4=VP]"k8`9WU2,!,+c*agq4\XUWH.p4@XY - Mc1[!*hPX`_;!sm9EkPV^Jr)nDF-Fl\O%u_F@O4IbhAK#sHn - 7p!!I3W!ldH:iF$up_(FKC:lh4ua#eQ3NgbkLb'GM=5(Ja=eAh]Nh;-5!5XJFK(Tt2dZ3bJ - I]@0*lY@`Qtd#BHsg.it//`_$RF$QoRIDP**]-'')lSJ\:(XWJ^u%9) - \G4>Y.4i!OuSmE_H$9nW_rtRWWqU*c*d@[oZI48thLa9b$EkT3nH[ql^\LJ?_7-=.&p1TcP - cA&[][Z8d\)-R0DU;o2J*%R``Ld-$s7bDSH&c4+Gr^b7k_BZ+WV\]q>U%(Bh!Kf3RuaYGA[ - IE-@DHUi?cFl6GZ$*6o)-q88 - =0%4ES3J\XDp&7OoV8Vjgt6ga\\X4Ve,@8?JN,OON++s"DIi@J9rtH&iC]!K`nDpRQ_0^jm - ]F!BEDos>DX9Hlb[A4mdf%,%JbVZU>+#UG.@b0=q.RdAZ#8s:P,3#&Y#8@%f&[lVC+rr:F^ - OZR+Al.M]CdNT"El([,dO,HC=Ul47E+_a4T:_b!cTgU^_2:A;ka)EpAtP6Ff0UMg2:GlKK0 - j*i^n"7gp$dtL#l)($aqFq$^AL0Y\X:<`h)!V9*&PI3rQ]4A2gf>>:/.8\D5]ZeR*gLVJp1b%u6m`!.8`nha2t; - ;lP,"B*'\kdClV/WVk8Y87[@N@K"`cCiX-q/;R"1'4HQ - 6R>_hJi@4p!gWq]AraR%>=rs":+\F2jc_ZF>r_N^l%>JMNN)"FRIDQcBY:(YVt%61]*IIK` - r"jLYeM50R91(+7V2'Q6r/"ik\4okGluVXHAKb"R+@\*c2a/N%CD0$CuB'OliOV%==V5[YUXCQ?6l - \Z%ZJ[f@I`d-]-CiHA."Y]E_8Vi?LMciI`)\Mc - 1VclM9A>*a,ZCf'(BkSbAO;!=B4MKk%`,(lY4:SKU2pi=@SB;?TCj*`oFu?I?IO*ct`iKa$ - DOn^gL,T%U3o`@!lt'4"0a6p-:6jQk20omlA>C[@BBKa$DOnX\,;B%1bt9c?o[o5FQ4mNWc - C2i@73$JkT1Q`KjL@0*lW@Y[N``JAMA,iT.Er#1j%eMct$c%Hp> - :1dZapRbhnKAEGcF)U74]&ek/XNO4q3I!l'Gc3,Vm[k`)n&S('VFk#Jh+F!86'a(fFg?cq/ - $j`C(R$)#`*5djkEB7k?#8Nh*8&\g"8W"!P!)pF2EV_d_j$/4+bA\OE> - s9nh.!jhm0M@_?+]8oVR9`G[7D+M,/6sMY35K/`3kEVRm0'2PI>T>W@rBUVr[*.R)8m4;Ls - q[!^%D5YiK!YQ.Aa3OZe4%$p1pntQ_HFBs)`Fa\pJ"'R?ZrVi+B/JJtNWI9Pf)&2e-mO(6q&-b(8(XJF7G`'?>0U6]uWB - jt9F^Osl#P`hd!\fm\@#70X0C(gmUCZhpK`tk!s8KtAKrr_8j[UEN!t#FOeG^GO_HtX'j@: - n0J(E+!r8p?UDI=MhV/-&TRpFJ!Hlo!TnIe3Y)TWeD - i*I;Y<6"-t_Y4A=un,\4H@-Z+No*btQ6[+Y7I=MhV/-&V(hg*;9YVeI7Ie3Y)TWeDi*I3"7 - 6"+POBDKp[pjg3_0QlHY3.3Kl%+=j0JcQ-nG?sa**jb[S^\[O$:>a'Gc4uj0-c_-u?4bQQLX+%k&t/;o - `7;DG\uA:6QcJJ.F%Z!)k7g:,=<"r7hDP6"'e]3k8a8U3Y#6A*)kpi"$/&^spKn;\CZu^)e - L`pAf\>=9,3nD+hCC"a[QRg]RI?@0)`V[<6MaKe:e!jl[]C%gi][eGEGs-/GtVVh`IA4hr-R(4`C5RjTIbfHNIPIdR79TWeDiS_5h`dD?[bU%378n-=XN?n0R: - 3_K'Qq7-b*K>I[DZKLEYK>O53KB22:i"$/&^rY@Wf`m/DTEiWc4hr-R(4_O\LR/qop7`TN5 - PG/-:fm]ep)R=K:tRPZ?[hDDrh-IqN8ts8.;ZQ_.mNb.Io'5I7WJti2(7g&h+)q^!WJar!0 - $:%^VleL'tUmPg]RI?@0%4uTI[DEa[cKK>LsF4ltiJi"$/&^rV)?$Om - GIWI)_44hr-R(4_O"HPRG4[Rtb1T6l)Q-nG?CLYeM=:tY*FX8L`Tr'=`l(ch6V4G1N-cs>_ - q*oIP9$c'iWG98RgCphQOT6l)Q-nG?CLYnM8:tQ]B=aoc>rh-IqN#[+YN^7)hFXR!q#P`qg - !\eZEW3CEUKDaL,r'=`l(chHTCkKUUlh9mj&+BgX"^pEDmTBaJD:%\2k8rOl$4FYjYr?85' - XgkOb0N5d*u#K@OlO&c(_jDMB?8C^^`O7+JnBbYDs5Y7"4>#^7hb2'"@&f$A63]lG<$Y - &+C$^"d$7-7YSOK#K[K>^c)rC5iVJYaYkR676NnbkQ/DkYQ,=tI\ - :'-u)&2e-mO(6k!8gUT!/\lZ\C@1UjgqYnci\Y_@0%W+kJ.^__H,kanj=tI\Gc:@)\ - i"'mNk*i!8gUT!/]I&ZcJfD=*7=7Ta1@I_?*9`JUIn8$@m$4@trLYag9luh,aIcpgHl)t$^hl\sUrsG^MT]b?6+1"KDa'uHp_@m$4Ge$eR!Orll/mi[^AOlVo]H4^c)rC^ooUSXtN!o06`WA9@m - 58'+aRGK`r$1j5TY-mB40^e$_A%7Xj=tI\O&3b%n"]GOb/Bn(R%dVh`m/D??el_?-^]r>pg - (jm'*PFK\nr!fIa\@=_BhCQCk&MY5>B[S.fT;-4qE75e0^0$\miX#laiq0G`"mOg``!'q;7 - 828]i8DL7F%q^5'5[f5n&S^+[`?'jt($JLRFBA:["p=s\"]BtsHR)d#e-"?]#lFm\"d5G.H - R'SbH=W$'/3hL35bYDI;S,f7]Q!%En(O52a-Gt>X@!-YK>I[]MEMT&0FA15UDk#8ZUY'.7U - ^l/dSR9IFRMn!04!6U!Q9]/!_FWJ7j;lp"Rk'e*XNqi'Ga4Hm#_8LLVa*9kK=p'dF[%r!@3 - Q>!b'(i#p?`+/.bSCOT9un_?(l[I4\_X8.\+,6jGZD"LQ2"jH?abdk9EnDT;,Voc88 - FU8Q7&B<#5@f;/7:?5SYt"jd#o"0EP:q8.Y3BipZNU>_"oNE4lH41[Vg<[RarUj35U\2A^# - l3bjep`U!MgP>$[@Un&jV>KY^gAT)qL>rSa5NI$aW>)#VHGlG_8-l5cUT`J8G`QCJ+O`6QLrlsel!5aZ2!XW=s"G"!Y=+:db#!0AlA$abaR&c`>;=PuKt,6>Rh$4GW"HR&U$7:Cc)`Z>EdG#6gn)B(M=] - XXu7@)[s9b75Loin1YS_2]Q-[r.c2lDq635PicdJ,"e5NdpuIBeB@S?i4Ano,hr9Z`;)V^GOFE/2qEldj.L6HjXU[]:@e&*Ps#aFW^uNo2E,P&.9ZK?gC&N-e(NXUhUFdioC: - -h1\.fkL9+b>Dr/,JUITOreh'g'4WQSZ%iWr;lI;u[QhULXh]MXHcOPHp`NfI?Io$'1J!mQ - eo0Z9kCMMFWs08@Hp.,1f/7f08p$*IYEmhM6r78>(54QRqN>qc@T%uO[/&Q6iXdktGgGqLQ - UQ5n4qWX-f:[@r&c;2aYYGZd#,B6l\@?1PQ>*?7sV55]OCmk%bDbp;p7gDhZeu`H&FQmVQ4 - _\VFaH2Etf(=CY\^CHGs33ZHA8-JjHLX5_,etH-!+&6S_E+eZEFRU^IPg)=(IgJ&Vu?L:OR - Aea-SmPqe>U[<5OZcKO)qIFSNhZr7/l(4\?JI)J;6Hqf^n9-3a$b)l&gl>okN)29fdKL38M - /WY$BZ#$1hgZL$r7_7V9I/8)hQ/rr4\oZ'1f - =](=0[B7.=Yh+4I0'WK(OT9?)+:DC[^A?A]B0Yq)YQ4i(iY,5W==:17s7r/qIs=dL_O@/t=/Ga - B2+":!8ki?$G18lO/WO#.eB4^#7'6F\s0s?Ns1@dPs7tp,s4aeN%9BL,;BbjPH.K(bhu<>f - _SR&ITet+m];B]-9M[QJ.=6F0KLj?>J-lK-*m&5mR",O86jbPjkmBE_SUJ) - :S(hbbVLJjpMTHoh!Z2qelrshHe0(0;s9:G8$Bl2+SqMp.&Hk?,BnT\>-XgJ:OY(Bq/&_dY - ;`L_(;2TMdnA:#H6@P[!$oQ^@;k"-s+;Ce?b!>fC*;V - _u]PB;#=hkn-WKi(bB*rE?8,"qp;$6XFq(\[F]e_g/A\:4C(q4ol@W^mVC3'#Wo$Yk+TESgS[:+`3A7\LL'&MWb@70HR- - U;dCjF;kmG4l=G:U0^4"fuIXDr-L2UFqQ7MW@dYU8JQ_HEN:\SD'@nUIZY%R^e,LK9crD5:mli4NNH.?&hkM0=C#n.H?=n*-LMoWd++Rfm#B(Ok4q* - WcWI\G9VRuo74^GfJ.mLm-^1[CWGZ+XIkX##NGKCkg&6tb3mBob'Gfpn`f(TgJ*f)`=m'`DA*-tVr@lIG;ahjJlTpLe9FYX\a[;MZ*sU8$ - XCs7B6XRJ+AP3[4DpOQ>''O*a$de9m`E6qs6+Fm%'G54"CkX%1\)cLUEc!3)X'qX/RBgT+C - /4+f:OH@*jLddnR[pi:J/V7hrU="ijIiu^d;e5l96!:ZJ[n^p&%W46K9B0Q7fEqDFW6^3k" - R@3d=&+hjNmpk_H27c;PKDaI55Y?$'fIkS?['JEtO"3(_=ZJXVB"[mdI8Bgi6rd797H"E$>J3\q*Yha"DN;RC3f''>Sr,`$fA5]?,NINc)"=32Z:=s]Yd@]g:=4j>d8no - jKMo^QbHl54eQpGC(,Jaj-p].=3G/=ETbrNl..#l - sDS'_\kF@Ge'gHQrV/D;2\Z8!l7P5!J\*nVo(>i!kHSi'qd7ci.fdp1(4l+P;N& - fO5g:4LcLNp6!5q`:"P.4M)Q:kTT5JE@T1cr0!2MR]M]hnc;V?fN_TU/"s5T:mo&%l60`WY - \_BScHE%XbJg%h1aWp8K@6\4KHt6AT0EQeT[XII_,VO.23Pko5aK&!L_*g4T,c-+q)15$B&Hf-'N\JSm8IfH/K\ - C.jus3fN4=VV\2-7b>sV;OFC[@8-0J@amJCdbu'=hO"=jj0`]fR/5j;fV:&=0kHSk&\eorZ - 'G`.WObCKgo@/($+gM*,;-9WSO!OYI]4ZRoqAfnc]I5=/asWEgY7`nRpo*hS'L52d=JVB]. - b-g2FM@^oddL*7@74OdJt>f=Z\ndYV5`oH.K*9N&3:L@WTbk.\uPOd'ArJYV:PX+@Ig9ggF.$6:Cl+-nGqY,: - `]aa?gX;Ds@!RJ0:Mh5Wf\Trm(Ok>l&jX+:'AH&T.AFl>j - =mr8>*-nLIYQ]rDii]O8So&Ul7sTs0&oT3&%FMW]*-;Wl_!!Mfu.$Wupb($ - BjaZ1PXLJ3PG5@*JE;U6h,`Wo?&+1Y%o^9>CE+G`'?>G!-`3%Z9/3-iN`Di$f!@=LV8KH+&/hH3+*9%YY( - fHV(3N.\8G1W+u6aoFM#\&IJnjiKGb:o63u&%flYOTs0&;VQ4r:YHFnba3o#/J/9Ubi/q[4 - ?)@>g#GgM_!&%/N^cN(-GYYkEFm=>%UB#ge'Gg2`91nkTnXL+@aGf=>q1/O]+pn^&l9d%mP - Er(pp:l?Hl2J*%PPeM_?j25f]J`*qF-b.s3hs_U$MDK`\kdrr00^NY\3XO(YC0HNF;<-tO1 - uUa'%n$hH<@$3fq^Fb#6b)0'Gg2SC_b9!1o'Z5Kl'haLKreJtLq:E - A$HJce)o(8<-H9en+_ECa]3)E_m+3RVGB@b!r%k;Cr!^3PgpZ%hn+Qt4)i/]NAeOd`$IdlM - f@RdfF=ZrKGpMS@FjN6"'edX]=n]%kR23^9Dh<'6Qb*0i(8.g) - L'9Pf:_Ph@inc]/R(/!q65E"oPi9`H*+#?r-*BD7q=^Jf`#($G]j[(@^4>ESXTaeL>qH^Q7 - n5Z7YY?BHH@>G_O!9,2uhso\:hkHrioahMM/3P@kQ=p - S@0%hI4`KS6IW9n1Kl,5C]H[2;+$QV,9MnnWb3,!];_cV*E?5!2Co`j,qQRU60L9+J6/ts- - k83$`Y^OBbQ3[99kmm8_$O@+I$OHT=(!l^.`ZF,nD8eY85eJKr+DZdjH+IUa+8J]>mrsWob - :A)[-nHM,%ob[Sn,I.hff,da=7ol^cj+e<$4FrORSm9McmQ@8;mS@mI*GY]$s/ - !/g?/.`@gWd)f"IP5aPQBeK_6 - \?B%!EIV&ZOUTd"VHpGY5)&[.T0!2E#fbR&R - 8X>Xg@)D-P-rF"TiA-MKqDbnKB*1D:Pr)6KE\]jgqYnn-!3pYWr3eSr.gip[?Ja]KnH]/aB - LBnp,N"3B^VrGQKTs=ENrC[C=cE#/jB$l0U1i]>m]IYn1;6=G - Ef>HD;*c[9?7GTts">7#FPpQV;E^k5Hc+NQaH@\,#I\r-70]s=DS%16=>#Kon?i'AbB&7f. - .]fn/&9nhS2Z#.+%I[NK1oCU[k:6Qcq^jT'^+Q3*qQtQ$QLVskj;H*NTZcD%Tl;$o&'Y#e' - $O#6)(%ZhGX(0']l(6SVV[D6aIZOX*7:Catqk/glQ7_DShFI=_:X1aECp>$.5=gA&U9HnuI - 9slTSN:?3f<5udUeB7)Z4./1X2d*`FST&Hgl'#N_:M]+4T"h((/L`!OerS"&%nso=n8m5\/ - 0i&Jo?g0H2crEVMHVJ4gRZ7Q0Z0TZP9ncKa!'HNJ`"QG[d%b+5NtWZbW6HdA:fnk8?AuRA+tG1=\TrgP,2J,m1jc%SFkp&HS-!o - .YWsW/<\[C!IcqlZj!`_Be"EpVH@YoQ"n"]cJP_0_AuD:"ClW`^h7HjpaIobkHBe=e"n"]c - JP]IO@\.LcA"7cch5e/rb3YnU#2N%'U&oP5YWsW7To+-pi"$$LpX+kMm?7qc>G\sT:K67I' - G^t.VcCqsDHrKP*3H_qBT>Ae0Bb^m',A"jK`sg%AYn+][SM3q>'9L!nNONcDXu6))[SD%.+0%RV$4DO+C_cR>gV:uIn"UVOY>7&)lkS>LI]* - =M-nGoFlVu+"c#8UYO/!GVN]goLfMXk#I]*=M-nGoFJ#$PF:ObDC[M(`?>Zbl?fK;;TU=82 - d.aWda!b(Vs.csmEZB'5MX_=_AQb1B#8Tlu&Zi"&3>#8P3-ltl/R[_mkDDq;=p4huZ5,]&_=* - ;gor7X';5`[t,7hk9_-$i+]*dgfq.+eDIFRMiXo:#)j?$EF%?#"M[HJP:bc7W$t+@I!GR-0 - 5hS08qD>>!bA@8PTL\tAc!7d)g[F@p<;HFpAngJK$jrU-;?*uY.=Zr+e)YIp#,:Y9B$-$Q' - !E-&0>`5aaPV+G;\=/iY!g?L?'Ip%56!/T*6.d$QF-\dGDd?^8'=.2jl7\(tlZk4&cSQ;hI - >0e^Q"[_@t<6V[\9jdEG=qfSV[q10-A@n_NM9g$l7ChjP7:%Do&2ITm1!c@M4lje"chX)6# - *\GK*[!YU&ILI=(Q\,Z@=do^VqOe%"14Gst\3&KAt\=M6(`+M%2bP;=clb$+t+FGN:R-3,,A`?k"kZ[LSKAi- - "a5#ZGG.=06\td7+&el*P*ck?*o>V^W`3X,Sj3=uDo?A/Kikla%e"ec,ogg1q8n!;?=fPr& - TDir@GS]Zrgbmbm^0T."`90.4VGn)r-a+4"#bSLSk5##NVi!F\Y-MPY%H^lO*[3eo&L`ug+ - 5cW]@=don7)_:Gr8r7o-JEV&ic9\IQE/';IJOeD8@=f-,q(=+$pL\#T!*NQN-H$'C2-k;]? - 0,*Fu?k;pg?T'2!B,YH8L*0loF'^:H&3c - /UR-/O_)^-7jlcN#J1Dk+#HC4JPLU`7gJBM!UD4TTUoIj)j/.aRdU10]ASerdq7-eS?Y:uH - *brS4>_F%.bmd;j6"ktC^hH\h+3c&\=n,0rdDlgZU\^GathRKf&WbBN)JjgDe(6kG]=*A]R - q#drqpWb==BDT0p;u_%#?cZ"O!:frfMKtBL&[AG^bo:[^RsYos6['+anrQbI41JNJiEWimQ - Y2f%DQDhG#(ZJs:;o=;rpV'#+F9_lcO>F4GtI`DTCWWACh(#oebhig?F1C^#aN`:R-0T%#) - LmX$Wlm7)GJ5YHA)'lLPShkfO\C8kKIu@U8>(ZW-hI]4<$3N'87H6TXLDsIOY;0bl['-I#=;iU5319Yu,9t4?6de/N - a_?-G%'c$Fo)J!RpX]Vl'kc[>Mb;.mII/oe"(WJst1K`sg),o-$qTANR0Kc[3U - ]@^Lp(?eWT@it]+/sdF;19[%)5@j!n94@L+=N_'FbZVpN/+9(c`2;QMg?LtX#CoCWLn)[/MJP^Vu`B - 4Yqgdf#0hRRlUNksd=Y6Tp1Is@tgGK=j=>b\*CL"2M?K`sg)9.K(,[k9\UXjc95a\mcMRA\ - *p,eG>QXOGuYQ`.u2@/K%X@=_7#7`@L)pNt#mEM>n^c*_0To;:q(*WgTI]H6,hYGM,.7O#9 - )I\j863;o8&Gs:kojP63VlKfHg$qBFqh+Kj5n:L7Men&.qG"ueP?g#7@Q,3Trnt_.g*d]FE - 2uhe"dd`)$VJ(f-.c(5/kC0s0mcO$tQ:k%V+T.g-1!ca^SV0eB]@2atb=r2?*ZNMki^JAdN - B4D]ol>nhU*ZDE<[fR2.\BTncVmUci=jl4eC:]Td:ZmsE3=E'UB"LB3iuKpFd^jeUN=+rX\ - r7D<'I!bShGJ"P^!>E.AXZ2p3Kq/.d8/P]8KZY - DPo2Jfgp6T1(cL7B:0P3!'!TLdX6ED`)\9M\Imk7NF$\V+_eb87[0jQnh^8X6,fO3.ild=_ - 65XNV9?^H%$]6fFUb9\Pf&:PX!gLp$6NoK$h9geRlZ[#cQ4,?r?EOqjU\BJaFQ3m4C<_L(n - .4c#O?W/]*o]b2M_rmqLSWaIODPcbM*Y--+c>naL4tN%nYX$9X;8kZNftGA*kGPU1Et(TVW - ?*VI%`B'dZ!?;Zqt&oUT+i%HO2k8EPOsoC^WZ;uAL;nDW5h;JRWq-F[YoH^bIj.RkM("`$4 - DOB#?B">r:A%FpA[l^FmI7`DlrFQ?4aK;;YP-uf7NCRS - e>15RVcM'%ld'B&RD[Q99=uU]b@TMMAR<]s0u\E0LqbJi/.a.8="7L`I0L2J+?`V5'YcH[? - kesn^>_GpSXeS_>imn^bCmImX2JA,'G^tBc`3d-^C00: - 01tI[Tu3Ohq1Yo*H0'r6ZSqQ6%J1C+C>`)ot`76bFt[(#A7aUkcS+C\c)Fh<%49JWTY4(%Z - hGX(.qqMMt*tL>:D#Rp>OJY2XH;/K;kZMVjii]F;_cMSG.f![4g6`B=1m:Xc+0MO9R?1CV9 - DRI2^L(KBYe0(W8NI8]+M[GijVHVdfe,Y"i%kOmF1=0N%DR_QpO1)K5%cg'&/V<<]>o*8kFN9 - CXqTQk-J;haZ,19ZDD-3n.n`-kKb+:M;OR-3.8;sUDDpSp?bfO.>7ld=^o8O55FZcg#D]f+ - h]eOo@5?(35n&-apibk%SOc&eME_Wn1'S'C4Zh*J\5.A]mS3ks=W/F9d-V;a/===>^3"p7l - LF80_D#EIsS?_;-gX0:f[imC=it_-nV>%iCiQ,P5i@&rWHP_@#:P\5iD<81!aX=L#U)_5Eq - e1B/$oTohb/_-+EJlONY;.c^>N)K];%Do2'G^t_5>j5^Zt\?l-V8hF2m,BOa^g--lR_Q=,piF2_K`VC3u,?sei*CVi)Ec\CZ\$\#%fo!$e$G:pDDX- - ?:1l$b8VHLB%Wbt[/&V*\UA!;i"qafc]I(rE,`8%Ge]mVMFT//C3?FZQb32TO2"i.;Z+LD$&&P7WgATgrq - :9.8C*CUW[_1]0]DWrB^niO<0PTYd%aiP^fI%2/H3esLVa+DoQ_1taj9.>YRf5cC\T0e3o* - *77:C6W5M>O$ - O5Tl%d1!f#Is4FGXVpL/W^Abg\SbT+>dM"iaCSF/n$7:cgpiF0u5L0)AeUi"a?SJoGa\NNL - GBlcZp+#4XZ=V32:7LFb^Roa>]oo>/:bO`+bZd[_*Yk^L?`L:Hr&OdHg+0*#l]h%"`KMN29 - EZA`D6Qs`6/l&or@uJWM^1(K^Z;>.m'S7.:+%j@0 - 5@>ZW,/TG3rJti=V+Ar>FLB-1RD<94a%%.hmH05%sH+H`%rts00&==o)^hebT4G@3)gQ~> + Gb"0WH$!;8H2.E#,k(WEmu+jINgJDg*q*u>=t!U*"pcKN+d=O>";8GFVsnJK$3s0rQNOSuq + @2=KH0j1?*@FVA7,DIt9>"rC,[4Sc9=?CpZE.X8)iqcO>P?9gmdf-poDY93+'mftR$m->la + cWB`K-9:I]W/P^3nk`4_k'@h,I\Zj6P;]"F/kQio\*A[K!oBb5`[@<7e4cIXN6pJLV!7+Iu + A3G_X':88AnTStW@gdMdgnB.,<]ZpnK_)%$`oX\i2FL;F!_CD7PUTs+OF)MNlX-nKnU7Ub6 + n$4@uY;-8't_?*8D'Gd:6YQ3)iKnX.f/:b)s@Gr(2$QuQK=EoS;!_@)@(7q5(!7Dd>KkDeb + J@%dSi>Dt1+K\LCG_X':88AnTStW@gdMdgnB.,<]ZpnK_)%$`oX\i2FL;F!_CD7PUTs+OF) + MNlX-nGAmlsHdm1F.go_D;lAJ8=kl1Fl)ra%-PkKkDeb!8$Ralb6jaB)e@L6Zb%U@Gr(2$c + "WFgL`n_)g1AP8NHY`X8b;=TWmnoKa$D']PTHn$QF6LQ1;J002V\2ihE[H%#"lCRf./"^KK + 3fU[,t_^,K-lVn+eo$dVXO!3S>dFkWsO]b+BbPPDt1^blA*:;*d6bmnW + ZP*<@CrPr[cKnX.f/-+9AI(eXY7NL>B@;#!poq1_K'Gd:6YQ.pLBZlXP(&#Y\:E.=@hDnsA + LV+(MKkDeb!7r4E4S^%PD>mWqBIbM[/3H:.!P%:jf-]=cYg!YoSX,HUPa)ps(7q5(!*o-pW + tQ-qD"C8V*PkiL6"/^i$4FX,LGqpuYctrH8O6eD)%$be2+KQb:V=l)Dq]$tJ2MHit-nG@Bm!WN4s+P=PA0hD + 5KkDeb!-ddVq`_.!*kS\?@WKaXS!=[%(7q5(!8N_2Q=aQdh,m6'Icp,&ao9p,(7q5(!8N^G + D5M2ZO#=,:a@U7q"bhYC-nG@Bm!j)272+!<^LNGoAn5Js^:3Q(r5Y\=n-k!Si/[bS9Xb::K + =%YEcM96Lh2Bs=>7u?^!/6,>-nG@B*8!F^7\3Zn7FQmRHoi8rDF!.k)kt4@kmsR7jS!=[>5:M-"_D;lAJ3+7S2C[>Kjicj + L$QE(rm0dL$]9/*PGV-(oTWmnoKa$CppKsJYD"XM?D6^W,9+\l\mm#iKnX.f + /-'n,^M-$?aX`MdG:RK/P@U8J!U`GNkk_Qpn'/Q=a/6*1%#"lCNl)gV-el8n1Ai2ID,r)K> + [L'bpX[QX:C:\I0i;4@Rop.Rq*Ts+N%OL*91Y\0cH?"'4(;O#<8G9^>!H,A*i%r./: + U^RS<^]cgn0"*1^=m9R)f]J6+@h3O&1U>%ZN2\kC5^P722,IBT0%cT(a,C=h(`+t]@BMW>q(PN%NJmGu0YXhlMoQ7.P+soHFR)0(fjNW3%-,ra`mVR!N`(^ngH$h([T8 + Sh]c9)daa8m7U;:&7;b==T]E5s%CN:55iO1C!#O_12qOd,d>L_[$@CU8Pn^VBQ>+Z^KE[&5 + i.Ba.k?%kf=uq"3f^%CrUaTe#"VO+0!OuP+p=U4hWf`=5(:IA2FEN!X`S(3r,#*G9MgM5'! + *lc&SQQ`!]&H@kYC!U=iag?AVYW1(1E8\M@0*lGhqRm.7&j;Aqbu.GH>-Y9_q^7k%`i#t\L]a[l*#[]!)MGd3DU'&d?RF@0I;\DZkrU^MNS7]=B"DS0 + (C;7:Ol;Zff&81PAiJ.%CM1-XCN'[&3'P2:g-.k+*!9kG\?3h`bX>]=5k\>40j6e*u=D]Z$ + V\8m%tp>gun-nG?7*8N;,g")D1&G:dt'p4Gql/QYlO0'L&=YNWupqm=N(4ZdQY"q)8-Y9N. + 5SM(.$^(U9,)GG)/?SD'0:;qNSaVOg5#2+XcG)<,OE=\03;'Gc3t\N0Cg[,[HZ:fm]mmG0tH+r + Q`DPBmSL'3e$_b2(.)m?l0kMGU"N!1_cTIC;QtU((#hTVnqaRpm9'DIcmA&LnJ1J5c@%Sn/ + Ll>3V\$6*E.kZ'&SV\=0b*U^.,2$c&`iDr#&'MQ>0#]n!h),t6O^YQ139gG[M)'6V`(]7'[ + h,[KusISp.r.0>RI!cTW-gC0!*>a%)2iU^"?RbdY%'I@ZlDQ$j;h%AEOKa$Dar\p`Ze=g2P + X&mV[?EBqNg,u)&_D:MCTLBP1J5Z]]::!JY1cO^Ao:JN'.5%/nW*L(lM8B=5lG1uM(0tU:9 + _I7VWKOEeYG=.iIhjO`!f"PnE9cBI2MI,sc)I%)OV]Ms:?G5cWaDNl(#22c!B>48bEV(/OK + IA!FnO1o=GF@*fc)FjW`_n*&/5n1;hm8,:,W7N10Qh[6-pmFAMVWga;/S3GPTHlmC6OEKa$ + Diql`^d\cOWZ]l(Y`+[+Z[=SA\(T0GA<].]h]!1\B8eVVBpTMS+XG:cXkV6g%L.JC!/K#.Q + p\&4[^,Wkp^A;#9YJu2;s7fQTGNGS-n8Z\_\YQ2Q+aN!aA:OX1a?F0]IPcPNRf\OUa^,j!: + Js@#a(-j8gH"C6dA%tEdWokq#]N@af^L:qYklU8-U`g'Q^o=(J`[3e=94`p*bC[s5Pn]?-5 + *i%L!5dg\qP:%f#oTgNZ%0G1*Sb!nn_UXmCTFkTP!06.$/[^u;AMR)q?_PglRAXSG]9+\t33N,M,456&!t#FOOds'1(.8).oN.)TLf? + tWpK(64SfWKPu4%:ZU_?+[ef,gEuSs/$r[0imh + A41o3EAt2DerQd]R&Ac8e3dXF33m>o!fN1I+[+@S'l'hG#QkDA^l_p7*j?LDQ-r#_;INWWm + 0dL$]9,7]I;374.k)ktJ3_c#F2#U=^ED@_$^Cc3UihM&FZ6U[S&\mLpn2f";?MUVi5hK1o. + 7oV7Hp\@VpsYD8r@*t``tR8:chH%JAMA,A5CML(,NTq\uN4gQ*?DqO=2X[7rmLVCGEUX8Jh + %LV3i1c=Q&mFMN^-!2:hB(/;48#,PU"S(8Lc4FAbn4lLA=07I_=\Y$GA-SZ7dGYQ,Zr]7%- + ,gl>][WV7ee2+lFk)<>[G$T=!F3O_Dg%t0HU=<3i?1ZlY8T?e8Au(Q9T-u5jTB*[s;qS/!:;&qBc\n&&V=.)d,mAu+i + /bNf1G&h(2//g,TQJO'X\*/\]9-YN"bd-_Ock*l8:kclD8X.%!+A52c`gUVrpYZ$4FW]S*L + t1Un/4S9p9+RaZt%p>Hi,&jg0lQHfq$@qUO,o(9RW7Io0;Jp@^/IR((HDY20cGPPZjZN_9$ + @UN=u?kGe;..k_U]5FF;Z5PELHhfSf(K`tj9-Z=j\YWX97ZjVK\Whm&f.k+6\R?:r/.AHi% + 2m[;-3fnjQs3%ll#&kSe!*@:>-H)#^7Lt]PUORXJCYYY$NZ)i:NV/"WQoV0 + 0P!mi5CYhM_/Q-=m;j@%O@uMZ;/JKG9^>elM0(j&?_gbXK/I.G5h!dP.)(X`D'bA33a]PTu + DO6QTj]n5iTFY8WgbrCfT^$rFdONTl/DWP7);6Y&6l"X5=U,J.k$=fG+%LoN&."P#Wf6]i[ + iaS%iN5[WD;M%A$Oa_?+]"St,[0-;TnK<=]5&o40/dDYI417%/!uY"&"gr8o9.X%He$\0 + ^m_\JCClP&sR)"'e\ND-^L?;6k%ZoLdSX=<$R,s5mNDG@(\C@0$L3m^mNk-;X9f7m.<#Mao + 9HY+/j6'@t79\uOlTO50mRN0UO!a7U53?E9-9K.[=1e\_7W/h=0LG + )QHe[-8M>djYQ+`?mlRj,-I3D6Ru7,N8TF?+2UNMFpQWMrE7_h5n$ABD[F/AE#Om<].k+50 + TA!DVqY.^8F*>ZbV%B,%=:.?&!,t64I$(&sj^/-fh`4Js#H+?OU! + Osb#gJ!OI;^ZG46NHSn<`0?&&"L4\n+p`*$c#s&RklTB:VF,4Y5l49+['-2=>osFATX=KXH + K^qde\8k6X^!tg],98M^`aXph8!'pu&H"Ed]6&0HHM=jjL%r3^tCTGCKZ.(4ZJl[SF.G7u` + \.55a@M031sR7Wta>WK\HJ]rut'!c[)5lVfB);j1al:O`'cq?thF[oZHU.[6'.UgLNONrLl + 3YQ,Y&\7LQ:N;C6&8OFDPr\o:$HE5*6Ja=!e=s6%m]+(@K!c[)Ek/eCV8q#Vb7?qu>&(S?h + "Z#A-%#E`JeB6nqRDK#Ei6Z725HH,;7]VSrB^oOXL$OV-=S.LJMKEh6QRrmeJ-58T3mlru5 + ^gpF;=o1=o[_A:6aocXiO9p0c_(b7%mA[jggX"C'M!$ + !u2EC-remC8*OTC<,<9'N%TOs54_H;Xr9s3n/s^%m>u!c[)=.nOYd0:$&3ZQ0'D,97/&PWr + S.OqD?'DaN?eE=7I(i4tHa8O&6A7#%]PFp(>-6F'^JHPTY_DqVXS_?+]7k_?@a%_YaXU-@* + >.']$OK3Sh#@I.2VQ_]Q1DA*?b^k&9&5>(:<$s"J7#Tad_;D`mH-9t?KVQ^'%m9aT_#0UPM + roc]l7B\b9I8YSChm.50K`tj7-Z=i1@8FuFrR$Cn\sh8DMW6Id>l&^DI9<(i87;POg9Gk,7:FM55Q/ub@#J?Z7UIOf0,TC]fslaQ!j + K'WGEG=&UXN+joH=Vq9./&P6?nC)!jK(RcU*Q,Ss/%-UCZhpK`tk"fBreXKfq*&oLC"]!t# + FOeui9h=;821HKpPT^]tPhJ?@Whk?AY/;L7Pf5/7C;(4`OC46BZ[p4@D<++8YM-nG@&HZEp + 0>:&+t7j$Xk$4FXMDU/1rKtSa;oLC"]!t#FOa'Gc5$I;dH;-u?#-cl[[d%k&t/>Pm9gc_@XFV4IVL"+3@B! + SGQL2UeD)Fja_U6=9,er$JQhoqP+Sj!JL\bleq)-/IEGZ%/nQYpjg3_Y]XX&PiN4AXZuO[Io'5I[LR + /GS8;X*A[MHl"8kGn!SD_u[7SIU[Qm=HcTT&c'Gc5$pKsJYCgd]_V#K-Or'=`lfK-ZZ6F): + sOqIP3&+BgX"^pZ;`pq.d[TJ:McTT&c'Gc5$,Hm&![]h&5<.F<:rh-IqC`V=c2VGs5aq4N^ + #P`qg!jHfhIBm47*HOOOld%`0*u#K@X^q#AF]&G.cTT&c'Gc4aS@k[6Ke8L>qK!2:#DE=h, + q!Vf7:AtbJ,Qn_pk-EbYd.cr@Z/*pOad.J&+BgX"^kn3mNRS+Ss@%dEt@aFK`tjqBC8*E_N + mh:rOE1.K>I\oZ>5Lq@F%M`rOE1.K>I\oZ>919Mo&Lc:]7g/pk-EbYd/&r!u?TmPTK[B&+B + gX"^kp9cM860]cc/(k8rOl$4FYlSfRDR$@>X]l-DN.*u#K@Ps%/%>84M<]'@U)^`O7+J;:lfOW'C"VIo'5IGkU<[Sa@&k8rOl$4FYl_: + PSg'XgRgp#dCS%gi][b/#Z./.-`01A'`Ni"$/&^ri@qPERY9[f4VUHp_.d"Y_=i1J*6pITs+Nu]YA/IZ@sln&c+j*n-t'T@--mtn];R?\.$t2H@To + ^/-%1j?5])G@>e]arm1ap6X^!t3O3(0Z@slq+o4P:n-t'T@--VKU^MN9#3ud_^c)rCJ=tI]\P[4>84MDHKYl&^c)rC5k9*eQtK + :KMpb=LkQ/DkYQ0iq-g,r:_H'60rLjJkL;F!_?LE/]/IHhr]''Yf^c)rC5k8sQSRYOLC?_f + aoDmcj21PYTb,BgfIt^=&.fgb)"oE-8QFKCFl46%n2dO]$hjhjTf@L$g+taS`ge3$K_BQ*Y + =9(pm3P2)+o6?21ek1;fTa1@I_?*81A)6EK]4Y6Q[FnPoVa'eRhb[DTM7I_K@H"7FQ``9t8 + R).e#Pa%j!f3cRb0#o.e"!eQ=hV2i+*rHU-nKmqLodX.R$[iq=QashC67$C6P&YqK`uE3Fd + JD\]KOr5[Phoc/H)7IpkHWe\3m'3mJ3Muo/F1#l$?9lAEj[g^c)rC_!e>+>ZUdB,^8"s/0Y + u.6P&YqKa$sMNV0?q&'dAIhYjn&AF!#R^eYX[5YUk877E[nGgk4_nn#3-_uop*@=bf;=L\/ + !hO36.26''!Z>K_XLVa,Wc1rE;\i-cGV_T.B+Kb;!)k`,+!bin-jN/I">8><%bL0OCZ2m6V@ + =e(/h&_F1b!%'KfS^%5%YY'aS]?(&DE'8dY=JosJHc5aK`tB.jg0l'[Wo;P,'rKW/q$h]%( + (Q%!\'!i&qD_E$`'N^;iB9Q!@X_I5\a_g":u7](.]=J4V@o;a*H9;p.,0TTWg[R@#kF%3#! + ,63Ig`n<3o'r8/?m__MgVj5]BS&+Iq(/!QHpnC1Jk + 49!5f0/$Va,6<%M_?*$Oe?Hsi4VB!l,6@Rj_?,9c;ndoJ&jY`TYWr1Fe?/:jO2NaG8-$AuY + ^eY*8-#WG_?,&9,6=Sq$4CP:B90t7l)m6]J@%aR!T.1`?;^RE'MgAffcpO]UTf90r7N<78\ + 4-3('#$an4RW[ZJmejH$SYs*XNr[-nO;i8E8FqLZbs5!_=sY(@W/f,_.^?@0+\2eZV!FJ!Q + F + mgYcLEfcpNN%tt1WnU?P;Qr>bj4VC$T:ftN!%soRZDXS;l!;r=VR7dI55Z_lWS;^c^Id:4u*dr.og9l?AbimccA!X_-Fld`6hE(M>IIMjkSp4^ho + V[]JbgE&QlX*G5]gj8ml$?*NZUY'f0.C)WZEa]oD4:FjDiaeJ`s,;1gne;g9^QXW4W,,Ld% + GP4)s*7t!0fs]P)uUG+7.gMk#T*2*!:Wfu$[_0gK@-b_n\jpBo6Rc]*VrE.=&,)7Xd1R + )*=_G&/T\($jU1`gl>'FlG^$0cQ`amTYb!uj5WT9K@2g-9mb[oXGJ5ll6Q[4#E*tcpRK`Ys + CR5q;h;ao97fE28pROO`6N+[5i*_TK!1sD+Y`4WXF)pD50?P%`mM3sXP/;7ZNpZJ[,=?qRZ + fr2>BjKKI$d63>,B63LYt"l2%=p(s\T99-IX/r.Nt1R/0.f@bp%)^+B$?Xip"FPj6"VP&ZpS,Lsk;Tm+LNR*usdf+&5"Bl9Y(EHAXiu!#0 + )_[\d[0c[S,A3p4+tCTSVIoKCT/BkGZWp5O!L3HAZ8H4A]\]LUt[A:'YEp.,0sR8RIs?CC6`iG7I:]l[hH"G",T\$48arp!SZIJC\^_\(HN + @lbRXa-^;TN&,'t0f*9N))FKhM)!1_f#=`gmJm#Jm/MG4Du]K/@fP<4`XF]$].kK)CjGmLY + 2B6P\DtVrWe]"h6g^nZS^Y8g.siNA`8aL!lg&IoZ$uW2K3VdTlh#:X>a0.&DdEDEkAar%o9VT2bUEE99@7AEMd;+`NKs.2Z+<*]AOE#5d?lR)o4Q'j+BI?5Z&@h + +0)q%ZHa9q4G0a)67l(VSa#i6U!-aiqt2\$#2.D>3pqf@l*na1"h9o$Wdd&c`nj:tWY%,u6WX]L)pb + msNA:AjG]doo[lY5VF:+d;/Xj`]h*'I#gL*GL!R0?%;F/BjU7MgV=_:CV(2PT`%,Y&duE\E0k37i144EfZcJNKcqY"<]b%e1H\s1t*Lf-+GtS+42SLPHLtnLSaT7^p%u's.[IA1!^J5k't0j2im7Z&e#cf + ap\iZOZ#>5i`kHlVl#C4q\9UHkUe+iuIsnI1HR+Wpn65(Ls1SSs%_A+tIeUH;HFU=X3&>`) + bbNn`+9)%-CkJR2+4i0&dm$:9j8pZJ,qO(mAfA3Sc`/4oa2!L]>t[[^&C)J/Tehm;'p54Q + ?X\n+#%_HJM\L4CM4$?[ZRSPJ!4'+SJ>+:h*SZhQ_"VYWRR;kcY1iEFf*Bf"_-r_<:G"9> + og(A;7ldhQQiFR5>WgXEQ_q:S^#D^YUqWN5_h"`>"PQ*6[^4qB;)[Js&"D$9o+@1c&V)`-B + #%Fk$4VO5+Q#,%!n,-X7D9RgPrH?Cs3s,ZlC$UC/UYR%P<)7.ud=a7'Sbl@Dd'D]'0>7@"/'+p + 3R66\tQ>6Q`#c[D(JF_VYTXm[GPj%dL([ske%aZC-X:ZELp:QIRO6cTs+g%cPt?^=I_UGD6 + Ee'qgA\l*Dfjkf2mqLD_01uiMPs3g2B+S.?D=ie6XtDREbH'JNZ`aGI?=V!)abi71/gc0c` + uoVfR3kkp>^U+Fk.q@?DJBBX;0Q9e.:*"2H],(+J8IA#>`+:M,@Wf.UgP%0<7M$B#V)3pD] + :1G\a*/-$2W=JVNa(t6bUH_11Gm5H8?'LQ;kOrcP/qV"^8&6T!NU9I0g?adN>l*46cM$Ml< + E!:qa$@C'cCm45]p`e2V/+#8BJ.t7WOkRNebY45eq8`Wg(B\W8_BL#%1a^*cFWOl5?P`f`L + ;G-/4I#KIGTL4/,"*2s2%#96nWBW>H2$AABYQn8KGT>2>5=sKD+mjj)^!UZP6)"=$B'#tB? + %;98Y'dL9)F#D+H.^"da4c/F>/QDN"#<1a0aa(!()IX,!A),9P9RaM+73rr^-M<$6WS%/CN + )cN9Oda5HV68L9GC-@0)#eKge%L,.T5W%W0J9^XIYX!XK/@M_t0QEhCJImqtbt?2JIH+;?4 + Tn9JNtS&EYJ_<0b'[GSLpJrdbM;4gVOHa8PVR;9ReIe*UH3!p$Mqa>HDT,q.4rT47pE"j(t + kp+2YRDdE15Hata"V>2Y(:_nMP'B5W7Y^`"0581T!WVUQ#2`6,9TWg + [d&"1A^6ZYu_f@o\/!'rkYJOCdTrLL_%1[XooJ0:Mh5U*p[bXdrUBq8gYrOqo/)h9sV$RFq + MA%J$1ca^gRatL-Zh#MOo=hjp$(e% + YT`*^Pi&(iL\EH@k#+J"URo\\(5A@n2!I0L=!q(@cd2`pN0MD<1&&;*X(3&(2PMg`Z-1)!= + ^cN5GE8:oAoJVPrnUjO<:BUYq'nlh(n7O)ao8fNq*Wc=(6=BnU9JMBo*h?$E:UT?c!&%/N^ + `uW$\k;LLFkUm:J/9Ubi16\kOjcA7*<&Iq;#nCS'Gg2`$c_fmM4A^0>=Bf)Iin`1&VUBNoc + ]O?aiH19oCmm`9&EfdY)^S.,:]R^b@0%Io0Suf\ah8.>'6EQR9i(T,7P@7puE?4It[ + pGQ-oHL9qZJ)[^-J):N"M-OL,!$4HXkX2]68M + 4B'BBi$R=rJc1B>p]ZSV)'4C!eEUF!C&_\&ik"[GH`L^/b_Ym3[=U+8-f)""/I^[$i&sH@l + g#Gc7f3+W0>!\#>5V_VTT7<2K5#OB236#=opN(@]ub`cNqT:KC + N9S,Y[h`Q6oK-`TeMZFg8.",-pV\(W*J*r2QPDAW"jpI(R8!A(DS]L?60s#m"$9uS.-BC>c + pBTF@lX?]8F5Tdcl'GaN-fSZT)5LnN;_VXiLm;S@m!%t%NJ;U7aBl2k$^-,[@qIR*"O_r)? + I/qkq=?oMg&)@!8^[1E%@@K,-YMasqJ0PaDd..?M1/-IZ3P1j^KtW?)'nAFS$Rcp'&ip!hS + RID4mdELZ]Ge2I8PJG@G + h$E'"C7W4Uq4EXj0t*0"c6,dOkeEnT.EZO/UN;\/2bRNL`lcNARq$4Eg0$^8YWlD]ojKtSq + `\@27`U9^?=o/DV.&*ocg?@d2g@=]Otgk^"UudXgp$hbo3df^0'sAM="6lX4ia'P;7sU + 'hCh/('oMI9HrZ]QY3[-fUK$L??,u.guDTdC0.eH4O==ILVa*Yn:EC^:#*'uD;T[Vp85F$j + lH_[(VC[1%r+V#9-M5Xh&'%u0t'3-'b-gL@Ih@mj1I?5X$/.`ds)j>! + qSq$^j.F&0/o + P]\>@ai_aS044-nA:JYre@!gCO$?J;c4orO[NrIY#'*:nd%IU.TKEl>W/5m#l(9;L<7mZYC + 0+CNbRUWT[*o^X?S^r6r6u.B`?As4]to*Y1 + umEKZL`nErfEMOj/bnKEHo3K'"QirC-8&Im2]?_Z&-2#Poi$YGdSDqSNu&Mo#MrP\oEO:OH + W58muXQ?='>r5bE,G>CUl!ED]g\JH!X.4am49D/*7T)qehGe]WAI?;Vn6onOn+(PN"okS[k + c9\bi$]0JN_2r.SB;6fJ(hQ2<'4tNnYAr/6/JAMB(1r.$mhu)V=ortld>$mRs<(gm&q$Ggq + Ka!'BTo"-YcgRRadsGc.?ZWLIJJ6e9*gLQn$[YqNl-E/`hC'od9V44_:KCLc;NHaddSP;NS + uCXcP?]YTOc7YZT>8-;=U9K>,8q2KqpHW/<+R#bsPBQe$'Y#(h$O#.1'DHnBF$nI + kl($R%W(Q[S-ni:Jp%f-nGp??\uN]0dFShi`i\^ + G,];O7:Ge!F@7nV#:\"D$4?uA*u"C]o#beA6orhBD7/3[n7nf5rZDAJ6t(XsRq^lRkFNWpS + k/&2Q9J;>0E"0D["uR#!VZU.pFKS%;n\7;PolH3;:nbaU-D2"(/\0Vi4`maT(_QXY$G8,Sk + +WOb*Q4YB.(;_1A,9/E$ku#"CGdTp[$]2J9iC[MDVKD>OGH8*VF%oE$ku#"Q+G;pO)FV'@d + c\>j:r2(54n!IIq/$E$ku#"Q'P"pH8:c.dW_)3-N&*@>e]ar[\6@6t(Z)qi$AYk03(mhFHa + D/!nq;'\ab4oZ&D")[ZrUht-C4:3>iie^Us,4crK.f%E47mZM\>+/cnk-nGo:4\a^1=L^a` + OcG^oS8M3V1X]'KS-$0nYWsT^E3n"HIsIZ4+.^#?1uG%375@2ZS-$0nYWsTf13(W!^+i*jO + 3Z9]>&O"1m$N*5XZo[6>>\KF'`QV#4b*#MrRLf/MSdub>86e*FOQk\^o]"9&.24#\0jAMUO + !ncEB8<@7:B!6!<%`]gkrrMU%"d2D9T[hAnFM,c"co83`f=m^nS%:1.m%j!k4o.5ZeFMER1 + dnU#2CXePNm#m\AF8dS>:j!]NtY+=q,l\EBbSSZ^g>#bNOamkg*8%)2/72im1?S + ZYWc_of0OWn_38QpQn=gC+d0VWG#"Q0VB/6/>WK:Cl8q)\9p2J?=qOLedd + `3:\16n2V+m_lME#u8;-4r/-CVI-Xgd_"O/"3LNGFSlYk]^@Z?/tde-MfWdc54hckX^E@Mr + 2r6Vbds1)Y=+qNX^G$L)h%\mLUQe^Cut3n6k"CXTZM7[JUO],cS/6=dfeH[bnQWTnJFbDCG + @=qikUqUB.867:L-A!1^%f-Y^$niq?sSGpk8OJ'!.mWn&n<-Z8TZ\i8e>7A")[>+Of7^t9I + X!/GGQdIs-4/Z:V[PQ?-I;U8D/k + [Q.5CTH55e+3[pHHKLF\pDYM_5^g\!Uu#1.$D2,P^Q#9nA8o.'F=##bNa3hPYGX,/]VEVm0A>c9=Vg5,C5kka + HER3mu&#s9i7Ak3>=q%l=1-g8B,HrdmGH^U1@=dp9@,da[#8X=Pd7'Bs/@673"Z#@(:U3>\ + Y"H'i0r0mL0:,=.=AiE;I + qLjWu.d?4PYD'0>Dn*BYq/t?>YU932?bUZ%JEU<\q6aP,$brWONk + /j--V=.),IWNs3a8NFF_uPn/c^VElPo^Na;l"-pYmiVOnZm77+7dcV-Z/Hd[%kL@+*6Q!;V + Tf?Q2\]%OLQs2\ECaO*iL,J0:[E.R`0q2QTj\U*\*W6=>!HR0[IRnQGN]snjO5d4S2I"obr + .M,6*+M$!AgseOMc%PfZ1GGs/QNL9h[JK`sg)02:6.AkcY*)LA+N_D8_`fc[EHV`fE.+[$k + .p%[*pNV0>$/m2K@&q&,'&-TdM&MDVWR+r2@7R?.9*Cr,b@g3*\(us2s!*Wo]7JZkO8] + Q*6XdB>K1kqDIobWEh;2VWA][V`s_2RsJdSk]jX+eVOHI]OE<@&1T?#\7a*Wmcl7naG&[G/ + Z**AN)Unrn4,l%8nEgr]TsY,mF!MN_?-G%2&#`>G@*<%'RJiojV;qpo:!$`+9(cG=m>6+Y9 + e*_N\ml7YWt6qcGVm;kP%eKTm$i&O1,sq(T"b?n;#UJ8Jd/-/UV\g6gZAt?EOtX#aK$rgp; + *:0D,g$kOdW!(6c'u#--"!Gfl<5&iJcD>^a.\=@Pd!3goEX<'\K\p8dmWnTd60]'S< + ;FA`(/sjY$s%9I>j$LF`3Bb]gfQ0p@=dopeAOMBq90EC#^Ye,RDfqo2#kRZln[+L!#IL`X$ + R]gn,g%ue[H1oogl36h6q'0r + 4->JAGVM?5fSF5d+HR^XR#$AiE>JH6(qj2/2)i + \\q@e^3jOtpFfJQ$\ZZ;'DHnCHP=p;kd)_%UdTJ^EE2#oZMe/9n$eU?3p\XMJV + 6E4`/2hLoc[IcX]mr:+R5<)s[%7 + plAX&$u8k],Q+aP?u$9!mM(pfR)X*Q=YX@?o(jF"L+M9iqa;HP&@#+2ouVT-*["B0JYpC)s + 7HWp9HpB!pC/XI=@0X%BqBDVT?V6E4`F>NYTmFl-OLX.mqM]]0j["U854W-f53U,obY9hpM + Hblc?+[a9j!@<+JaiH'M0l*nl + (T4_58A%'?nl[T89a`G+;*5$9nA7DP1LYr,u4?A>_9fo+nq4YP/79/no\Ftc+QpQrb8`Wi`;oE9X,GRkG + 5S5:iNK!ml\m^9@Qrp=+VoF1Z4K'HQ@tbdEhX,r/H5.Cc?`>!#tA&QO6oOnu + ,@_?/]f8JCiGoV2Mr7uE>\MIffj3P/.oeBKQ\bW]V>g%t$OR8p_,D]K8_^H61lFcQ(;;id@Q + mjb=7sq9XYLdX0X%Bt"_>/3M^U+f"qR35BDM,Tg@I_ckrggS7eCEZY9'/)<$Appk4SN#M2h + 0o?Elh*JM:2Dih!'>)Q>kqPZ0RDd=^lCm[$]A[Uq:qp + dRQ\\8f]RO3.#&!qMQp+'G^teY8bsEh#"HnQ#ZJ35dm5!3h&(O@U,8A5kXQ@O0d9d@9uU`S + fGE)&hTi^5i@(,`'*/$GLdf^JP]X\aC8WAbNS/DQ$Rc)_'S@*0WtYL`mnF!WW#L_%#"idmu + L/toAL4Xksj[e1t8WgC3BPMXgD05:Wd`i\\sbS9@[B]$)=^ii(Z.McX6_F=RBN0'IDK\>E% + tWl)>_seCGn5a(;cu$eWW"H0R&eA5HANn9US6BB.")4]OmEq4ZIWU\f]Q;/]9G4!atKk-]$ + goLL5A+2?Z#@=_g5esHkemus0-EUdL?5+1usjg0lI#'b=`,"k'dWN&jB3m;BeQ3iQ&K"P?_ + 72-LC[Z?leBCb+9g63dIRMsc^^0K[ML,"6d0$tjM$O#43'DFW_fJ)\a& + hGE=:UM&"kG5S5N)EHhNTCct*F?N^Pm2`K7#NHE&8X^-h"+nQ2-kQl"O$V9F"PCVM%!>O;2 + U2^QPe^9Y@mFo;h%6hU9Hn5I4bH$M>4]6)55oa'[S+n`A5Zr + "ml&[F4FVdN_rn$W1HfK6eFPK6@,,t\*>Rt[i=],dTX=\@HW$K`/45c?B=8.XZ*G`GD`@Y3L]J[7]]B0X'G?0$tjM$O#!r'DJS6gK/`V"P9V91G$QCBc + 3QbWUQpQpeTp*qMWB?'G^tS*[^=Nrq@dSc]I(b.U?F:iBG*=ZKjhaLdR+JOX6bRgS[;>!Rd[r5VX>H9nOJpqGbC;5o,NL + ,g#q'BX?HFZB$RVDASk&H;DiEO"pe^q39.:+%n@05@FdnSW(Eofp\i=V+AcVsl>V;u:g)eQ + @$]L(SDjoZBK]XRb-iZC*:1hj]u]K6'W?Q Q Q Q showpage diff --git a/testfiles/cli_tests/testcases/export-area-page_expected.pdf b/testfiles/cli_tests/testcases/export-area-page_expected.pdf index 4a510aa30861c99e592292ee9ce7416c8e36d642..ad6ddf46d7a16f0cf0055415b643cd3e6c80e894 100644 Binary files a/testfiles/cli_tests/testcases/export-area-page_expected.pdf and b/testfiles/cli_tests/testcases/export-area-page_expected.pdf differ diff --git a/testfiles/cli_tests/testcases/export-area-page_expected.png b/testfiles/cli_tests/testcases/export-area-page_expected.png index 8bdc9e99e224f298d2d7534c5389e173610164f0..ed761204b78654a9414698760e69552ad3508671 100644 Binary files a/testfiles/cli_tests/testcases/export-area-page_expected.png and b/testfiles/cli_tests/testcases/export-area-page_expected.png differ diff --git a/testfiles/cli_tests/testcases/export-area-page_expected.ps b/testfiles/cli_tests/testcases/export-area-page_expected.ps index ce348bea7060527e4ce6f6a41937144753337fa5..a3b3f6b3cfcd2e96bf036013aaa24510fd7e2980 100644 --- a/testfiles/cli_tests/testcases/export-area-page_expected.ps +++ b/testfiles/cli_tests/testcases/export-area-page_expected.ps @@ -1,6 +1,6 @@ %!PS-Adobe-3.0 -%%Creator: cairo 1.16.0 (https://cairographics.org) -%%CreationDate: Thu Feb 27 23:59:28 2020 +%%Creator: cairo 1.18.0 (https://cairographics.org) +%%CreationDate: Tue May 7 07:52:46 2024 %%Pages: 1 %%DocumentData: Clean7Bit %%LanguageLevel: 3 @@ -161,320 +161,320 @@ q /ImageMatrix [ 630 0 0 -630 0 630 ] >> cairo_image - Gb"0W#BX56H/SW;NF2j8'mgT%fW-Fo?>[`Kjj-sYf3!(&ekM#QR6&W*m:?0DCN%d!log@ng - IYG3j4uA7=i;XaU*TF6OePBG!['c?"A'.-5h*V/*(Vb8Js_u#^@shccMZSk4#tuX3"&?"T0 - Fu@k.*"V<4i84P0\\XFfcXDRn*BF%KI\$/B@.ak%je-g\s&182_86Sn`+j#&qE/[_f3IqRJ - QB=p4oJk'H^?f%/)r$d'kKJ7M,Xi>N%2+K84?\;%j%888hSY+`'"OrB%.CFC`aUdeeO)@?i - pWDQd-L;F!_C(qGdTs+N[)MNl\-nKnE7Ub6o$4@uU;-:>__?*8C'GdjFYQ0h)KnX:j/:aNc - @Gr+3$QuEG=La+&!_@&?(9X@8!0S7SKki(fJ>>YCi>N%2+K84?\;%j%888hSY+`'"OrB%.C - FC`aUdeeO)@?ipWDQd-L;F!_C(qGdTs+N[)MNl\-nG@6NZD5HjR?XK+!WBP(9X@8!3A3NqY - YD=-\hZ%peMMa$4GH^=9-pBfK\hp^-CtdIBsEr[8QqcM$8TeCFC_6CJMT%\#llCDss-__?-t(o^:t7(Uf=,<`2ar\ - ;%j%Yib=YdpabBfVt3M*q75)Z4n(WTs+NuH$e/m(^h9c8MSa+.k)kt>XMp#=EA(eG3jqUa[ - umI$eJ3W!7m[n0dX!>[]Q>broO<75;S"ICFC_6CYm`!3UXbuqkSbq;GRXmY](&*"^oS:gC4 - PfmRiA#/%knM.4e^k@0'JOG3_QV_M4W71$-ceV&oZ\'GdjFYQ1abkH(oN+tG+&*qFD!"bhY - G-nG?[%YP5U/?eHRHr9ruKki(f!':\OXL)![EuqK[TWiAEK`tl)_36B#(A@IVZ9,a%8O6eD - )@?kfNc5Hp(A;!D-,kukMd?:d.4e^k@0,##kkHq!KtYamPnXoJY+`'"lo9*'W@N[^LodCkj - i=7&E".L)@)q6$/f$A3oPR`2;G^K*L;F"rj2d?/Xqp_9A0o3'gFF^$/6kPN!SDDQn7&j7)g - 6DjUn\/:[.n-0=La+&!jL_KTg^":[Z/7k'GM=cTs+O`#iAg3=sna72)1n@5^cDEfRXnr^hM<`2ar\;%j%YiGKt$bS.ZD-JB - Bs1^JkYph5/6X^#JET6Ze151]A?:dFki>N%2^lB**k6Vn^m-S*-$s$8&=La+&!jL_GCNF%p - )tqR;abPp0MhH9,SQVo3fS - s\u.k)kt>XMp#fQ$3d:mHO%;XO/V0]Z-aI_3GWUlAh'GdjFYQ4#L[p#B.d`=La+ - &!jL_WmV22l0:^E"$QE-E]B/?uFsD?\-,k`&n,'@=/6kPN!SHr9Y,Uq<02IYD=(0"8FF(25 - BfE?dCk)IW$470m:fm^@SCnd(@Dd;gSJ4!Q[VT]H^<^\-cY]4tm(gL3+.L-a2&$gV*G8^$N - ]2-8H>MbuD<'nR$470m:fm^@]X)*?]U::uULP`h=H2`WI3lks4jsr(XJE(>%>=sna#i$'a4 - n,qH;oiW%;X:;Y;&NCI?J-AFR0/CW"2;Ql<_VS=qZ`L@+-p'> - BPn]#ZK`tl#d=VrJ2!_a@j&0='l?-^4*c!^?`NN!SXBR&8[h0;g(4ZS/%PBRD%.;kA$Q(Hk%57.e6K&^c%0M'_gS`P$'g - OsL2F0.niX?:,:.k/go$4FY$'5s6P[RU#\VQaL:[oZJ[IT!s#1R]7b8V)5^YQ-46nbRWc6I - kAhSe\^,p.C2NlROCkpLZ?`Z]YA1U9FVK34XO16([@`2_PQn)HfXO;;Z"FZ?\MYh7U+"HC2 - U*-nG?+:;/X>S"7^g"`\CY88dV*q'Y1@0'JLC@;R`' - "-q&n\V_kXJE)u[O`pXOrf;\@);(jdg[30^4-i8CV)X`c&Iu&:%dhKk<,P:=9',,hJH.mnZ - 'd\@j--nG?+hVL+AhXomnn`!-RRp.Blb#h5tKIAAO$dS+/)K0ijof2 - K5hTJpTcYd:>793<>6CXc`5.j9l!Ea='V3X;mL_?+]Ai5l]<=h1>`M!*2'.k)#3$4F - YK6RS0="Jn6%Kak*14fki_\8Z*s.k)):Ka$DaU#X<)C3!Gf'!J)uDQKW^Ho\gtk$%"+^^:D - V^o>5lQC@_L<"B[=UiUFTUr[ms;GG+;krQU?$4FYK_ag%1FQA<#+PoU=696FjB<`CY<`11G - $4FYK,=`Lg7&3`T(QY=[Y,nEYa\%FUUTa`g3c]J-5sDI:Ic=M;X%mjhYeH:[F]MBC(PN%Ne - BpVo)RSP9mN^RVlWdUbEU3I9kub_$Ka$DaZ%FBWXjXo%KBS&O::[0UfSqF4.>!Dn!c[GWJV - =I\jsL%Pn^&"B/V^>[VS - c:=9)):Iq+$q/8jaEiXp9:KC2X58)'"S.k-(&_?+]A`9["(8l;Y$M\j_B/NWQV*bI@:GrmA - 'Pd?Ois!TDuVbZTNB97t]j="gI.k-VpKa$Daak,Yn1uahh.NL]K2[Tr.WOMTG1?_&gc4KH8#e*i4Pd$=fD^[.:VquaQUp/*)>s*:^,#8QM);27bS9 - _33n.Q-:^o:7ZB,]j(7Al#U:6lhL.4uU1e^DNOT6u7-!c[GShAHML#jE&n`eeK\)>7-0m*0 - FHAgN-Q:fm]mS_cNc+9^\Ho82tMr'c%+h8naGQuqrJEp:;g_?+]Am9S3#9gGkQ+J(JuWRUn - 67Q.@-bQ@%I,WQ2>^o=ee9oXAlkNd-I;=g8lSX%5fMM+<)!B>46B6LqO^OX1EUg@[Ll7mH, - HiDQ0NOA)J>EbR*/-)Tnm$JFSfFN0o4D4ZCXOBf6#eQ54]oo+QPnXoJ5k)Fm@rf4<^IXD$D - jbNtF@s(bCcAZ1>:3`1=tEsLA4'c+3-BCkgk@np7u<#FkH=,7elPaS4uJst'GMc=&SD,^/_RE\j" - 5d_?+]!@)u^%_o1RM'Q1%K-t=P,Ka$CM#BEi`9_`Q^`6/lH=*6a>OZaL`8J);HP0Ql*UTa_ - <3I#Ru_/(dR6cGN030BI?QCek1naPn2-<=#@=9*elnAK@"_1"rd&J%nR\ZqE.rC+KcXb`Z - FO0e?HZ\$SYQBU@0&?.pui?CH%&0m&FWTF%lf8WgW%@eGFddY/#h$4FY3UB_kt(uSE*O*I$)$nEe)LVa+3jOf$;#,D'MmOb$ - +`)EFX=C4X>iQs,V;G^Hq,*'G6JAL$Delguq8k,Zp%\e*]12N+02VcYl'*]EJDJ;PP(LOPnCWTro*iW]X - mRg5TnMUK8rBq7F'>#X>F*,-nA<Ip;KJ,`e$^\YP#5NGS$kM)S%n8ghjG-nG?gEq[a9R*U - aQ6bGM&k.9tr2mQj,V\c^p`'`Zk'Gc3DDnRpL?ns\*FRe?9Tc2`3htB["dXs?F)gPYR_?-t - 1eB9WW`cAC\'@0BnC=$7DoOtk!AL.M6"lP,6HY42>jH*+KDb\YpdA[rS0YK;Hu1@X/.O25PoQ7Yg!X%'Gc4eZeHhG4*$8P8F_r&?5ds@rKS(DZQ#p.CF)D - JrVFtrm!dpP(Ok$6LD1MSR<)R[`]J*3oEauITG&nG5^*,& - h#:2-P#4=1`3P8oJ#f_+;WKg/!#r0pWuaM2LN,?H=5mGXW@F5.4bHH%N6(j9FhkV#@^Z91C - Njm]9,8VlEnRb5d$mgs4s@Ab0#Uj$OV&Irs1cA"lR>8P:I%Glm4e_4<0/_ZVE$u,O@lXJ*5 - n(b.t*S#lf=GWd)Y)_m=En`UTo0$4FY+6cYN49\4;J=M,Q.91YtF>@0t,FlN(g`T$R'QVbl - &gUF4r()4FppXV),$c';?a-RZ;T$)GAGVWYLZBE)qgTl948^UeXiC<([MaqFCm>1XTBG;54 - 7>HpU24-K@HD#L6@&?\CbLbe@7^hB;),9ei-,UYT>[On9o%_+RYJpI5-jg?t,tEtH0pk(g] - "`J=@'!4k31bm#*\5=mOR-"3#DR`P4^;)S66C1D6dKu;"LQQneJX#6H&@F)[Z - tg%uq]YcsZELf6tU'!%T6D.4d\4i8W_K/PUCh;Dct-]3`"WoYA\\^1BZ?!VjL9K*r,8:R1? - "Sd*j,`_),_I8c,,;Z(5X`#eAb=",[#l]*!1^gQ`gd(RB0!/m:g>,`Q\2c'62-\k&,fBqkj - V%>Q!AOBl,pqcJZ(Doi;C7QXl"kXR<.2@/jh*U4@STl`9;.j;HD6CL^WoD5(]AKofYQ4UZp - _tQ6XjK;MMZ>]G[#uW_nn4SCC2U8a'S=1'n?!A-cSq4R[CWtp$fDZ,<`7^VJ*3S."+e;jE8 - osXGm9H[4G:[@!;Al^;N.nc/%<:*lA#=ckVrNEI;Zf'9ORr=3;M95K?81-G87:]>iistMP( - 74_I(_YWc_A@d&sKCKi(qP#'+%@@k`N"Ff'BbhuPc;;YX:T*dpHfW7B]k3p#1I!^d]V"^il - ohe[pJe>P1ALHiFKUO-*M8`:cM5M?9(X@i5)dcheMnL6!h#MscI;V<`4/n=&0,%&[(DJ%D#36IrG`&^k#F29ti[9U - /T$D5!0Uph7KBdLkd`lD7?Rr3m6SQ947i\E,*^@K;fUIGAB`SDQKXQ6a7\8WVb:/r6RNZ"l - R2\h'RgjTCWUnVn3\r?:eQo^)g@QLYgCkXSn05_?+]7m9O`#K?_jY`.pHc2/G'(Xab!oFS"%7er@0/JJtN"(tr_Nd%uJkcs.-*BrHP8r>quVEtg - iZ_\59U9FV;33sjtLKoV4SG6KIJ%31TU?u/Cf6(GODdcRH!"QZJI<)t6Y=O]iTBbp2#2LU' - Wj$ra)UhRi=&E'"%\2>jpB*I7ognV8%_l`*W>G1\a1&,DQfo#_F[)e]ZE],#!c[(6r6H'-T - s3aJiC]"$BA*&tP6lpHi./X+/ChZ"7j%XY*6K+";V5+Zi@50B^siC[_HtU,j@:;>XBoNTkc - sln>qOMm?2p4M/JJtNW<5.2%17GZh^:Ybi,=aC'I2@>1EAT(2%V/e!)j+uXtV8?p`;hPoYL - :USeMa'gXQd2=9,3.Slfl,fQ',CIe3X^TWeDi*BLpYTWjdU+SjCiG_!X40Qm0C;M2hek^i< - "&+BRQ"^pWbd3T!N]qPbQO$:>]'Gc4u,=`Lg'Xgp6QQLX+#:M,';b%na9br$p9/O)HT)mG.8!\fo2l5YGnf'?$*d0FoH_?)EVZZ!9<@2j;)q9]FlJAM - @Ap"$i[#)-pB)=jO5n,S.G@-Xk\9a]XVe)a(AhuO/m^so?s1CX]GU/U/D+5d/,$c(8k]e^' - ()q$2+UCZhoK`tlLo^:rJKpA9WJ2\l-!)mh)RMKgJF7f\3\HdA3_?)Fap9(Cb(,fN\Q_/VT - 2^g3W;eL9n?.]3A:4DU3hOq(Q9V$GX45Rgil7qjN&+BdW"^pVd*L#M8Clobj5PG.r:fm^pc - >J]TYik[-q7-`TK>I[DF`uN8GRI`W8'9.@^`*t'J>mTo-cTBC;E[?qmK)1;YQ.A9S(FkPHC - 4Wc"2$p.!>mhYZdPMN(\N4IEt@aEK`tlFi5l]?^KcVX,pAf,.=9(f#+'iVXfIM8ur7hCe6"'d23HAY - e6")9dJ,-V[G_HP6mk8rOj$4FYjnM_4#'_\El.6mP,D - bsO97S^T[hk722\qg4="2$p.!>mieqE0DIf';\oEt@aEK`tlFjD/fG$7 - I:H[n%'/-$om?Ckk-fOg[$5PG.r:fm]e:@4T\:tUk)Oe6uN2^g3W,H(902^iJmKgkR*YQ0W - PhEmig;Kf^, - /^S"hLE>@k$4FYjR2o5c/SpOj/O/k-cVR^D7c)f^3SqWEf;%D^*oInC$c'j:D3Ns2Cp>$15 - P+s%:fm]ec>K0iDdc<2#l1/!G_X':0G8;1_b0T@q4S%HS4S%m@]a'G`t3%NSng:tW - s;OSmH-4@QlhLrJ,h9F?,6pQ?VEJ7m^hChmflC[5R4Ldb=Ka_-I2FdDsP"$AqZ!Q:t:DLKVG1 - COUpUO1,o4hrif(-k"!GHZ9RidUTIRp+WRIcpg(Ts+N+do`rZoZ`e9D/E4L]?(RH^bZZ?5Z - FH%fCBa>DO]i8Y@4n^=8ANpG_X':kgZ_MX3^7(k-1t)l?0!%ciZBt@0,.N9@Ln"%Hp*MQkob7K=rX7( - `&:1qW^;5G>;H'"D)Q]O7)f'o7(VeJ#+L!IW]oG6EpF'SLr'4MW1dg?adY(k]ldnI":t`j"VQbAHR,V5Du&o,p`+tk&dWh0!$@o - =5XOsJO$nO?HW0['T3=KV:fnk8fHUFl&VUC?XLW>'gNt3'fHUEaTs-dieWd6cfZbHD^oo.; - &3q<'OXRrW7j:l\7KL=o@=b)^;7\UQ-2'k0^.2U_i+T2t=*e8OX1,6o4VAY'UBsHk'G^rWp - .,176X^#QX1tq%r+jbd]ldnI"CN=*"al7l&jUtE=MV-*Y-LP!#Vl_ppl@@\,\hIGq%3`T0F - A3_%!s7M"@6Gshe91M:j-r@X1,7uOT<$]@0(jcNs(PX-nHL#4VC"hU9K0@d,sYJMfZA67fl - 4L'G_7s*XNt*;-:VTm#_:2!egfjFY^cF.B8;q2B"Q4J< - (9iZZ&cgcW@0'#)RQ"9=>Bk%dMf!$d":ugm(9i?Q&cgdL_?/#i1i9r5C4Z+TVAZ@OllKL(QZjbp".(IOT9um_?08>;7\T6k@?Mi?.gG'"l,=J/TdQ;*Y-(?TLUn^`sg&jTp#Ye[ - qtYXd]9*l87P+>H_qTHbNEOXOb$"H_7a/9$rFjic=%!Pg,k)o#3_oT:c(gT2#:#%jlbc)e2)-a^H,H%%9Dqc^6HCqtA8?bK*4OnXlnao^I?RnDMM>;mM]?l - 1Z!D1$%qu+8ODbj+%!e.DPlTN#,ikpVn/1_KES=Fe/:lMBmDPCcQhf!*GFqKalpCL2StWG[ - \mYIPgl*a%!4<4^dX/mV-A^h/q[fAu:<4T(#Mjrn5.Q@JK!os0NRqXqq'#QLNNO!PlFf/Sn - G;qYg:5r;"[:4oY5TjidO^*o/tBh(L(6p[mmE5%@'c%tAiF1XA`#B/b4UgYBG5r1@q[M!U8 - FgG-g`[^&C)85b:BfPkg;$V#U8t5!,tes$[_mNWdji2=^V1!kW*^1GO:hK*Bi@;FD0gVNJ%5mD^Oo,[u_,'7HN&@M - (JI<*.X8='t1)UdubkJIe&]hO+$B/@DfZK2pK)bI"se5I\/!Z)"WMIbUG(&p@eI;,6>Rf^+ - \iT>8qeR>4Cmb'3;3uhkN]1FLqU"`f00]kbH&qnK4[jk,o9q(FlJ^Bk)G64BL9@Uds&$DXs - ,P!-*H>Cgl;F?i4C0_[d:FeLOod:^M9aNpZhu@j>?*@EsiOlc&lUHgGA?o"mQCm^4oRhd<& - ^Klj?mFUc+sr)D=[\dc8q5Od`tj6"SM/KLS(5O%D]M\c!_d>oUr4h=&Z?bP4,fctKp4V@b_ - jFb=8PcX`G:UAZV+5!\7^BBi_UITP.iYP+%gGZg?@AA&VXn-.c"=k#a]UrG;Y?Y6-MdK-gh - aSpcf4on[qXhmSo[2VN^,W=$0bM-2*^uke>5e?("RK'ZB - XXT<.Cb4M\rGCfSCtKALfW4WEGA`-LCa+r^XjMs-lA"s1tpWdXR.!:B1?E$aZt[9T[?8j0!"-^B! - OW@*pV>,g[9fqQu?Pkp88T2Y%88ps702+fHXe9RcdK9aUOm#N1Q))Nc5b?l=(Dr8EtQGB<) - rl1DuY>'*2f!S3/`uYNe;7Kl`n/[VAo02rc_7nQ((qpM$&LlrWV*:R%UIor/WFtT%e;E]ab - 5O,lejbN=^*=\p`a@anu#S3q%MR#q8Kb[8PMo^]m@I%0?itFB;.N@,>kTK\+!rK!&: - cbfZ(p*J,J?^p$2hi]ZiK&WMrLl;X0CcDg.gHB[9(aFoOe-Kou+W4^W.cBMdi8^,,343FAk - lBPH1&EI'q=j/WP41.m92JXA7)l!F;:EEh(12Z=T("!+0eDfVYN?s7UB;]\'u`U"&kY>EE3W/lrU40C - l[;#9poOGPQ@JRekZH,L;remQk/BfHX7mqm/coJ2sCA<`cL)a\T34NH4h2_L3ZDo6aZ>^Qs - mZ_Y?U]6e'fWIOuro7BKlZi5HMq\^X[R8,rpM3"&qo])"MG):uKE\m*WDI(MF*WJsMu>rkH - 3m?u&!fcP>1:"ssbqY^?eZRtUH#_b0:'ed\`iSko84?`\TmONcq!_<;G(H*pb`MYmW4Lo5F@[<3In15Majof*cmAHR&Sn'162dMcW(8f - LT(E7\iO<[e+!W&*sg6Nhk6s[qeKom.Q?E9Q+Z$\t9FK^6ZNi4V@_rVUNDJQEL5`f>i"#RV - I-%VFEc0\j#mn+;0rTW=@b2L-$@+mu;6k>OD,*M+`.cZVH_!(]bnLHPH2%V)>E_F`?D)egI - :%Jf&nZnmO?/+,cRC-0p7$k!WKs*,=>0EJ?Uh!!;Jh_H'^?h6Q/AXK6N5,;hXTq:/84pB"r - uM0u%EXKm2YZYt%G'^@Z]m#_8ZG]skqeuar3q*.[Afe_IH(`aCfH3eGq-AF0B*_'O[6f\+? - IGT?md@G,kWaF'n#loh?]u%MQ_S=aYdN9OIo9B*GF:mGcmmTF12H&3h.4OPE7u!:phVJLV* - XMh'j1W<54'@[VaZLkT!I3O\OU(i\.4_rm#'"PF?gZ)>n[V - nV6:1\.SfAQYmJ+oRiS:/e*>M15%5_,-:NfECnIh#':Y9RDhZF$BQg4ZWTs48BkhjB?BktF(G=2+f^WHO%-aU[e7G@Sil$3crqGcP - ']oGnWXL''sA[#T03[sE'2@78):F - igunj.@&kPiksqB&237fn4EF]d:6Omb2W?[NZH\u`p^HMkro4m\lNdLYem@!)8l&V - 9Z_s$kW)TcP4"2WD9,gmu^Iqp.!-"k0_MZZAi//=f,s5+FRf%!/6+k[=&+V].uH(`i/fB8mbg'OrT10T7'A3a - i`uBmA[O2OhSO0,Voc8s*H`2"XX/3P+nQGW+pTTqC2T/S1#oiiM.C6@#Y.F>AUb=P)8V - k5-.))n7El7@iNZY8rr=A..%7%4mYR$OJ.+M*2S=X$:f'c7=D?,h,KgjqtF_#iBr$O'rrVT - =:A1,r'Z9SN9.'*&P>=9R7-,#9qRs*SpNWr&R`V$A.%;-3tTdn3>7#%um - ?W=0Nn8P<'uIRUPi]TO$9Bcft5Q[U_89iH12+,?0rq94RCD/t[;-5)r/P7TlnXcBDSh'jh-nL - P%C),>l8Q!7tMY%Vj+LigNP00Zs^+,V@UAji?dMF?_J>08=2BN6\;oO46;Ul.N8meIrJr%8 - f$%4YEBCU(8B<m'Qu$4Ff3Br)$5(f0TAWp!O4QSnh`'GcMPf9GR#@Q]PqX60GD^:F?dU9HoL5+uFMJ+H7rW - uHR?$4Fd`M2Li!Vl9b_QG1-8#bnOg>dc%%L#9 - \`;!X9^(A(T0;:J,!V!(9Xc^s"hSj6>#[(&tW4Pn(TV'G`C/7B(8V`j99S$G@j9YljJ4Ka! - E`>H+2D`q03o+apTtn/-i_SG9UOnat"&jhI"nh>qkX_?(4l;)+YT^?P`DK>I[jXB%'IYATZ?l2c-^_?*bc5CA*`@QCD: - CB*0_?haU`5[a[t^(fo%MC/m4\,Ie(m/q;HK`s#bq8k]_r3Z+(&&<#r(9m!?V$toe:?%*15iSNBJ/ - ^\L@Q_td=*b,PE!_4%>]RT:jbt1XlM0KOOH:kAi0(8oQ*+RV5PRn93<6i('GbZ665@].ejU - >RZh,XG?laq)h"-b.4]Euqe#QF/VZ?e0-nO;Vbpo$CQE"9eVkI,%GDdfU5bapUJ?-,sqF#i - E/FR^Vhk-qs>gUBgF]M&2aNk(k:/G'.D4a\1SCA8Gi]iA/8%).+*`oKLfBI2D;Z_Ie$4D)d - ZARbF>EmZEfQMspbS<:6;0S-E(BEG]@0$md,GoDIUnjshn#)rKNbk1o4n?2PgT8lck6?H=: - ftLP$WJZijas)u.Vu^p4oC'&(#h#HJBq(p^`203.RV0q?MO'3W0(ro9N7E$>,-H8R4,kK-n - O;E#?<X&H:k0>.dBSs;;6Rag.>gX?3/-BY^jc[X'!ZffCsG - jfQR(:r$=M%@X<18\j7bu\XSJPQbCc`^%"5$"M!@cJs/E^[']?dh#[UXRd?ibrbUcbM`3V - 0.kpV*m!hKtn+b:A,<;-6*1(_t56^4!kl[cJ6d#Q!pZ/J']F$OD&H`H(tC,CO,2gFb.b]V7 - miCaHJS<"p)ZZpgsF.dH9Q/+(jdSfjWV40cF"LlT=RZ9G3r3PYL(Z+8U9MMLkBHmT31-OL7 - &qHcig'\BH+f\F!q7d8@L"#Iosg*)b`!:NEd!HI^2ei;H1$/[<6OmA)4`^Ko=9l3_d8e - GN46<^n4G*5hq]*/Eb]!-aXVs>sn;=MDGVIG"iPZlePZL!Dln6(P\ofB@sGkc-;a7(YQ#hB - N*a;&*r$Yp"1d@5_JJK5OJN4;-:UE5+l@F>0jgmYjGV_2r6)iIdupoF'IbDHF - LnoUTf8EbZ7 - S:f<2)3DQJmFZL>M)HZO&c\V3I*:DL(uh0>S'&g)f2I,YM`QLh<[3\0p@[]?T8;fR+Lrm&X - F@D,n"nd7PoK`jeP!WO"2U7MmEE3MW:h\FjMHY)<8'6EcRqscI>/?_=5Ts0&sXB/7pAnGdX - >[73\F - RcBfND<%)B;2DEIjV17Dmjmmn!5&@TMM]X/jD%@\56;C=#R(W-R - 0ktlIC%.,Pu+leG=94`L'7\TS[U:+t+iji%*\c.TJo#^\ZIh4$oaImh5Rm - <.uAA5Dt[?9PcefX*SW5m!*:S;bC(@\ - 6j/(I?_4b*Ci>k*QgW4?ocNRFZ"#A2\I/C1kZhbdLk\#-+&k6kW6.A"h02:enA?F\Y`B;2D - JDtstPFFq@2^@`Waoo'a89tZk5g6Ph$mkT9c[S.3s+/(qI-nO:\hbp_LJqB9IX`Phq\anNB - K>Qd:EUk=gkQHSCr+rTpH@G`qCCasBdTF-,lo):YB?F"Ii*If_i+IsJeFWIqUVmi1SD!XLY - N`dl;\Z_X$4HX^?`L&;lI7BT4uuc:1XD_u(\BEW.14iFJu0f^"HVpFh4(?+9aeo@0*!iX.l2`T,oYW8E\ - 42+^:rCb7@LD6PL=*\IYa>a3P-N@or3o!e\@UkRmB<'^-*^"ao_W.dH9L/Zk^TUNdt'Wk#d - ZTWl!'rYD%S5>-R>;-:X6Z;f=pkg3f/6oriG[.2pPPIG":F:\1M$J^WX!ra"RK&WF#,[3Bn - cBZ$s_k=4KHD(0e"n"ig!WQB%U7T\\5'W+g\=;1J`:J;>r%&$>7UZ>tqgs]Ok0<3:hFHboY - -'PWc;'QuQQN-@qbUD1s.3(;(nm[q - !s]TupX-OK3l<>uea#p)TaJ0%YWu%'n//cEs*BEE+0?sHRr)!SCV$/9:^,QM=$15?ECo'G^t9;(CkQcX/+B7k8=)TleGZT:^/_c=%Ge'SpC.'l)K]ST:2eNo*W4fjYX!^i;c1joIlUdsNjb1d3RaY*i - @u`EWt@>M8qrOEn9V_,pEpXFosd=O-62,F4oD%gDu7ljgXO5+C6e_\!"d\9<7LO3e)SEjBM - 7HtKa*/^I,?)2Xq<3.SJ&Y6Fct>drHRP8Sl+$O"Ze.dC0FXM8utXU/jigG-fXZJEm@`.p7&ihl/.7U@Mp&2$f@G0%rh=!,sB2[]M_iSG_W8tVlD3YT#9D;J(J4`iDufPL - _"J"flc,ke\"d^+F*V4sR=rcY/`(@Wj7K>HrEKUX$r)D7Ke'"VSYLOW*0CqeEJ`->S(%j]gpaGYZ>'KtRBa,gMMB3m(gYiBT0 - :/?95U(GX76R:Alj3,(>'K)P!sU!Q=H=/gm1]U'sB=Y+hMC*Z?LL7-e$F2s,!6Gq8TcmO:?\+f/>>($[L`Dagkk0/@gl2Jp*0QANU#aH9#356Z*[Er4+m":sjtNtT&'oFgU+L/?*;R,G+;Wj3 - ;:!t("3#'>LJhFftbmO?dO`F*Lgf6tSm5MT;C1G(/EW*!Ksj[rs45l&-O - HKNN?,Mk`\>WOUk?ufT'pPI4G\>huq3aXL:RCgi2bE]pGJb$1*H58*t-f./`gaHHC0KJatp - fW"Tmf?.dC0O4u%7B[4?Q7WkerM%q4u>^^[]_]>M;DDOXa8[DmpP.YS$--&0+O+8X - =qnHc_knu;Eh;m]L7(N0>UZa3o2`;+E[$4@!of2U8knA>S-Cn3ZD]X6JT5"/6QnQ'I8$3(A - bd3BXn[6/)nCTg_F$O"V=#k@IM_$\06'sacl(+Fa*@(^nT"uOMe`&Is"pfZ]j5E - 'Ga4^O/o5BF.XBp*^R7Mn"4JO:9Wj^i_b4XeN1l^?EGiHTWg]NI9o?*?i&2!nH-;hau^+N9 - [mN7;S3CAU0_Kpl1%O>H@*3@AW+_X-nGpWO/f/AM'pU/R!BBS - SONQ07KdQ_:QJn3*SASfdleR+`M?a1mPrW/';0lV^CWM,E.YS$-AV5 - kdIeL+IiW\s>GXra3f#rB$GTbq_293ld$MS)CYZuds!l;?N76Ss]5?g7"*k7[5TYY_%d\Fc - #!Fp8`S($3mgEtdg^=oVh.>7p,A\O%Jp?[&i4F\CXcUkG\gS?XD9*#.>3k[e>Fa+"8rVU+8 - ?_nGP#aD;7p,AXnX(8kV>E-((op7 - qS6L=Zo0c(Pl.H*p>;]7Jisn\Qja'!l6fp76Pd9hL(2JhY!V^#*UrPNt^scic7ulFI:[m*K - qCZY4_]=8n!99l)d!2h7KSALX.d8R;"Qt)g%s%2%ZMeh8S>nM6NJ9Q:nkY.4c#Y>u^!"jN) - 5uiU]t+Sns%)HX6@X%gQPUZE[Zoau`LnrM-S^E-tGM4YpaE'!Imn/tJAF0QJi3@H-Z>/2$0 - 1%eElpjpY%O92J_5i4c4cT%0kHLf_nq2bKDT9\hfoSgW4ehf[c,@9s=b9kgaHr)a#S"bPbf - IplQjV$abhHtJLQ0@Ot_?.QY8OYeaYa"p2MI)]c0Rg4jI - c:sU]eo%3$d0iR]Nd'T@=`r5UnXIDa7=>!HX]ghp28R[1#l)m'3?+Qi?Zttns.KYM%:H'-0'mcNt:iN= - %SWWAg\O;k,EX<'\"PI._W+Wu#*mu9%7ciiPDo+b0G#LVa)nnHfs'4SIDul&d/74BHU5S%KJ?X - g>shK!6`\a'i9^G"l\^[9id-pua%V&.kmBeFUR":6HYI@P/XjAMBFnojI="_"i#-goV7U.V - 4r>FGRd8!ARkK4jQ>W>C**$\X("O?nrP_1#dUQEm6YI:lfU@"+..XOn^H2,8:n!LqtS+3H] - ))oLKQm?a[mi3&g+2n]E,`$[S2X]O!_[gV6ig=!_-nGo40mo(-`f/u)DVa-kNm9?rd:IX&G\E7=XmYU2YIWC8 - 2H6jpJ)T3&Ka!(d,:_u(o[2XGo0c^2q'?5D3a[p]DbUjZ]"M?XSAW5#-s\>%Wp:@Q"bL-eM - L,:$mjCcS'4HkXlc]QU^!7MpRY>]:VV>rd_j!>W\5nZR+Q\1Hq:/kV^EA3lc_K3b9^mQ93f^LckDKi/DtluqXMJ$4HXc3#2,al;od;LP]AC5!@e2FI22'iW2Yf=*6`_GqIg@h;#5#q - umiH$4HX`pQ`m#;:!q%rd!e9YaY+t#1GY8:MA.TiYb?f(7%U0Rs:t`E1c6eY^jJOY+hKg"1 - j.Pikr\f>+.@@gM/L]CNL*W&ga>#SShlH7#B6V96?!,!rfVo76Mahck,G5i\)U]cS1aF'mc - NtN"SoWVj-BW%eEkq1Jdp;AV@o%r\dbF4`#1cT.=Or0Grj5odK>O-(`;+OMe`&Is"pfZYt' - bGE0NArYEDSM)X;\kJK$ne5`Y,)s5C+>s(]8UfqCkb\Q'.P6/$pU-fV1n?F??e\5RB - ;&?KKOqlI&sf7H;";H$4HXZ)AVqhR*eQQLA4j'YrN$K]hAqOO&U' - Ul1>$?f&7'SU'3Ea['5cd3/i)psNC^omHPP$X-d7*M#1\o;@';d)Y/=g-\M:+&[o#og - ;m^qk9&fr1e0(_FOX0$O;/2rdqDWL;F"@e5o(%^\m1H> + Gb"0WH$!'\H0G4!#3O%OR!G99YV9Z1bY6G1F`3G_8Wu&UP;J-R-E/$+%USDe:#=a^e[3#(D + R/-S[cq`OkGG;*Qu"H72R^S`0Gon),`i.b5p1h+1`j>c,F]5H3p6G7nEbSKpU<>9FKIB&_E + R/YZ?>kdp9H?B>m:;n-hg_K.u30H;1F]1J-lY,nbPBEe^M:cqDA7D2enA*S__QN + ]1b/l5XMl&,/=k%#"kHegN(36X^"k2$s`;:fm^3NP`O + g'G`r;UTj4rKa!Rh-nIJJ@0*/]$4G0V=9*&p_SS)C(-l&uYj`'T"d%:`/3H:.!MhR[$dVXO + !,7G0_D;lA5['qen-k!SOObg24@,/=k%#"kHegN(36X^"k2 + $s`;:fm^3NP`Og'G`r;UTj4rKa!Rh-nIJJ@0*/]$4G0V=9,KaR/=sUI1+STN209PG_X':E6 + [?)o1j.oE+Db)qG386$4G0V=9,Kann(Y29Sbb+qWrEdY#,.D&j]sb)%$be20j+&e5k5#j0) + /"4gIc[7uE5A_D;lAJ8=_!cb3W:W0:F5mm"$YrMOBPKnX.f/-+9AI=9D3KoJ.$H/bNAk,\M + im@^]Q%#"lCR\R(2r_R$A791br]eT8PU\[434],=+L;F"2c+CT"h-JYka*,bLc!.@M'Ae_c + _D;lAJ8=#t2=l9NNB4=mb.^59e:5OMTs4"pKa$D'k_3b5KtVWte!=h+hi>Dt1^bs/V` + $oNWa0L8"8O6eD)%$be290o7O'C6pn*ZX0RhpmG'GM=STs+N%oA2"H&hIkR8rC:_=@keei> + Dt1^btSo4>lZG#%T"`KnX.f/-+:lfKLU\_T#VS9[I':6"/^i$4FX,-al`E[]oS]:COGs/UY^?C + lJBFMYG@hqn-k!Si/ZU1bM4]9XcI&&:>3I4Of(E%KnX.f/-'n\4g``3_WH3[P(LQf1DCUT4 + 8'[ui^I;1`)]c\nmbU#G_X':E6%s1*Yp[!p^U)#;G^J?L;F"2a8Z$q4=tC!M/?2b*HZQQ"b + hYC-nG@Bm,,S)lb_NT=E_m+K6nEEpg[6)G_X':E6!NX$[a@=IV^qcqeO>>GTF\rqZ + ^_D;lAJ3+7SrNQ#)7TWmnoKa$Cp,B#cA>M/jX.k)h65Hm,H+.L-a1DCUT47_6qCXL'V1;_kghHEL5S + &@o+$4G0V=9.d1gHES$L^he;;YoItGW-l%[ZVi>O(Bak4@/ac7,pWa7_$a_5lCHYXqHTf=PO-nIJJ@0*muh-I?<,1n + )BD2!saQE3t%k&+^*DOGTShnjPcC^LQr:fm_c3;2W2X28K*/o4KT1+p]LG9_HLo7S?8Epi[ + 3;cn:!_?+\sZ(3[%non_QDl]%-"g6_ZPlL_TML.H7JAT/Z'Gc3\Npi@,D"XM?]k:cKlD2U; + )<5UFO2!G%pW0WNrR_f"StW@gg_&pTZH4V]EVCu"<0Lk)rb^>P>Dt1^`=f2:J!Hf75DIQ_RAuV,mB!VB.,;2GF + [;D3SDgqbG[f9)c\-=$>$%Dh%)nk[R>k+ + cd*(Hs&YQ3HuEm)C3"*[e#-XPH6Pn[dbeD81]CnF[RBnc7/7S9DZ!8MR/F4,aoQ!m6!2C@I + IoQN7"q3ra2(,!t*`^i6,(4\JA-(hX0"_mO]^#citFT"c3k7>gs1iLa4a[d]l(4[?aY-<^c + \I>_nX@FcicL.+JFE=SX=@UH+YQ.piS%%1IB]ip1F^\Ka.k/cdE-EO2Zt%8'CY\di=&H<"$hbXM.;X\HFBI0R=/EC<.k.2F*h + 1b";'40k2$s`,"oAc][AfN%E2Be@hQ3M$bFR2=a?Br5UTa`'Yj^K_MhGB!rb`H/W_kgC\@n + '"Gh:H")lp@O2+!2;!cZ!T",ec-F]nRO=P23WP6/mH1gt!IYXtJ-ukT?!u']C\!!G + KoNYmSVq$c]9,8J`q6,h<`1&+'Gc3,%d#ra&ZdJHj]u;!]idp(q!t67.m,6\M4h'Q>]Antq + %gBL/j2?LCo_b1.F,5^!c_(>!%p8'p;7ZF\fj5L/o7$@`Nne[.4UA?6sB@f(@@d!!*jL_ksXsV6eL%D0&/B])/c!Xd'SJ=@n&STs+N%OLKS + >7@(mV.ZgCU==.!eS8j + E5BrRAm=o+8[9'=)q?KBV)$9NaI[j+Ep[F=;IC`'"lP8^GKmFL;)E7'Kl-Kr-,o!Qf8:AYO + *@OtYQ.pIm+%L>-#/Q9otn.G,m?Cr@0*mrfZk?M%>nIVWjhZ03pCZg`jcu@d^NgHeaoSeZp + eC(\D[.Bl,a0D]R!RB`/Blp76@Es=09mloh[q_!P%(_/(9YU:P"I':[6b>,kIN7FuQ_9<`5 + H#,t/`HYQ.qthTJi]mZ*6+n*0dM8fo]e;FD^V/=TZg/-$KVfKLVG?_>JSoocc$Mj5"gB$buqD[S0B'11t1.H9DpC-+sZ@?:f,[m + ib_0UbiDd^]gX!mn9&W(>ea#q"Rl\N"lS%\Pnn?gQQbYA9NI?fT90`R7eZJ7aZ + $CdWnd`$J\hJ-.Q;:"79RpX@.du4n7!\4rg!lBbEci)!3W*$!1_1pZJ6WSkN,#&>ZtV`"VO + (/!B@L]n1op!>`_8:Tld"u\eW@iG;!ROp1[YDIa-a:TD+H8r=AuO,XJAAn'Gc3tqtDbLLr^=qWuabIH*WUm#V.>jNPYa9lG1,f(AXnn53c9mQ + PV/$cL.+JFE=R-.:@qJ!c[G?h?=\^\152BS*7F1aZ_5mh2Q6uN[_'*#U]re^o;ssO'%(18p + [HAd0gQ.\0-FZNGST[$!!g[[WP-[LL7mRiA8MoXtf7b"Ji`)0 + V::*"ZX@>eGk9+U\*HHMG:'Y1<@#S@CO^o9\>fbF.7FB=Nrf,CGpM2ZI6ZE0i-%gi][WPa) + F=d1+VM5OZF+1+RN\I1*RAc=PlM:#Z.k/m[Ka$C6hAZu. + 1ubBSede=UWhj?ZN,uJ7k7>gs1p`>hYUD%&Vt,#W[AZI+FAf<6c_>og-usY\`L/Z_Z]ZN2- + nG@rnoi?a84/#O<)9+c]N@a&q!t67.m,7'J1)rKViJJTgS,(8dQtQj._JqVEL)k(5so&]3; + )^B=VakJQ>H6H.B8P;Y-4Y"31@aK!eQ*B!c[G\>r+'Qcm"nCRFC9F8rAqSp"EtC)[ZtgWFN + -WY\60WlN3=!q9K]D\OUZZ"*%WolqS'G`qnSD'M*S1g.9lMV%n031 + sbm@7'][7\%pl%$VPE9?@VM]lF[6e133(+k\p:'SY + _8fo]e;FD^V>F*,-#cQOkjVHnT]KE.LXO-KH,t7XDfe#o\.=f8:#bp+lb:Ekp"W=*WX>'3S + -,l;.dof7.@0$MXhEspfVL!q)<3K:mH*ia/p@8RX*i0nb$c%mQLR)&$oSYIi4]UGUOc8Kg5 + ^+HB^Ni.Wi<&BlV&0OI^la$M(0f#S9@@1\;di`R]aEP$8T>pBTh#Rc5#GD^JZsY(,)]$A7J + a##.Kt(%P+qTNREbEj[+gf`-nG?glj?rGIFRkQ)O_rKN/V^7ZDVn?^3T*Cm6%3F$4FY3"8` + j;bj_[]>J3`[_i'coq-,1N03b/VW3E&$!$"#/GCd^5^EsJiLFL3HOs5-!/-&0Mk!!6/]rt' + ^od$B*)X19NA)4hc/_`@f\WMqa.4bH&LYeG;0t43[Vg=D%;S@0%G'bN(nZJi#2ca_G.Z>c, + !(RO4rNO8f^*2=_A!F!^-*'^o\f9>>m?;9UYd;D.-5`ZP2cD5\WLLlIA3qGr,C`ALF`Z&:$JAnTQK,28rBs%Ai/=KWu + \B?TWeD9aa7O#`K[NlcKb'o'2=X<P&Wbl\Js + 6"/JG-V(MbBLi$^,V7uk;IgL;/a]:& + i)Y.>`7JYfnXJDMn!5.ghY#uNM\QQ5fF-E16Ugo7?E]:;*UnFpl]Q!5fR2.Th75ECtPI@4c + nB5C!]9+]#2X/58A_KQ\)@?kfO^gIc=:F7ka\WLBH=J6)K`r$0a*13QFqbb6GjW6'Q>5Ye+ + QM8E/1W.t/-*.!Iq;g3^JU58l>]+ZS'njgm3`(-H"2/!*@iQe/-)U)H[0A3'DSqn;(i;po4 + TF=h:7eEJQ#'I!B>+/qimnsYDcN.$L[!*8rBrh^64)b!c[5=[`QY:hI7A!hA"bI8p\%^P+p + rL:8B1Q5F/.M!$"#0F],MD[mW]cf*(j/`Cc#ngfZ4gla:6#E=miACqg;4,@0&?MS%%0^`h + &C1lKpiglD9^1.k+)6:M[%;URk!adD$XM>ZuUHfY#Zpinm'u@0&?MHc2ec(M&VR30:X^aXW + QamRHpBbEM7pKOuN!o^;n6c7CI&2`lL^&q<@X$c%V4LR2.;f*WCo?4A1!PEciO-X860W*[g + I?EU`l\m2pr@Xdi_8T-2UYQ13JGA*tpjLHZ@e<9n@l*->F\UNB-Z6eZEc7Rh;oG>1i/-$LR + 41N4A.$h53e,XF4#Ju?EBrY7,7fEp=?]h.[F-UbphA/oV8 + '-/-$CipF`\;4P/WpofBl'?EBqNh`&0Dadjd_1tJh?>-tN)V%==V5[YVVIDfUc<(Sb91r(\ + LXJ@h^Hb/lBMVCcIMK\i?\Op92\F.^*o.;<8Fm5m^"h;XKPn^Wm=!Vq!HL8p#=2&65@0$MI + Tsl,PQorpI6Tkcq=%"eraoIbQ[Ml_`mjWqoi3't9c*Nd"2:>44# + k3E^.k*+34nPG5'[7(//;;d025S+plP@H=mC8ItW$fDUM<`50r27)Q0g,gCiWa8I.$c + $OJhR"_JF9nq&!8_FS%VOR'8`B>d=2")f#6"At2IG*H(/e67(@p=,aC=_qPg8P/J>^%^8J) + `c9DAlMW*4Ms&*hqb9GE0a?m0I_>`01n0:gT+!$A_bOs7t>AE5'$?RoD,P4AN9JO,%-!jHk + ?of3eoW)5US%tFAL7_0>G]ojR!$=mDP%&oCr9p0pc5Mq5^[X&sWa8.BU6hq.5=24=Bon@3rkU$7! + !eOt3n%P?[7I1kb_ON=Q[>'p^Nq;l<><&MXd1"d03S\j^k#uWB37j0358ZblDg"R"X1DMC& + AhdECK^b^$2*)!c[)m%Z>1Hj43:V-lW$f"?msA\Y\-nY(pI&Ej3:PeJ;j3bdKec\W_F%Ts1+pWjXg#h@QAo)s/-#q3gRe$ + ^3/_=SqKPLqpq2ca=@QY^!PbrT_G_-aJ-.1--!f/4)5TOL&KJ'@'Os;>>V2/5=/ECCSeDG= + ^k%\R-RV"G8n=ao<(ET<8B2>\jne(R6;&Ia7:?3VE,nV"&*Q,#,Lf4EJu1slb@QLiYhls.W + D`Qqjj=\T(4ZLb[S0A<$q=m&roc]\=g"R1r@FGRD).fh!$!t`r&cNo@JZ`06$6IZ(*rOJ!1 + 0!Y]b,b`>(nR%+-/F#d@u!Msg24J3k-#:'Q(E/K;/q=9,dMo9NSp2EE#S7j!=7)UP + fGr:Xpg*fjV:"UYXQ:7d0KGs_?-s + LpWufr_HrIXqP+Sj!JL\bCYurS(59FIrUiZ&hua;o^t/JeM_,9_=o.BD5/7C;(4`Ocq\NBL + p58Lm++8YM-nG@&#PKaV-u>aPd3!de%k&t/>6jjSYUJj$>!4EC"5%7CA.d_U6=9,c>]eeQ?Yh,_LroaH35[a]G3qTjT@B65[roaH35[a]G3rIRH(59FFh=X8[hua;o^t + .JRk#iD,7_7iF5/7C;(4`M3Drt.9[SFF!O$:>a'Gc5$,B#\T;:n,u1)k0EK`tj7DtdLp_U_ + @-rQtlFJAMAlQ+)+#7:@.2U]0HZHpD*@fK/m*Cl?.g8dG=B#P`hd!jM@cY#uNoD3f#:d0KG + s_?-t7m]MrnKl)rGrQtlFJAMAlFmRWr(57`=LApB=HpD*@fK-aaU9N\Nk8rOl$4FZ#nK$)B + /Souse":u;5:A&_>OT6\/_RuVb#nVQ#P`qg!jM@Sb>XTVltI?45PG/-:fm_+:2HPcCgi64[ + /Sh_r'=`lfK-Q[EjCBNO\tsr&+BgX"^pZWek*t!p5FsWT6l)Q-nG@&Vm]8m[]jCU\Gk7cr' + =`lfK-E#fe:6<.$\6;Et@aFK`tlIkfRH-)HACWp#dCS%gi][:>Q>Y_@FtcH[n=//-%32H-7 + cHHX7cCT6l)Q-nG?KH]K_a.-q*TUd+SRIo'5I%1@]M_[P + ImK*<[YQ1ck5A4!J9@@0smK*<[YQ1ck5Hre7YZIX(rnIVR6"'drftW$!7:@^CO8do:r'=`l + =?3U(Tg`Cn8oO[=#P`qg!jH]amfJEa?BYWdk8rOl$4FYl_;%O3$@?')l-DN.*u#K@Psb#a[ + jZ(#o[KL[J3PG5!%Wi]Ib:C!2l+(bg]RI?@0'Ijr73ug_U_7:rOE1.K>I\oEd1B?7:Bu.X8 + ^lVr'=`l=?5s76PbBXP:l]Q&+BgX"^iYF%@b5kPg9,H&+BgX"^iWppKWWn2Fp@:\Hhn^_?- + r^o^t3[Kl*%aqK!2:#DE=hjlgKd/IL5Xg%ob#n-=XN@--3MZRcII\oEdYq"j9n%UJ*6pITs+Nu*.MO'Z@sllEV`$5 + n-t'T@-,(gV^>X#dkXCBkQ/DkYQ1c;[s^lSKe8J8qEke_%>=snji"ULaVM=sn-2B43:+ + 2="qEke_%>=sniTLW,_Nr@VrLjJkL;F!_?>eV?Mo)'L_>6M0n-t'TOM'?n2MJgeKCjp?i#< + "2+F/;E0=*'C"bfBKTs+N[\*q[AMo&eb!W#.on-t'TOM#uHQWOsqP4[?JH@To^/:cgK5Hre + 7Ya;2Yrm1ap6X^!@Du]f*(59FGIJ&@:i#<"2+F+4c/[QpBV=`.WH@To^/:ce%fXnqnYh.q: + rLjJkL;F!_hSIu;/.,T-^%@($i0t5aqe,rBjClB)!0E#s#ApVPY=h[iTa1ATK>P(*f!Vr(" + FrmF4(E?0;ml8E+*rHU-nKlo2:\f\I=%h[@;D[Nrm1ap6X^!@,C9#d\8@Bt?DD83X8^HJHp + _MI + Y#-iK_DpkHWe\;urQobW\6bMVYQ>MCu,8,]+dpkHWe\55DbQZ;#"O\G_I#Pa%j!f1R%\[HncCld<(-6a?( + 6P&YqKa#n2,dQBDnNuP)&rA$$EBI>.BnsP,l)gXk[DP-?EL"_/7db7_)PXH$\oMSI6p"/e( + nRXXoL32>ZrCD?lHC3r4hYr#-\JsT(hnuH!bj"0]]&F?)Q5j/o^jYt4hYsN,lRPM/+> + VK/fTD.]#okQ`e+e;-4qU2:S][HY8YBQ`f\;!90Z + U5`1D7oQlQdTo?+:?M`[i#WN/!U_*5j!!,DpJX#*9*f2U1Uq?WU!lBQ+!o_0@#p>$P/.cF< + NU,4A!!jDSaHR++QU9Hn5Yt"j$0a\9kY4j22pp@M + r/r?1g]!OMWCs&2&jT'_YWsnH7fl4='Gf'Xb8uM6m#_;G7UZ6=b!"C\.aQ"]Ykit+FR*h^`*_?X%V?JP44+D4PQYg;p";#Sf(.d'K16!aMt8 + ^f0iV]'&U!a0P(NuUT/sA2qL9:tYA!f/CMVP$s/8K>XZ[;PfcpO5%2?6n@me01ZEd!l + ns8Xr2?1W3kPY+qe%YRi$"*`/kg+Wgkh+81fR + 2q[3C!;TUaM`NZ#XFUdA+gK%T.TmQ7Xr_I:ODNbU[%m7;BC01!4X + \)$X6W=B$[%Ps6pQJSpYRsd\V@^NpN4**L)^6H[>AM0OmSuO*tikF3fi(AiG%PgYBG-rL\% + dM!^u?TrqOk(V/Gp$ + IGr$mom[-R`#]3b*XZssc^\/6YQ$Z\b?l;NB25YUSS^P69r7I>K:UVr)t92D3H=f]8%?Yj0 + #.*No[&)K"4.[\_DX8g3,i9QO+&qI&,ou+O<1MTn@`ucE"PFkN%-s3YO02u"oj1:,C3N$E#GBk + d+;0C;Us/c_q>u1o5>eK'o5Fqf+@tT.%bgm8'8tFA)]0>c'LmMG:F8br=omb.K]2>i7B]g( + *03p6RJ/68VIpW9`Jfkf:Y&<(@SI[s`o[l0FH>/-_ohTaS`e"ABIdht]#7iFfs&\YHp.,0[ + &:hW`QEL/?^R_rJc%%EPpa*/i)*,"gTDn)2X]ssG1osc^U$MX0,Ja^T!$A(>fZTU_s7E5@E_Zi4DW.K0%jd + Z[uC-?0jcWsPK&""Qq*-=&?GC*@g3k#agXKXU1_8l:1+"kaPFHMBk[lJC?r(,3rA%J:G6#" + a^Fg5!/SPQ_FC_Z2K-n:a@?F<@Uij=3cnK*Ul12bB[#MVI+=VIh$^/3rZYD:5;6\LXSC0ni + W7&@nDKp.P%VjtcHe^>^8,:nH9g6DcQ7pXp.,0SKP,uj07WU!5F=9JA66/aFGg$[G(q_eN; + 7:k>VCc`2e%8o26fBni7K]c2,7aq`laSF?[?E]!:Pd8@?3g1%-5Sk!#"C:@2g0;s8(>gf;o + j_n1e]CA#:0N_O+->I_5JD8p`16WV_%^b`@27lhc:7!X"FraY:tVYB%KHFrZi/OkeA7NWZ&^%-9Y4PB!Sg+c3:cc(@XreS$*g-5/U"2Fc + q*#Eg"6m'D'm%!?eK3.dWENX#Fl6hkN]MRj0dPG5pi?[9*c9[q;l8@ + 8Ca=&i][g=mc5m8liH@_"Dr+b!?iTH1mbP5**2>l1?Ou+*f$s2cfY[u"h9_OQo>!HV/A/2rVo(Si2PVDgfF,>!)VTG@7SB3beJZbp?gMaY?]dY4`+He(]Sfk>Bse@"99 + H;$C`(5.L4`'ESL*)I,((oNUr@%@6Us%f=l_Rl_LY\A^#;]WW+RQOIkeGRh]`)2mha.!pd1 + t8,#&.]m=4;mjeM-np4%8ZUY(IVIU(('t_$js#KY!H + "E$>J7+4+$.H>\`4qdb4["HgN46*B6l?E.j`o#HMp.,/hU36l*OA3U]^pD$3MV + X*Vi/Ac*C%L5sIkc;94V@aHl(3Ur')QS,GI9pq;V7V^DOGQb3d&[Af&um9q$hZr!!Li-$JY + 83*$3iAJ,G-X6]I;9EeZUK:YK_2!4\pg$Tjf<>iE:hZ"9E\NV2PnbP^K_mIq1mh!K8n5GLA + UOj9OQB"g!f`pRAJQnPR![D1.=2Z^^bDAQ?%`WCYTBG8n6U#s_ZBYU5@i&1q#5^D4R(%uhi + GK_sps.o@8$38dV=9R"&@Wr".Skh!o2(r[YZN,Nm,+.*PJEgn9cAYW_oZso%>QE1lKgh)O_ + jF$8me#pb[XM-\$SpJZ8O]g2B+S.?D=ie=JO0Wj+P + T-^m^.UAaK^OP2*6;S^<)UQ(I"@g8?"5LFo^!_?`6(9-8RN9Or;4oK_q]g("q%g#:%'G`.X + c]&fSP!.V^S"5&.rAFe?OshkZa`B@IjQY+?Ctkca:'?Z)-nJ9B)IkNr;Q"$5q:3sJDaj^UL + ;G,\IF=Jm-E32oC&[Ygg@%N3WJ&,4^f6mMTH05 + /*N!Mfo,Mq%I-0dBDukBh]WrPeu%![oTthXeb20Zfp$HPgPk+=noldbNm)rOR6rE-(FHBYSBbKE?i25+13^`QP-HLB#VLk + 5koQ$4FKXq[-+Shin#U9'pgTJDd3,5b!h'Us=JeXS/,"5ep6!i3p$?nWT)DroMJ6hnK/CN5 + >X?>%SC3*OF"+J#3)Z"qXlN(AO!e-LoQoLFN(fp:D/,0Mts16T,f4O#48ZdXEDC#Ts + H^h!I2C$ohuaZ/LhjOmr&jWam5HnQJ=s,3>ap\? + C_ArcIS9Qd%3Pp)/Z+:4MA=pP5e>TXURN9K`r+]-*V=o6UJ//J",jT/SHC + G&`k>uE5`rTkIY^hETP1]I"daQogEr[[bOuTfFI9o;T^_mrnb + \U9K/8h_D"#8auZ?3UO:k-Y6"fb/TA7h2l5L=l>"7Q>F"l/[F7"89#@>MJiJ$n22!5(d0!>d'[jIPahPb=Eea^I.bE#jW9]^q(:h+T$UF"9'>VZ?dA;-:V4)b_G'/(;01P6\.a=,W/ + D+B;6?^f-^8I3QJHV*4E]6@k(GQQao-,9e$%o)X@T'Gg2l+.5MlAqlG_)]=WORmIus+Tq&+ + MuJq@@#)T#5+#jn3NEtg\6[bAMIc?*l/*m0ou@5dUek1/#@C9jRl_K*k!+>Po'XT-3?ocO9 + (-B6et3l]&j]q,J'9^*SE2cemoS-PN%^Bgf;0kh6ifSD@0(k*e!LUlOn7!38&U>PKXQ6%cH + 4u_l-;Fh5X81K^c.=A7*_O[h%D1'*?Wdk\L7Qi-OL,!$4HXk>K1bAM4>C^H?`>MD,VW'Y=R + P?l(C7C!!n8^r3aTkE`UDBqe9kn+4QmIiN[a(ZLfLQONo7*i2:4Hl"o@NN"!&[a.jVBFooi + ;!.>OsnC^^'aSFnc#94NQ[R(jY5mGk3=8d[PU_O4Epag_/gVq)o*BRfErQf!/MX1TB7#eo/ + "6u_Hro=+.!JL^T\&'D2rp3R3h(@uapI&"RT-fk*!1ophJH*?Sd&4.^H=Z-N1sVl;k'q>G' + G'g:]L$$-'o>)DrIlm6oWk^i@sJ79#mq'=piBrN9\_F5E6"0Wn[u*8nN\239b\Ai1'9p9qEk'fFFQQ$]6U\0ro$BTk1L:#+40B@a=pO&sZnS_BB2gm$+\6OX5 + O$4H(oLe6\*lj'9n[S0X2leQfY%rbki(=;`FKU+$h?JWB,ES^;n-"XWn:._[mB4G%M!SiCG + !pXf"gln.o;,X]o[<5tY;r-0.712b3:XcK34n(]Yi3t*cT^GH"On8MBj4aof>=8)Kb9uGEI + V#g2=RX9#$@otpJM>$\4[XsGDJn>#Tu]\l+rfes=&p5i)sPNR^hMoT6RT2_VWkl\Gk + 3M7AXqI%-B]MAMTM3'hA]H('o`L-LU-R3V3+?[>@K\PB]J3^(WqXlK]]-NpN,VQ,6AiV]JMP*\4mU]1FK;X\]dKg@G#-Vg6+ + eSFm=@I/dT(d)!_K)i5bP27EDM?"R?a%4Io5"H#IPA.4;?H0'pkb('Ao`W=AW*B:qgX;VMT + YKrpq#!'_VWkQ45\dbKh9#)o1PM=/<&!j9-)]@=b'hA`B<3X8@6If\6J;]V8A[ktb%s&JPu + dRLLV3aj/2AHI\Kl']2DZh:g#g"[Q6s;'=oP#DI,iYX$S577+a;roGfrpfZ8(K()$t)X4+` + 4Ft!j/.b'V9\f78cOWr'm/&tK?D2C8e0X@_5kNguW9o."A\#/&>$f$dmLXIS('q" + `-LU]h(ItF%1KdFp[iXbnr19i:l(pU2JTajl+L"%EigsSr\u6Fo>2/V*U)d2!$@?)#e/tAL + 4")Wk=PkfhS>)_mlE,.RZLLQd,8Ptr/S"EC.*rG2$O#-J'DMFa(Too_?0u+0B!pH+CqXr=T + ;'.":fnj](7MbraX>"#;=*-*^-?sEqWL^Pi-m(*&7g9(ih!,JH`6\d\Ve$>Mo(e'&H+$bg^ + f)BK'X@b1/.Qm,K!J]#5qc2EE/>Y[4#jBN/L5]rZDC4M8B;p2o*?p>IVmV:S^?PokWs/-X. + s(oEQga:D=QM\@EsSH1op2lA9WNbjO^2KtS[Yo`lpb:D=QM\/E&S+3"CD3C&LJV(q'W.8-k?#EeX56 + A0XqiUr-u>$)efUg*LJV(q'UG5ukI]UlYMMe4lE@CsqC$MRUNFkTW5+rP/.`/\1i^j^4uPs + FO01J@b%-FI.]i)Db=[@TaKRE@=bXRi%]ETp + l@5]qrips=/T49[Sltc%oqOG$4DO+H5$$hg6;rR'1Z%Bc*8[5R1.UfWk`Hc('oi=:#,?NcO + Eg6UFW@j/[[!CJW,LXLAkE]g/(lo/A>`UT#HUdiDJr=bmj"02qr]o>:X"Pk:4i5_?+09]J1 + ;$rA/7l(\Kc!V.B?m6qK+4Rs,X1n[)2)i46'Nj;h).uS=1bIc.U*JtOqDQ*PJ#c$%gROS"nP'Froa + =nf,pEjkL6%EU0^VXVU=UaW!uq>c5 + mDV$hF(i + JMIYkebu@,7'm"lA%j6t(WX944mN^3tphZfn-`Z\pDi>G7!mj[jX*QY+>hP0mV!Q,3U]1Z- + ,6gU>@*c_?>dlIO\6\*.slqCkD>Q,uiEnk*>An6'2ZSmVD)Q?j>&%_B!T6[9;5 + kt86M0khAEeJ`P?$GFjknYN3ZLh(fm0EcsQV1.W+;I"Qih"JGI[GEm?$H+_I#AS\;dKAiRX1bmWCEq=sQMWKgTC[.s$eQc9aI=> + m1l/HKtM^f)M>^GktMPE2p&'cca41k-eR3f%4/)!"[E;Lmf$\Zs[9\f5r:HYQs7+Xkr2rnn + ^)36_P]D@VE(0?ep>WodIiYC4r[/S)@_Fs?'.a'EmT,RbCnc+udBB.!_l9Y&/ffQMk`oBK* + TK_P4V-Ja7bj2h`jIFo3/eBC + 3/Dr$4EYfB2%*eFD5+T)LA+Fl:SZKfd,89diL\o^0IC?lbrYcX.6k:.4c#[h_B,sr;"%VCZ + pVr`nU6L,iuW0<$1o);knFom?5<34FBt`MgElK@FNtHkV + X$Z]:AF<4n^\`[d@l!b%:,g-8N6p/"PY8$3R[ZC$qH1.H]Y_`&;ccs^8,]K&.'Z@TjokpY; + nIjRqWY+@t@e5riZS!a`6;18#Q#M5fM_n6b&T@?uT`hc`fKa#=?_mFoj:[b9S6LPWUQsqd. + YK6n3_]N3cQVPhm9@R9OiSMbC_?-G%>nctVSbmq`#JG3Xn6CmnGts*mc[U2>75Bd4Pf!m]b + (9f"^0E*<&1Z"p\7`X053a0\:0$OfRpZ-9fN+SSQdX1$@#'"Zh`!V0ETQ4^$4DO9djFKc[] + H*I#JJT.F049r]IBE%.fbW08md@!/_W4]\\ZC.#mq&nh`"9bZY%G^oaZ;JjL&\[^AsK^l61 + ZoC8IuKjV%An-Ic*W>V8%moN.(Tg + eo/ojUr7C#erS^ER0uc1fD#;?FtT*^;WK'.jNQU!*?\9>b^B?EfXS+'G^t>a%b@\e5N-m$g + >"Pb:B[VlM+u5;Qg0BC6?^Q)3YhS2l9/.a;'0QGG5^,J?kJ6n/@Cadpsd:098ORD + CbJ4%VK99d.HZSga[$^JR1TsE7f4[8r92"^qH+?Cc"7dEJ\W#c4(;(6o5k1,s_&el)e^SN: + FH1Aj6lM:%fQ%DLWVJEGq;Ju]>eO^S?b9_%4Hdo6#.>7pl--*qeYJ0;>#>FugF4+sG2G+5I + ;SYW*A/@75/oen+jUr7C#f!>cmQ#;uM0f1!&D:0qI?MR.]e2Iu@tA/Z,bn=n3R$gb6Z`3SP + u:^,<5t<8Vl\+O?\0^Wh`ok)b[-A9eU)jX?'>0so6P$CF>&tR:fnk<*].k4?(F3(ETk#8ZH + -M'SsD+r]g,J589]h2^D:^Ve8Sp*mk7NF$\V,*e,JUc2+p)[^5:-/]6W-RS^a]k">r`j6>+ + If:-nGoNh_B,sbaCW + EX66Ag7inrQ\`GS7]cBY^"W?ATY@[1Hnn#?7K`sg2cV3-#Eb0anlM9[jfb04B)gd`RL3c&1 + ^8o]:@9uaE1:cZ3>.rN8Ka#?R%o(iC%M..$;fmJ[ZFU;'VK>Zg#hj8s3Yscf];]2]`4F*VW + io2*$\ZQH'DJk1QHCoSNo3)Vb%.Hg]Uh2%K\`=9('osM-LTQnB7]bUn_9$2bA;W].-"MW;`c;8#Ns + 8V8m\"D80H"]dT(;Go=m0s3F5sk0br0XQ7_5SN"_/*5,BN%gqaS-liY+XHUsiU;OT^%\c-X-(7#iU\?/pT.P`?=/H#XnM;mX+^+5">1;HP%MSi8" + >[!t,[b\Uqu_RJlS6mu"*6"_oQb^Y4Z)E&Y*VoK`CVS_/=3kkCnCJr/f + k?-KY]V]pMA_.r.3n=i`BugA9V%"X%+P>UuHEOH_R`0)"]Y)cae+2\+BW;(QLUg0s8]2LjF + 0ka*$Y20X%B]T%mG[7fC;l!"'):hUl-lP_p+Qf+lLLbe'>$NL73k5`4mAVG3]UP";1!# + 2qC=-P'G^tea%b?qFD5+L@1ijjp$5@Uh4@dt?.A2(nld<>f^X!=.g;!4^W[<[@=c3uC>thg + Isn]t0FB5G+W49S29PnV8\cGGu!L% + o@#!rZ0!e?Ai0]g>!7ljV%;U@=_h`XtcJ@giMc11@8K_'K$WK/Z*`CN,OhPoaI[s>3Y;kXPkl:Qf*7I;>_!%)4(deROhi,*HqHL-*Up`R,[u(R5^B$O#3`'DJ`UksMV + DHGj9OT90`N4\@jP1=0H996O*h1^AN@`'$t"+P7=Z2=^Cp$a_9QYgcr]Bf4,T2bUT*P#=lZ + j5go-gF%\c\\pX.=#@7gt\P$&tR*F?NfaUcd>M0ku$5_.*6ER/"i`E;L^&k + #es0=2S\75A&[9r[*A%!J#iZJ8TbTt`sd_?'bhbJZOf+hc.K>P2!sliqg3FAU:^fMtNcLNt + K*3@/Lb-16W,!^ZKj'DH$JQqNcHCQFU..XZN)`GD`@?L!4O2-6):gq8pk%uR*c5YsUYigq# + DH>:cfi&Z@,Y/PWM0+Y'Q._M2qg\GlHCM+E^^Na!M@=b'iA`B:oT&E&hqhbkuMt(+%A(ua6"lfs.&Pble,Rt0,>)Erss8UCr-~> Q Q Q showpage diff --git a/testfiles/cli_tests/testcases/export-area_expected.png b/testfiles/cli_tests/testcases/export-area_expected.png index c9ded26144f53abfe47042bf9069d16986eef90e..e5029aa6255754184b7c181453ffdcebf6175bc4 100644 Binary files a/testfiles/cli_tests/testcases/export-area_expected.png and b/testfiles/cli_tests/testcases/export-area_expected.png differ diff --git a/testfiles/cli_tests/testcases/export-dpi_expected.eps b/testfiles/cli_tests/testcases/export-dpi_expected.eps index 0b2157441d6779cc7f274e0a240606e3d7ff0ac1..51587dce23cdbf8e8762719c8265bb9ec1526a69 100644 --- a/testfiles/cli_tests/testcases/export-dpi_expected.eps +++ b/testfiles/cli_tests/testcases/export-dpi_expected.eps @@ -1,6 +1,6 @@ %!PS-Adobe-3.0 EPSF-3.0 -%%Creator: cairo 1.16.0 (https://cairographics.org) -%%CreationDate: Tue Mar 28 19:36:46 2023 +%%Creator: cairo 1.18.0 (https://cairographics.org) +%%CreationDate: Tue May 7 07:52:54 2024 %%Pages: 1 %%DocumentData: Clean7Bit %%LanguageLevel: 3 @@ -71,7 +71,7 @@ %%EndPageSetup q 0 1 173 90 rectclip 1 0 0 -1 0 91 cm q -0.9 0.950196 0.9 rg +0.898039 0.94922 0.898039 rg 90 45 m 90 69.852 69.852 90 45 90 c 20.148 90 0 69.852 0 45 c 0 20.148 20.148 0 45 0 c 69.852 0 90 20.148 90 45 c h 90 45 m f @@ -98,182 +98,183 @@ q /ImageMatrix [ 384 0 0 -375 0 375 ] >> cairo_image - Gb"0WGB=Pn\c6Z%%44*ud^u(.KMYC`,R3VR5&gnFp9BgFm_na4)sko\Vu^)(,`76nLgk[36 - P9Z$5sk2j#t_(uHIo;7Valj8d58Y[Ur..*%#:"!A&,^uC(f:Bbi",b%L`6$jJ)Ci,eA*1$"+N#!#6)C[pk'/9 - n-As.+,m.L5O\pW?pkJ%JNrW/,AQ-Ir..'L;$2=+&,a8")YaDai"&<,^`W=RIk[T$rkKWNK - +U!1#QDL\N2V:ln-Ar#6k0'F5O\n&C>]X#JNjuK"+T5>r5"1-p]H$(%n>'U4DH$&RNFCCUJ - %AZ^8AogM?k[i'UuSi@73U.K$8?#o!iE*$?QHqo!S9>L6B]jb)4m[rHF^qM'-E9hue - JqHlgBaNQmIF;jpcTF$ir5(&.P[(I))PMnUD&CZP%VDpgH?1"I;M.^SQ0ga@]@*DJT+pW - d(H6ur1nmLQrN89Fe,r4i8[:3q#8AWb/"$2S15cn&56Zf["d>o:/7g&-s-KIZ5[8l0N]'#3 - OMl=n],;U-T1?-L#Rj]:QQ)Bu^Y>p9$md*r&FCi6>sTBrTEUs^Mkr7Q;GCl%/d^A%;l4*t# - "?V,ce=rF_gH/ruIA9RHYB!d@t'XihNK$X\e0!01jFEb@V`lU*Soh='WVE_1s[$otmQ9VM; - +8B?("+T5>r..),#6)C[p]H"6%K(cAn-ArK)YaDai",_t2=LkM^`W=rC>]X#JNrWoeA*1$" - +T6iW.@2&#QDL\;$2=+&,^uCU'CV4+8>qf6k0'F5O\pWL`6$jJ)Ck8&N0%_rkKWN,AQ-Ir. - .*%8(>r"1flpd> - )Ic%%Vf3LTX!WA8891c*#qB*OhhfX8cI:pDpYVr!QCbh#e54l!#M5S[q")B4l-](# - Kc(o,Y-G.@[TZn5j]#`i8M*TK;/kPrd4JCQ[g4(j)h%gs!i1#r^ur9>/R:Q$pmfg=aCS#i0 - )>:d`ugr9Flq^[Jj,SA4EkZKX4]eF=%'1CFEs'kS22P5=N;Z.7j9`6__]/tAQ38TW\q?;\X - Wp[;klp=bq#8%6sl1e1%MDMRN&X,>$Zc@PG-BC^[utWH<4*S=O*;/(4$2H*d_65K>4%5?:qb - +n2PuX,.roqF:G>P.SPE_*ej4aGe1Tk?%TGpn.P/l>P*4Bl:$@fO8+(L3jGtT%@pBK)(!uo - jQEUe[t^EmehC@p?:bpPFJRbJ+rpqGePla@kafH*ePg7:7/J:g=4qW3PE#ieb,9[X5[,`/o - Qm-k0'5W'0T:;;[F"1F9Gc>_4UF[>jJ"sP77HG/%la^(39--=\e-8c`K=Eo$8]_9M6L?j*\ - n0+X=WiE2"TL;tlJea[(%<,r..),#6)C[p]H"6%K(cA - n-ArK)YaDai",_t2=LkM^`W=rC>]X#JNrWoeA*1$"+T6iW.@2&#QDL\;$2=+&,^uCU'CV4+ - 8>qf6k0'F5O\pWL`6$jJ)Ck8&N0%_rkKWN,AQ-Ir..*%8(>>=%07fbK7Nsj7GR3GKS]c^[]4T=T4973Kf - Dq4Fa:1mK7"iB9N:jf)fe10)h(<159 - sO^L,+DOsBZE@1>e-X&efO'M[>@t1-t&@4)T@tsCg3m;@V-TZj)"!'=YBKl%dk:fP\ba0XQT,eZ==;3N`]B8LF;$M"^0N)!q^(tm.l0a-r - ]Z';]8s;'dE"OCbG1BW``d!H.VWuCHKr&&I=Y=e-B?B&h];XN#=gu9o\n1.l)LX#FC0/tAm - Lt>53qk-Z]BPAjIU-C7gNaZ7@#qVU - oue/.8tjDi>mk#U&6df!I;709*>mn^4&\?VlmGn>9!Bs+?M.O$\.I+ER@5/X[rnMi",a:5=5;4cmKIb&,^uCn4-f7b)Bm7 - $iGQ?n-AsjnGZ6Aoel4DrkKWN,K(\(4Wao\5O\pW^`Q5Ee"/4(^`W=RJ)HA6r1QXcp]H$(& - ,[%!:RRoXm=-eeJNrW/r`I76Vg^96rkKWN"+Mlb:Plf]NVDGk&,^uCnAfSK'Y!m">2^"iJN - rYEUAhOi?G)6ommLbW7V,)0MUfX+`&PDXi9cqfB6&=unG>Rs%#$q>k0G](r*kmLf:N@u'Bg - ]`NcjLN,IZRZ#2^B6o'F+iBE"_C[dHq6?1jEZ0f0`+NCH[?-:2FS`tNo]U\6(Q3m+;bO#R%4Q%2r5"AG5h96q9M-[p=46b4M7:-HkdGf - oPl"kVr^2:NS42)]OM$=s!3Brq6IrmWgTOg.:cVgMd!E*Db4TNUkRk&X^HG0.,,Bg&osi,= - lUkL)/H1>JOV?W3p%d5m-jiq/c4F*-T>8$GSaP:$-#PT29LhE1YlRFF#?+.VD62#6@cJua! - U1@`b]oYUP]07fSW+6-.n%/ph2jNiG)"e8cpTr/qY55j"0Sp,JZ)*&`LKO7ZVrQ3uYdrRb- - oHRPQO?]8Tq2Th%aSS8;61F(7<>[7E&,]:>nAi]MU$,VlrkKUE#(N`3TC4Fai",`_;Xha'r - V?F*#l_U],mAd7_Op;R#_PA27nLN=S+)2JBPN&^n4-p'+'\95]!`OV^`S76Ir*)rU7.gRjs - Kj*J!:ZFYn_be)H.+CJNla6rn*;s`APX,#R5e:O(*]*2WPB[CbW^JpnO57O)=\kAgi9rn4, - 4L+5?@aW2<*eVh75'7iB,bS+EOG?NP`8O"u;O2WmkRFmd/)L[YQJ-m8:pF8k6"jWp*:_&rH - IJDcJ?qK'@.3rFOn$4-X`U-FK[M7^_cW'=ATY&\t!4o@Us - &H&e1J#JU;'UCst+gZf50+;(+Bkd@jl$K;Ni/\iTZ,^DHa)BtuJB;&G%\*DWQ$S>:Z/fDPf - mi2Tr`&&+BG.K/?+F*[1SZ]m=rOMt?L!$3ro`(p\XXFi/t[>/OY5,pf:qR5h,"+TYg=o - @OHdVZYcGZk/Cn=(Q20g9)Z*hGq2Qt%Sc3$#S[kXti>]#Hd7^i6;.eMK*?aQnmVVod?KE2" - R&QR*_F3c[(A.!706h"f1c&rOqnb9ZA/6=Qg[^JBbD:V"Ap?, - +[hEFU3!%fa))m^20,553)2lg5kfhO!mke?)>leKU=6Xt)fB%(J-PT_c=h-aE;bfsV-RLcabN1=N]LUAdPnX']_P>S@M%A-8eW]p)"jCW&&r'>KCr36HU3qP$er'?2Hr`Fe;a-)M"+(U/C - +)CA$MTCU(5KM]55OJ(j[63!6rj;\q5k#"--MZ%bJ+haTCJfRV5<'+%OM(gIg?HquK)T=YI - uYluLHil:Jj8Ypk9jJ`@SqX/s!])cK+o!sBr=2A3'J"H)f8H%_U??[&$i7o:](*1N-/\-P? - aE$39fprr+Y=1_.%HC%oPr=q@g^nqelESqGVDt>^HnR)ZG$#k=a,RjYT24I_a3Y&q*?C2NO%Jp>;R - qWh6k0C3ci?d`^)Qo+O('D-"WV*h-AcS)6rTV\XM=E,!8=6(W^`lkmV(V&_5^;"Yr4Z#;'( - 'qIJ54$VWIV&QiGMDS)-'rb2UF2"p - 4nqdo^qdO]WZ\Zh!f$i?+4u/5.c'4kJ3^-s!aihgP9lpm1+W-^fp\F7fN%$lhA\UGMYOQ2Q - ,1$^:kb:"$NYQg5CYcf2iIO=p$,TQKBsu\dij31G`T)-59>Y&n-@WBn>AYQFEZqs^$rVpr,GAKK0RG - +Jj1:HLY?rrTB6!EG$a&4Iup3(Ilcl@g.Q35lAsOS0DL1d,l#hcJ,X]jNu%D$qpk*'#MD]< - Vka6Mo,hUBIlnB-i=E5Xi(llAl/pDGV(BT+r]Kj*0_g87'E%*n5?)0*UuhMO-`Gu7ZBO4-" - Fmjeh/56&<:MI5KBslY,Jg"KiCbcccVr_QLHe5>B8>9rj;V9+S^R''E"!N%i(ZJDE3 - 4#rWj)56&H!J8FpKGm9Q%LVsTNF_&oJ?i/]^E2F_VlS3Llj((U+l'GLD,k0MhE\X)ibm>iC - ,+/G:^O#2@cE;U64o>``F(&f>&#l[BdkKWsD>0`bK56=RY/.#YbIuj_`5LTIln.EV-p>=OC - \,!"K?1T7l*h.HTST&lJhgn!_CbkkYc@L2rqYDd=ea=R+=P`]5M9\h - l)e_RELRc$-@:R+GD*$qT"'_Tb?o//Efklr'>Kfsu$iEa<#ptdejE]'``aA%q5`mV"q@g`_O_QitUYZIulerJ#)m-%_s1]K:'IsC&Y%EeaNhC+ - D1+5RQmC#T(!!a+D1+5RQpTC&,\`ki/]^Er,;C8d%=G8KBsfW,L)joGPC+qGPHfX_&mCj*5 - $dJErP"gccAk55LTHirnod6JNnr.r/p+P^Ae[m&3oq7c(FLLE6J'0huA>q,krV/TD6'Dp]H - !W1\`32T?b`Qn!2b9TKi5MccA;%58*nYWTr.[HogO'p:ik9P$J"=ec$@P[L\MY&,-#ikWb5 - MrM/HP^]*s\L'Yl?f_6-Nm3a0,,cs6/**QLi-ShXsJ8[/.-( - X:4D:pk+5)J#)lr-Y/8!r)(G6i"*,Ki/]^EejlpQS3LltDX[na6h!U75CPQi/E`'T5n/=*4 - Tp81q%K2$3#T@%'uHgDXhRlp^mEI,mAcZl;(LEY">H]QLi-Ad0'@j3skn.e=Y, - PUF"jIIXg)Nn.5F_8dbQ>e=Ve^GPIZr*V-,q&-'EeUnlpE8,ZR*[E>6CG[9om5KHTO599DE - n4oWFrFt:NO22p6Yi&<4r1TH-YoqG7rj;Wn2rG3E=R`4UrX@U;<#RsfoV_(p^;D%gYi&<4r - ;"fu5?ZL.X]N!K+(RUPT+:\'-%:b@rRlXB48_D<'E%*V5NafJ1&]c>[n6-L:KoXD^E\Lu6a - dNimb/]\+G%YucT;Bg@JXKaqsQL8r3\chj%oK[l^t]3Wd*'(F%^KD]BT(>!cJ;rNEA6_EtA - Ah$0rZY]N0Nn\THk"rq':dT5eNK/V,mpZ2Y8g(l)@5DL,kTVppT'9<3TUgjuS&Pqdlc6YenPkV-H^>h4JoURtgI:-^.cMS,"4#qg6,UGMMHE2AdEU,uA - 9!3qbNtL4nQChC\=i=sC(5=ErI=$1;Tep,CcRDYVVm"^qma"t^?SVLN/k\BuFSJDTOiJ6%) - FVnq:G!nP`q?Y'B^a=^Hm`m%j>Ue7q]PZd"@f:2iPVapWoqHo,JQA8jg)s=Sm5`3l-E^4rmdbB*)AcT*dllV;H!"^Sc8EAoXCb`V]VF`5KM]55OJ(jW?fT[re.)&^`R* - *_4PoIPH2s9W'B5O_r2-3\)<<)piA:VBKPa85=eA^+)CB/e:U]h#MEPT`u=kirssV;%oQ

@2a:;W.re.,'_&m3[_&or]*)/I4RF/1@I - DI#$]\niQIld5I48_AO$iYtTVaKpc_]P\Yi/^ui3qU]Zr':YtrWr/Z7.Q1piWF[lP(Gn"r< - Z4,L-NbNK0KG9OmqiDIuohF5GJ(mD$Qiq-s'VgIn#-%?9pKq.^VnumNM - FbS%sC5R&3EtU;9=7WNL.(k[>R3=/A`(&GP"c*iC#%Y=6Ha43 - +*h.qhitVjRK,t-S[9:8hNi`WlgJ?ko-!33[]0tU9fb;OnR8u9^h6"PBloekLT*IQfjul5B`>Drqp - HYO!a]tS/a5.P5cfLNUn1b_3#6pdR2+_jAO+NuJ#MA$Ypj5EURoX$?%pI\/5"=or\)SoXa4_6pk/3[R%t"L;WhmM+Q@ - N">hP8;1I"DdbkZ6Z1[%9/c'N?ru)+apk,Xnpg]6]gb62KJ)H@hJj6^-:O7:Ir..'># - l`JH5K,M!R?a30#Q?+opg\CEBPY$F#Q?+rpg[P-PT!0YY6KL7O"u;O[cJ%uLVY)-UR9u#%J - `qu.r'@Z!e9-h@Y-g&jH$H=pZU!Lp]H!['E#*YIi=dpi=Gi`;Xh_-r2H]uC2%C55O_07_&l - nOVnTDWrkKV0"b1@*T*f'sbI7S'i/a%Q5;qI2rH7$/r)$eia85``1R,ip_=XXDli&u+nAi8+_&m=]r]:ek-BKAar?b-SXRFg9O1ZR)((>\a[.nA.L]/FtP$OX'W - M$+2ls\QEd.p>[0&a/gEnTc*?LP#]h-]!1dfB+CF;Bu[SXhet'7NZLp=Iik/)>DO\TIf?VB - 26T,Z?;>VQVsLGMEaq,3':RVG - rN/_BG^l<\+8Jji=Nde[WEu@MSBF$=+o;>=3),o1[):W'hO]";-7=-3*kGip&(_l9<5"[8T - -(ijHLTnj6iCK701L5J)X0C_El8bWHo]2-]#;-=a#Qe1A+W7*Rsmd`7JXNB:7>olV6Q3IBO - QreJ$^QiP7Adt5L&l3g>'c.aJc0g:9h@7oKVcGYM]3TU#0p;n*k[G.tg.\k0MEA6V$NSq*R - dto#DonJIHOUFctP"R#Sg=Hja]sBaM_7DX3Rq[d?kITqo5q)?.Dnp]H"pG^oAYqDJfbJ)Ck - 8JNjq@Vp&9:kaT$[^$l:+^`W>]+b/bqle4N.r..*%#Q@iB+8RX?l9X>=r..*%`tJA.[QOL% - 8+6c_^`W=r]0G#\-beaRJ)Ck8JNoIlVpnO8YeV@U"+T7T&+@hCJ*[Sni",a:5F1f:4[4+E5 - O\pW^`V8.0n7pQd1\b3%"I1Gr..(uo>fKkm*J&V&,^uCn4+7D.V^IpH4]iRJ)Ck8Oi8CK^L - @KJ^`W=RJ,'#MrB4*S"+T5>r3;>J&tZbo&,^uCn;$I.M8#dbV$[.:+8>qfIX$?5!6GIs5O\ - pW^`Nm3MGJA#2uVh>!!!*\l5ilkl?LOrM)UoOS!DF:rK*]QmH2-HQj,!Oi.*qs - P(^U.F!c.$:(W3*#"X5p'6Bl=dD4IL4F-oHfZBNjU9O^J%VfH:-6i\/O>S>j()gWgb62[ - Q6$h30S35&B%6+EeUKaXf8i^W/A5!EkgH+]3Sdd`2fe*&iSMKLHh?dihN&Nd6ruOdSuSC[T - 8ZVLg^!aflVCjZI$)kc*\U1sR/Hg;f3d;0RcL@7R8,c\3k/O:-U8rT8l0B>[QV;J4gQUoUG - >2W%kAaal11:3Rtr2!D/>f?N02;UlFL^SLm,P=J$7oSX^7Cl37F)LWq39)p%"/Bs569PRJl - Yfq.&7n=Gd%(&kF'n4C=K:5O#R=s3D6O)ko9iYGfPSagib.DS*>/XWf?lb%umpdOWd(=q1C - DcbLXt"+T6iW.@2&#QDL\;$2=+&,^uCU'CV4+8>qf6k0'F5O\pWL`6$jJ)Ck8&N0%_rkKWN - ,AQ-Ir..*%8(>r - "1gU#QDL\p`jP3&,^uCn42!D+8>qfi/agf5O\pW^`QDUJ)Ck8JNok5rkKWN"+N^u/H4-Xe] - [&M86X*foWt(5AXltMIc[2_s7u+2s3?H,)Ws%sR+F"n7!`1Wkm4D;0XIeXG6kJQlM`Gc]7C - -`_&e4VqG/A"VX9ekBklSU%5AgW'&6aP>pZs+I`pKIrs54GI;,M2c^bMI_=?a!6T9d2h - H1EnhbaL\VDi]>NJP;AhMRM@YpEkpb`5,2KYH!,/tI)jSG#'g[skV8/4*FXK=JDP1Wq=San - e(:]T1HGfhn^=k4Qjq_,jHmK-q<%Z8hq7s`jF4H(2j@7BP@pVfq\==sdIPEF\ek(tZ+2$3> - 1f<$dR!.:la1ent - k;b)ZdO4g&]3OHl_*4OTIeqK;dN7628UQLt,i>LUuke_8o1S%IY/tWpNF?a - >+FGRXZm;(cc7l<.@l4(`;,H'7Mg0AK48m+c$Gq--6.49qEKD,jrg372eRYtr^?!+Ka8p[N - kXT%eGQZGOVc4/nN13Z%JJgFhuQn8YN[/SL"cRYUT$1=>=in]48K&\\-0tB$/U3!&cN`&5Y - M7uB(L6=<%=]7slo:opoP7uM!5OcbR&)DR(-9VsZa]NK*Y0'Kl9/c75bNi9YZ2Jn@'1%dCE - ::U!4*q9qPu0%l&RVo$RN45S(*:]^LWj*F[[>abd"Ph4r6"&WLH7`G8JSEiPBqr`\Wh/l)6 - I5dOCEVm-PDB(/kgXr"VZ;Qg=O+^p]H$(O/RUon-As.+&N,ii",a:5GAA]^`W=RImabDJNr - W/reMKf"+T5>r"1gU#QDL\p`jP3&,^uCn42!D+8>qfi/agf5O\pW^`QDUJ)Ck8JNok5rkKW - N"+N]Jr..*%#QBMtp]H$(&,["sn-As.+8@'qi",a:5O_1m^`W=RJ)HBdJNrW/rkKUQ"+T5> - r..),#6)C[p]H"6NOASG<15(b5KUt>h:]:;c@3kI6$mM<+[&joX]TRLH:6-NaQ9a[Ae@>1J - nNC;RC=Sd&[MWa$eATgkC1C_JH[FQbH>L+\GfaW^.=[j0sLR)W[WNa2EGmS_o.>1D5b^Yc6 - 5=lPWdrh>+U$qjlD!)#<84A*4g?M'O#]ph3n^e33Bm(Q3a4]bC\(EbSHgVr)rf-ba+i+\hS - Nk[]A.FYBY).:N2Y3ruERbRqLT-a&!tjf(BOq+8B?X_fq&oDIKj;,$.cG?LF.sn8^&fuH - 5BI[KhLh3C=^=s5;e':51[0c+`C9VI:3;.UI3Ca_jQZ6FC%LPgB9oaG0@?+celFPG-Jj^k7 - _2aP2ficj6C0p2:L9")&)G<;=[hZBoqVgK]"\n!r!I0GEj`IUb_U!)psIOt/a\pj)9N51d_ - jRF@R`9spk,i+JNrW/ra8,'r..*%#:"!A&,^uC(f:Bbi",b%L`6$jJ)Ci,eA*1$"+N#!#6) - C[pk'/9n-As.+,m.L5O\pW?pkJ%JNrW/,AQ-Ir..'L;$2=+&,a8")YaDai"&<,^`W=RIk[T - $rkKWNK+U!1#QDL\N2V:ln-Ar#6k0'F5O\n&C>]X#JNjuK"+T5>r5"1-Abr12,(DH")B&M4 - iracbehbn;:JJNJruL\P3X0IiO@H/,Q`&$MfB"e5JkGY]k0;:u&OFE+,M#(;2ua=Q<)sJ~> + Gb"0WGB=Pn\c6Z%%44*ud^u(.KMYC`,R3VR5&gnKp2Q7Z`&D[F0HFt^*I:WdZt^9],XaScL + oN'NJJs^89a14HlgALhd]2?KYb?*knl:OVYAefG'qBe?I+8CICT2DI + M[:\J1-)3\,s'V'Jn-As.+,m.L5O\pW?pkJ%JNrW/,AQ-Ir..'L;$2=+&,a8")YaDai"&<, + ^`W=RIk[T$rkKWNK+U!1#QDL\N2V:ln-Ar#6k0'F5O\n&C>]X#JNjuK"+T5>r5"1-p]H$(% + n>'a+8>qf0VSdN^`W?(&N0%_rkKV7W.@2&#QA+!%K(cAn-:1Qi",a:58[8Hp8EuCBRSCQN% + U+D`nFSu.jCok?Y\AJLCkPX#Vsi%/`9!l-$(GWiPEc=>a%qpYT\!,_DE*Je. + danp)M$1q,@^:MF-0X\CY^2;IMuY,\e11.Rn!UZ&&pa]<&Pb2>c_.9RF/Zp#a + eLjE[X/^;:XDef_.3eHQG!$c(MB[B'/Wn02pRFO7HkGiTebEGlI;`HqAG^)..lo4[RnO`A8 + h?0CFpPs.$Z=.%W](MEAF'9TuH==V+7=gAa/Ic%_H-#0mQ3T)#'")<6NLd6(>WFfOF7rE(O + 3N`FcAQ;#YKefMQ-iO(j@X/[kQl;%i^_11d9!,dNElJH$p(7MFcCOse_,djM*qo]dn-Hugc + IiQ8FG./@u'In,!,]@rm-(#OrbWPA_>.$rB?e82ij7(]EJMqTh9X0i`2Ll`9k + 'rH@b^Eb`.kBQ(0@(SiX(2&BPZ&MohTgQ6uB:EFW&KjOE69Ppa]5cdDVZ6R;ENK5V7W41ln + f%0==[9uNK#@B0Zqq>TuRd3<=>cr+J6!uGm7gure'`@8W[P@M8c`O(OsJ(i)tjHnH'r+TO< + ^`Q2hp]H$(&,["sn-As.+8@'qi",a:5O_1m^`W=RJ)HBdJNrW/rkKUQ"+T5>r..),#6)C[p + ]H"6%K(cAn-ArK)YaDai",_t2=LkM^`W=rC>]X#JNrWoeA*1$"+T6iW.@2&#QDL\;$2=+&, + ^uCU'CV4+8>qf6k0'F5O\pWL`6$jJ)Ck8&N0%_rkKWN,AQ-Ir..*%8(>_"Ji#WPB$Vn)WUfY%kA\V4-P&H$p"=F32\"qNi]k*NW:5V,e#[@m8oXgqCLR2chr%lI'tG + BdB]EVba&AafbD!=ot%2FJhncS]LnLgGG9,Q$8_q+rHE!LV5Ggqf9`u4_=a]@r]Ik;4k^sb + 0r'@X1cMB!eX?[OVclH)C.7EiU0Dh6m]\1T6hko^o('NT/\ToqqekhsV9>4!?ZK$PilLRdW1bFi>p@(]lI" + .8a_p$]Sh2nqP:IJQ"ujeh<)fOaO[DD)EB*R8Adl`_h0;e@&?2=oa":9P^`F`ZVUA&efD8!SOMROojUVF@!eN4[3F0e=ZmL1FT?mM)Q9`Y@Gto + PN`d!I$6q8*T4@kba#LUAKkier0a,C:up"50a?R14_d3<>*f@8IYB+mSTa<+U$36!sNDF`K + BS_&(K*ZA&EJ)Ck8JNok5rkKWN"+N]Jr..*%#QBMtp]H$(&,["sn-As. + +8@'qi",a:5O_1m^`W=RJ)HBdJNrW/rkKUQ"+T5>r..),#6)C[p]H"6%K(cAn-ArK)YaDai + ",_t2=LkM^`W=rC>]X#JNrWoeA*1$"+T6iW.@2&#QDL\;$2=+&,^uCU'CV4+8>qf6k0'F5O + \pWL`6$jJ)Ck8&N+V)UQK9jS\Xm5OGj`GiE>,T,M)kUggC&(iG[.+Deu`2jO*BHJ&^ms91M + gM5L^72+gUl!D?$up%_Xdtctph3qm//+X)kq[.S_9M,&#U$?.aYiBhfUoL`f'CTrOfdg/-% + N"M7r[qeXF.Ml@LQd0aVgfW%12rdgA#bU2\G0QPL\;=:2=d1M.YkeH<#SlG0S_rR1S0lRoA^Lioq[Z'1\pN%HJH2aQKlgD/.0/5+3ec6>FHq + \Ydh50j!O4oZXAO4rlgl^?/$Pcr?0Vk2'lQT<%Ia3m!;4.kDYGj7Xk+.`CeY'RZF%s/l6XJ + ^KmBC2:=es9N!gMVnM]3TW0oHO$Mkf&V6R<62gH1/q>)o$UpFRSI9&oNW`jq-Pq3SS6%rO; + M;l\Z'b$TR6^nuD#Vl2EES(hp1b + [_eJOq\NejI1Kl9tn++fX4kIrU>Beiibau(b7ZkfE(qn!Y&>pRm4XaKHNqF5N`dNg,82,AL + h@dAQ!3qBdOkf*?c22DG/(,0[I@4$:-F^"hauoVOd?R\\;Fg,-$EH&#MSYNSH[;gtm$=OQ=E+N1'#mR8-*/ZW + OT:jQJKOMa)L@4EL4OdPbi##D#,/#QDL\$.J;Qe^3(oq&o/3rkKWN,@S=XVbCGEq+WpF"+T + 5>r8IE<qfi/\G!-u!V?_]SXUJ)CjpT2L*:KT@V!+8>qfi"+]ej&9&IQ_2.Q&,^u + Cn4+7D.+/+p^`W=RIt[UGeb?r=&,^uCn4,*\.()>MJ)Ck8JNlL!TaWLWp]H$(&,[%!:O-5+ + _#f,E&,^uC:Z!8SpVI?0qHb$:JNrW/rlFLs,<'[IIsM!a + )ggeGS>4BgPZ.;Z'BNW8S9b;-<>.;Z%uH/T,9>JN + rWo8\lKAnV>ONB]0L";,Zo(K)@'s/YPZgp]H!['E#*XJ#KrU3qrf,'GLDL/:Yh8jmWNS-mn + _!=oK,e(2Fkrn-Aq@,PYbgrfl!"_B8QJN8Tc9q3.l)c)Ad85O_/D_&nU,-`8:=WB:Ld^`N^ + _Ik9!>Rmf9p&,]9inAc1?8LR,#X%J"/nAit058NP1^!_8s#Q@hOnAdlo\Q3[/o3:G;%l/pu + O!XXDXmd6o>b911-S8`?hu"cd9/s$Z=Y]gCC`3.jJj7hRD#SZRSaJY3ld&.t5NbtkF7N'!< + QtG(O7u/8':0dW?P%^fpf;!LVR5nOq=ISbrPIG.pb1R4,]LlL6SnU!p)0MA^hI3Rr]BVib* + '`9?4OXqHVUFackNZ4P+/[o_VC>rB`[7`ilgRSie`?*KSJUn>/5*W2:OOl1s,&ZSi/5lPJ` + 6;OAq?a1@aG)`)ERt:1)/T'V6$lTpWJ0VY[:gNS?P_l`dQoKrs@A4F$[q]8+Q%j[HRGS-h(V\jd^6nVB?cq3.l$jnk6DT'5d] + LBsUUdK>:J#te;P0e5UmI + NJDI-[HsrE9@5s;lD;/6r,5<">Y%JcDI+sk"g)*oT#t6`d*NGBq84:bc+tKQr^_A=K0Mm]_&n7-8:(Jl5=d + NF+0kf9GS^ef+N4J5`;4QSiHb3NX>]huet:F5Vo(:%p':kPOCEf+@fa$0)Z%]rZK/_Ilci?_hs3rod;F<&H3ef$iY7rT-O)kR+.`9W>FJ'ZF8[e- + iNjrh`jg"c^C$CIL6A[+N4J53pFsdG5fFUrcJ-`EPNZlR,CafrHZEVRE3$3PPPrW&HmTF<< + [k5muL2M%F^rFMPLoB-[s./:4gM=%qRGN_iEol9=Sg3rU[TjOr.?GU>Q^`r7%hIX: + (!_i\C;s_7dYLOcY5n>If8hRe,rE@UVm8pcti`2&a.Gi24[au0SFSF.>ckXIRL5jhAK%!A> + ?L"L8I,Bf.3mFWBW+G$rt_i + 4L2j>?(kj6sX/r,#34+*i9-e342Q8o&YO@-/aEiG&?1MWe`u($:j-&=%Ius=+J%F]:GJ!9 + %95^3D+'"g%QA0o'aY50s%CSFE!EP(?;IuoouJ%Fo@YALt9/!65=dNF*sqpC_3r-:o;CW0!5TbQR,F#^J%Y5pS'E,="D@L^X>@"IS?l1cKZn-:Bhp`n%U5FhP%l2775&$grJO#2@C4SGXDrTW5bd,`ODOQGs:.3SC1 + bo[6662YX#SaBLf5;DmOmR5E31[`!k,1$6Mn-=eE7Xs8Trm"a*]Z&!opYW7Q2h,/frDHN^; + O=-XWn;pU%?VSf7rNicNq&.Tf:3r;XrG"8#L":N3b\<6iLF6,q8.VArYPJu@cMCAgYK;b?c + Tme%aCGfBI5*1<31pYPHE,*WD)8=(OK/1PtCr=kL<:Z5;Dsgl`[DI'Xd1^D3*H>Pi22dG$a + &d045k!6-;_NIqBalqF;`&2X)+bo`Q'+4L$q + +);C:O>R/jV"o)m_%@@WEu"ZsWsJ\qo4S*jrMK2,0%LO3+DV8^8Fsm^Q3[>=5DU3TOlh_ZF + Z9'tIh4F2,L%=)^i]S.`n8-MA.??sXFK.=&f(>,jpuq?"+LkHp`n$*YM3CQ#Q>iXn4/p3q" + t[#62]Z%1k1N5*]KcWq"uqRNo5sTIsb1B:$3&$s+RO"p`n$*I/b5d+F`fMRQnVk_=.^/+D1 + +5RQkeGq#-!;#B>A\')_!3hu/$"IJIulMjJ#)nH.<:X:r,IU\+LIf_D-:$7R1Jd_KBee%bAk0KUH4/VVim@#!l6.QVFOl4PNkCHpXVb@$U]" + SRO+DV8^8FU8Tf-HRVZhsT:pk,Xkp`mVI3@EsVe/sa@*q0D;6h!Tt5DoU[S(+&?55Yh=OHP + 2[')Za]k%6Z(*9/##^^'i],mAcDp`nbBpWiZbnV7Y6%oQ5=eA^T.]q,0,GL83e(+K*W>+Bpk'7rr99A/,PR.]L/02qH/b<#n-:Bkp + sUO=rcIj0-M?PYDgoMEUnlpEQi&!N9^5Eoh`QTW87B?,0)60H'uomd+iZh=2c&0am_:!%59 + k?4kj@"ArZHd6kg)?2hdQ@kf)+pg8Gok[s50HKoP6nI[U7jnMH;sT30i*4Y:"MWm\(Ah.u( + SD,="4srI]dTmS>Kk?C]*/_9R\rSk/B(lpDP0!4sooF"DeJsIChHa$a^?DoI:<1khi2KN";Xd2@5LLUeW2SK"nn4Ki\B4UpF!?*g3b/ + 11,dQn4hr3)!s3=]4^A9VW927s+m`FCJ.JDXB4o#"%(O?&-l8d)3R&1\cTu7Ao$.kCE`+=S5Vc)n1%HlL)Sh2)J + =cHtTp3^PKT?H0*$pJ7@>2+;O!p.^p$@d\s"$YLQQj`E!:e@!//%F#>CK8 + rs,Y,S8^>?OH;Iqj#f01[CU-YM%A98dm$9Fq'Fs0JQ<90&..ZObCMle%PLpDN$Kk]qsHT7K + 4-Q7W%5B\STFH;0su4al&`Bp+tH3U+F[*sSMG>YMcNl"nMqS&I;qbm&TkSlp,MZ[)D2:lms + Acn^@9&&$KarT>g?>/FuBa9m4jEJ?^/Qu)cgT?>!Jh4G3QIR"\Q/V,mP8NCjekXIp%?r`WuJc1C=%_W'@8HWUL/l=F0JW')f@IE1MUlr'=p-r38Y>'$CpH&,]9inAc1?8 + ZtgC5O_/D_&nU,VkgaHX#^Xf^`N^_Ik8keFhJ^m^`R+jIk8d810kdre_gu9;+C&qZM`*-lJ + N3N;,$K"K)@(K(:U`[_B8QJR,F#/q4hZN4F6W*rkKVp"FhuATAnSak*BWS"+QOLr36rcC?f + Tgg=kHqr)$A]a&;hLCi4njn4-p'+,fWd;IA,NYCSq-7nLN=Mt!X<\H2]F+6.bl+Nf=1!Boi",`_;Xh`NrLpt3r..()%K;a$5F$AVD>ug" + j%kYNr[\Oab_c7cF_FgJ*IX=S$uFgN`tnYV=#0P\DqohTMUrOtA?+6\a\DDtpq`p?r6tT&c + Z^7q%KGZ'rBgW38"IPsW4"*&,A9s4U?0573NW]oq>0(%qlBQ(FrnkALs$>jMb&osG28Pm^S + Xijlflc[mQ7KiHN&0DO'hF+AVTr<)$cs-/#\M(Imb*,?^`(7o;Unk(#LdmrHCkU-(V6N^A> + )>Bm\@,Dpm5pf5WRQ(eC@9oY?QI&5JRu05la'o]FO[ht;8]di=u?5MQ.ChN#;6;2uOM;S1\ + ,k9p2]1e%?(AUF%nF5=(hfu'u5cZfD:N5q=5m\;'eb'hdXI1nPGfsi>C$`=G82RqX`0@1Eo + WcL2^eE"td$`t(Gg%50E9:rp_c#&NnO8PtqY%Q>7Is^V*\ + X_m2HfUc\I2r.V;tXW)J"D6Vo\dX]L\eu]?]=Rn2qDK?jG5I8pp,maFbk1EO0t>0eu5]W\% + PCK9+&(\SFY5+eTKD17iQdXqP.q)s3O'mnY`cL:QlT\)&A>T_'p]aLAo6,p!$_0STp4Eq^: + :!>9WpL2Le3a*]\*kb9'mhOZtbO2IqVLN1K%<7W7t4j]Phm9M6L'FBbXj(Q8!b<+"AUW"o< + I+8>qfT"[G#-=$i]JNrW/rkKThq,:U$roi*srk7e+rkKWN@j6C@=rKFc#lK68#X6 + f+;i",a:5O_/YIjFrL+nu.hi",bEqpY*09]6N[JNrYE7/jRnOu2q9J)Ck8H-6,Nl:P:7>,3 + /ArkKWN"+N<0O8tFS!e9,=r..'FVB):tL)S?'i",a:5O_1[[/YHW:4COTLBdan+8>qf,RjL + tf[@i'Ln+HTEW(a>N@'.(HgJ4_I-,C:HXa.:qsSX5kluW28e?LPSK;r;W0)ErOs+>ja%26g + RHuf!:-p_S2VM2P>GARIi_Q.,cYWQZD62h@7)o1L_R%+_jBUYc2?0\)N;`3mIeVg#%lsUFp + \1H<8($U5T^tELs4U!9=hA==[H/ae;Sh.O@<#u4SR1A`%__XsnJF(0F5hk$'-oMcn+c/EVp + e5IrTa)nZ.gNoI!OW>k3p&P"I\ZV4*$<3Il0TOTrPi,\tPRV`h9kJI/@0k3PSNX:4[,1?\N + q$8)HQ1jm6?Ad5adB*Pn\B$SU[o".TVghU.f74dkkBL3/Nnc@]JCHXXGME)+;UL=)/ + ok+S3H*dX+#Y7CPHrK['1Y&<:dgLV1:k7mBL<0+8%nD53D\^.Fr!Q5Gi-'UfWV.3^9O?,X=8c[G`5KhnLghABN:ZmM[iX5q:g*BVtbo + O-:eK3SU8,D,-ZkLWecQiE7(C5C&pTBuKtMf(1gHGT9.l/'lr5P"Yd7p_D7m)-.YRi!*2"; + _,-EiOB7mMideX4"=hZ8/I:-'qK";4oLSBl,#FR'jAFiToQj3-+hiDGV!4S9.K:#A"EZoto + M)R@XQ5L9IH(bMUVFN>!aOoZ)UGO)[Zt-2Ku%+F3m)HXYY-Bs/QV+%&#)M;$sZi(Skrc8"0 + qf8X'MF;`i",_t2=LkM^`W=rC>]X#JNrWoeA*1$"+T6iW.@2& + #QDL\;$2=+&,^uCU'CV4+8>qf6k0'F5O\pWL`6$jJ)Ck8&N0%_rkKWN,AQ-Ir..*%8(>r"1gU#QDL\p`jP3&, + ^uCn42!D+8>qfi/ah!`Lk/97_/F'8.,\u&*@s_o?s)s21Ig]kJ*Vc.jlNSP1QL,m\:%!l+) + kP,IHdQbcLNq+iFRH%t=%U)d%%tmQ,a\ri)^6XM_A(brdh(9Cs!tGULNk8'1.SgTgAH:@a0 + m&,b\?aL2#pYkR&&2F];"t^/87h<6(uPWBO+AQVrs/>+*P"Cs#m + )aQJ*bVka!HY.q8kmH(`k,G_g:o")"XID$b2_#f*m!=KYJ/m]U+SCu?+V4/\g]-Xtt`4Ku/ + MKA>ao8h.WNR!IE'U9V+FHl+7bB.=De!F(aIY1['W;:3DP=3 + Aih=(!4I$FF![/q&[.tN!PU%nH[*qgcIiQXGL&sM>3f6e9o%,[.%J3-t(H.TOl?5">8ALAW + pn]X)kp0-WQ]%@d6A`,Vh1^P3p]H$(O/RUon-As.+&N + ,ii",a:5GAA]^`W=RImabDJNrW/reMKf"+T5>r"1gU#QDL\p`jP3&,^uCn42!D+8>qfi/ag + f5O\pW^`QDUJ)Ck8JNok5rkKWN"+N]Jr..*%#QBMtp]H$(&,["sn-As.+8@'qi",a:5O_1m + ^`W=RJ)HBdJNrW/rkKUQ"+T5>r..),#6)C[p]H"6NOekK<1Y@f5KUW0mi=Q7n!N0lTYksaP + 9-bL>7nMWCXgp@f6=bO\8cPGGEHJ:*NDA'@,-6%?RE>Woa`,dpg^kPGDq8 + o@C#5D=49uEb2AVac7?%LAlb]ZdMOf`c#rk0 + 4gUI*QF9;#X_k21kQmod!0"l-SOMr?+D,^rf/D42)H^\_g/]X#JNjuK"+T5 + >r5"1-p]H$(%n>'a+8>qf0VSdN^`W?(&N0%_rkKV7W.@2&#QA+!%K(cAn-:1Qi",a:58[6" + J)Ck8_2'"@qP,hkJ+D.c1A\o/#QA,TB?\Dgg.(-%42sF*o\>7qHu2'^!c:5s[ZD@=<2M0%D + N;_p#c_[C_tqF5k5PG^TeWJ~> Q Q Q showpage diff --git a/testfiles/cli_tests/testcases/export-dpi_expected.png b/testfiles/cli_tests/testcases/export-dpi_expected.png index cf1781ea307e095cfddb4762fab2b46360079980..b2c28479995e7b7a741a4511a6fb831ede3cd6ca 100644 Binary files a/testfiles/cli_tests/testcases/export-dpi_expected.png and b/testfiles/cli_tests/testcases/export-dpi_expected.png differ diff --git a/testfiles/cli_tests/testcases/export-dpi_expected.ps b/testfiles/cli_tests/testcases/export-dpi_expected.ps index a92caa7ef377a2bdcc68e6c5bab3ee28a3675321..a431f332fc565a8a265a33853b56899c392e4d95 100644 --- a/testfiles/cli_tests/testcases/export-dpi_expected.ps +++ b/testfiles/cli_tests/testcases/export-dpi_expected.ps @@ -1,11 +1,11 @@ %!PS-Adobe-3.0 -%%Creator: cairo 1.15.10 (http://cairographics.org) -%%CreationDate: Thu Mar 5 09:46:34 2020 +%%Creator: cairo 1.18.0 (https://cairographics.org) +%%CreationDate: Tue May 7 07:52:53 2024 %%Pages: 1 %%DocumentData: Clean7Bit %%LanguageLevel: 3 %%DocumentMedia: 61x32mm 173 90 0 () () -%%BoundingBox: 0 0 173 90 +%%BoundingBox: 0 1 173 91 %%EndComments %%BeginProlog /languagelevel where @@ -104,12 +104,12 @@ %%Page: 1 1 %%BeginPageSetup %%PageMedia: 61x32mm -%%PageBoundingBox: 0 0 173 90 -173 90 cairo_set_page_size +%%PageBoundingBox: 0 1 173 91 +173 91 cairo_set_page_size %%EndPageSetup -q 0 0 173 90 rectclip -1 0 0 -1 0 90 cm q -0.9 0.950196 0.9 rg +q 0 1 173 90 rectclip +1 0 0 -1 0 91 cm q +0.898039 0.94922 0.898039 rg 90 45 m 90 69.852 69.852 90 45 90 c 20.148 90 0 69.852 0 45 c 0 20.148 20.148 0 45 0 c 69.852 0 90 20.148 90 45 c h 90 45 m f @@ -136,182 +136,183 @@ q /ImageMatrix [ 384 0 0 -375 0 375 ] >> cairo_image - Gb"0WGB=Pn\c6Z%%44*ud^u(.KMYC`,R3VR5&gnFp9BgFm_na4)sko\Vu^)(,`76nLgk[36 - P9Z$5sk2j#t_(uHIo;7Valj8d58Y[Ur..*%#:"!A&,^uC(f:Bbi",b%L`6$jJ)Ci,eA*1$"+N#!#6)C[pk'/9 - n-As.+,m.L5O\pW?pkJ%JNrW/,AQ-Ir..'L;$2=+&,a8")YaDai"&<,^`W=RIk[T$rkKWNK - +U!1#QDL\N2V:ln-Ar#6k0'F5O\n&C>]X#JNjuK"+T5>r5"1-p]H$(%n>'U4DH$&RNFCCUJ - %AZ^8AogM?k[i'UuSi@73U.K$8?#o!iE*$?QHqo!S9>L6B]jb)4m[rHF^qM'-E9hue - JqHlgBaNQmIF;jpcTF$ir5(&.P[(I))PMnUD&CZP%VDpgH?1"I;M.^SQ0ga@]@*DJT+pW - d(H6ur1nmLQrN89Fe,r4i8[:3q#8AWb/"$2S15cn&56Zf["d>o:/7g&-s-KIZ5[8l0N]'#3 - OMl=n],;U-T1?-L#Rj]:QQ)Bu^Y>p9$md*r&FCi6>sTBrTEUs^Mkr7Q;GCl%/d^A%;l4*t# - "?V,ce=rF_gH/ruIA9RHYB!d@t'XihNK$X\e0!01jFEb@V`lU*Soh='WVE_1s[$otmQ9VM; - +8B?("+T5>r..),#6)C[p]H"6%K(cAn-ArK)YaDai",_t2=LkM^`W=rC>]X#JNrWoeA*1$" - +T6iW.@2&#QDL\;$2=+&,^uCU'CV4+8>qf6k0'F5O\pWL`6$jJ)Ck8&N0%_rkKWN,AQ-Ir. - .*%8(>r"1flpd> - )Ic%%Vf3LTX!WA8891c*#qB*OhhfX8cI:pDpYVr!QCbh#e54l!#M5S[q")B4l-](# - Kc(o,Y-G.@[TZn5j]#`i8M*TK;/kPrd4JCQ[g4(j)h%gs!i1#r^ur9>/R:Q$pmfg=aCS#i0 - )>:d`ugr9Flq^[Jj,SA4EkZKX4]eF=%'1CFEs'kS22P5=N;Z.7j9`6__]/tAQ38TW\q?;\X - Wp[;klp=bq#8%6sl1e1%MDMRN&X,>$Zc@PG-BC^[utWH<4*S=O*;/(4$2H*d_65K>4%5?:qb - +n2PuX,.roqF:G>P.SPE_*ej4aGe1Tk?%TGpn.P/l>P*4Bl:$@fO8+(L3jGtT%@pBK)(!uo - jQEUe[t^EmehC@p?:bpPFJRbJ+rpqGePla@kafH*ePg7:7/J:g=4qW3PE#ieb,9[X5[,`/o - Qm-k0'5W'0T:;;[F"1F9Gc>_4UF[>jJ"sP77HG/%la^(39--=\e-8c`K=Eo$8]_9M6L?j*\ - n0+X=WiE2"TL;tlJea[(%<,r..),#6)C[p]H"6%K(cA - n-ArK)YaDai",_t2=LkM^`W=rC>]X#JNrWoeA*1$"+T6iW.@2&#QDL\;$2=+&,^uCU'CV4+ - 8>qf6k0'F5O\pWL`6$jJ)Ck8&N0%_rkKWN,AQ-Ir..*%8(>>=%07fbK7Nsj7GR3GKS]c^[]4T=T4973Kf - Dq4Fa:1mK7"iB9N:jf)fe10)h(<159 - sO^L,+DOsBZE@1>e-X&efO'M[>@t1-t&@4)T@tsCg3m;@V-TZj)"!'=YBKl%dk:fP\ba0XQT,eZ==;3N`]B8LF;$M"^0N)!q^(tm.l0a-r - ]Z';]8s;'dE"OCbG1BW``d!H.VWuCHKr&&I=Y=e-B?B&h];XN#=gu9o\n1.l)LX#FC0/tAm - Lt>53qk-Z]BPAjIU-C7gNaZ7@#qVU - oue/.8tjDi>mk#U&6df!I;709*>mn^4&\?VlmGn>9!Bs+?M.O$\.I+ER@5/X[rnMi",a:5=5;4cmKIb&,^uCn4-f7b)Bm7 - $iGQ?n-AsjnGZ6Aoel4DrkKWN,K(\(4Wao\5O\pW^`Q5Ee"/4(^`W=RJ)HA6r1QXcp]H$(& - ,[%!:RRoXm=-eeJNrW/r`I76Vg^96rkKWN"+Mlb:Plf]NVDGk&,^uCnAfSK'Y!m">2^"iJN - rYEUAhOi?G)6ommLbW7V,)0MUfX+`&PDXi9cqfB6&=unG>Rs%#$q>k0G](r*kmLf:N@u'Bg - ]`NcjLN,IZRZ#2^B6o'F+iBE"_C[dHq6?1jEZ0f0`+NCH[?-:2FS`tNo]U\6(Q3m+;bO#R%4Q%2r5"AG5h96q9M-[p=46b4M7:-HkdGf - oPl"kVr^2:NS42)]OM$=s!3Brq6IrmWgTOg.:cVgMd!E*Db4TNUkRk&X^HG0.,,Bg&osi,= - lUkL)/H1>JOV?W3p%d5m-jiq/c4F*-T>8$GSaP:$-#PT29LhE1YlRFF#?+.VD62#6@cJua! - U1@`b]oYUP]07fSW+6-.n%/ph2jNiG)"e8cpTr/qY55j"0Sp,JZ)*&`LKO7ZVrQ3uYdrRb- - oHRPQO?]8Tq2Th%aSS8;61F(7<>[7E&,]:>nAi]MU$,VlrkKUE#(N`3TC4Fai",`_;Xha'r - V?F*#l_U],mAd7_Op;R#_PA27nLN=S+)2JBPN&^n4-p'+'\95]!`OV^`S76Ir*)rU7.gRjs - Kj*J!:ZFYn_be)H.+CJNla6rn*;s`APX,#R5e:O(*]*2WPB[CbW^JpnO57O)=\kAgi9rn4, - 4L+5?@aW2<*eVh75'7iB,bS+EOG?NP`8O"u;O2WmkRFmd/)L[YQJ-m8:pF8k6"jWp*:_&rH - IJDcJ?qK'@.3rFOn$4-X`U-FK[M7^_cW'=ATY&\t!4o@Us - &H&e1J#JU;'UCst+gZf50+;(+Bkd@jl$K;Ni/\iTZ,^DHa)BtuJB;&G%\*DWQ$S>:Z/fDPf - mi2Tr`&&+BG.K/?+F*[1SZ]m=rOMt?L!$3ro`(p\XXFi/t[>/OY5,pf:qR5h,"+TYg=o - @OHdVZYcGZk/Cn=(Q20g9)Z*hGq2Qt%Sc3$#S[kXti>]#Hd7^i6;.eMK*?aQnmVVod?KE2" - R&QR*_F3c[(A.!706h"f1c&rOqnb9ZA/6=Qg[^JBbD:V"Ap?, - +[hEFU3!%fa))m^20,553)2lg5kfhO!mke?)>leKU=6Xt)fB%(J-PT_c=h-aE;bfsV-RLcabN1=N]LUAdPnX']_P>S@M%A-8eW]p)"jCW&&r'>KCr36HU3qP$er'?2Hr`Fe;a-)M"+(U/C - +)CA$MTCU(5KM]55OJ(j[63!6rj;\q5k#"--MZ%bJ+haTCJfRV5<'+%OM(gIg?HquK)T=YI - uYluLHil:Jj8Ypk9jJ`@SqX/s!])cK+o!sBr=2A3'J"H)f8H%_U??[&$i7o:](*1N-/\-P? - aE$39fprr+Y=1_.%HC%oPr=q@g^nqelESqGVDt>^HnR)ZG$#k=a,RjYT24I_a3Y&q*?C2NO%Jp>;R - qWh6k0C3ci?d`^)Qo+O('D-"WV*h-AcS)6rTV\XM=E,!8=6(W^`lkmV(V&_5^;"Yr4Z#;'( - 'qIJ54$VWIV&QiGMDS)-'rb2UF2"p - 4nqdo^qdO]WZ\Zh!f$i?+4u/5.c'4kJ3^-s!aihgP9lpm1+W-^fp\F7fN%$lhA\UGMYOQ2Q - ,1$^:kb:"$NYQg5CYcf2iIO=p$,TQKBsu\dij31G`T)-59>Y&n-@WBn>AYQFEZqs^$rVpr,GAKK0RG - +Jj1:HLY?rrTB6!EG$a&4Iup3(Ilcl@g.Q35lAsOS0DL1d,l#hcJ,X]jNu%D$qpk*'#MD]< - Vka6Mo,hUBIlnB-i=E5Xi(llAl/pDGV(BT+r]Kj*0_g87'E%*n5?)0*UuhMO-`Gu7ZBO4-" - Fmjeh/56&<:MI5KBslY,Jg"KiCbcccVr_QLHe5>B8>9rj;V9+S^R''E"!N%i(ZJDE3 - 4#rWj)56&H!J8FpKGm9Q%LVsTNF_&oJ?i/]^E2F_VlS3Llj((U+l'GLD,k0MhE\X)ibm>iC - ,+/G:^O#2@cE;U64o>``F(&f>&#l[BdkKWsD>0`bK56=RY/.#YbIuj_`5LTIln.EV-p>=OC - \,!"K?1T7l*h.HTST&lJhgn!_CbkkYc@L2rqYDd=ea=R+=P`]5M9\h - l)e_RELRc$-@:R+GD*$qT"'_Tb?o//Efklr'>Kfsu$iEa<#ptdejE]'``aA%q5`mV"q@g`_O_QitUYZIulerJ#)m-%_s1]K:'IsC&Y%EeaNhC+ - D1+5RQmC#T(!!a+D1+5RQpTC&,\`ki/]^Er,;C8d%=G8KBsfW,L)joGPC+qGPHfX_&mCj*5 - $dJErP"gccAk55LTHirnod6JNnr.r/p+P^Ae[m&3oq7c(FLLE6J'0huA>q,krV/TD6'Dp]H - !W1\`32T?b`Qn!2b9TKi5MccA;%58*nYWTr.[HogO'p:ik9P$J"=ec$@P[L\MY&,-#ikWb5 - MrM/HP^]*s\L'Yl?f_6-Nm3a0,,cs6/**QLi-ShXsJ8[/.-( - X:4D:pk+5)J#)lr-Y/8!r)(G6i"*,Ki/]^EejlpQS3LltDX[na6h!U75CPQi/E`'T5n/=*4 - Tp81q%K2$3#T@%'uHgDXhRlp^mEI,mAcZl;(LEY">H]QLi-Ad0'@j3skn.e=Y, - PUF"jIIXg)Nn.5F_8dbQ>e=Ve^GPIZr*V-,q&-'EeUnlpE8,ZR*[E>6CG[9om5KHTO599DE - n4oWFrFt:NO22p6Yi&<4r1TH-YoqG7rj;Wn2rG3E=R`4UrX@U;<#RsfoV_(p^;D%gYi&<4r - ;"fu5?ZL.X]N!K+(RUPT+:\'-%:b@rRlXB48_D<'E%*V5NafJ1&]c>[n6-L:KoXD^E\Lu6a - dNimb/]\+G%YucT;Bg@JXKaqsQL8r3\chj%oK[l^t]3Wd*'(F%^KD]BT(>!cJ;rNEA6_EtA - Ah$0rZY]N0Nn\THk"rq':dT5eNK/V,mpZ2Y8g(l)@5DL,kTVppT'9<3TUgjuS&Pqdlc6YenPkV-H^>h4JoURtgI:-^.cMS,"4#qg6,UGMMHE2AdEU,uA - 9!3qbNtL4nQChC\=i=sC(5=ErI=$1;Tep,CcRDYVVm"^qma"t^?SVLN/k\BuFSJDTOiJ6%) - FVnq:G!nP`q?Y'B^a=^Hm`m%j>Ue7q]PZd"@f:2iPVapWoqHo,JQA8jg)s=Sm5`3l-E^4rmdbB*)AcT*dllV;H!"^Sc8EAoXCb`V]VF`5KM]55OJ(jW?fT[re.)&^`R* - *_4PoIPH2s9W'B5O_r2-3\)<<)piA:VBKPa85=eA^+)CB/e:U]h#MEPT`u=kirssV;%oQ

@2a:;W.re.,'_&m3[_&or]*)/I4RF/1@I - DI#$]\niQIld5I48_AO$iYtTVaKpc_]P\Yi/^ui3qU]Zr':YtrWr/Z7.Q1piWF[lP(Gn"r< - Z4,L-NbNK0KG9OmqiDIuohF5GJ(mD$Qiq-s'VgIn#-%?9pKq.^VnumNM - FbS%sC5R&3EtU;9=7WNL.(k[>R3=/A`(&GP"c*iC#%Y=6Ha43 - +*h.qhitVjRK,t-S[9:8hNi`WlgJ?ko-!33[]0tU9fb;OnR8u9^h6"PBloekLT*IQfjul5B`>Drqp - HYO!a]tS/a5.P5cfLNUn1b_3#6pdR2+_jAO+NuJ#MA$Ypj5EURoX$?%pI\/5"=or\)SoXa4_6pk/3[R%t"L;WhmM+Q@ - N">hP8;1I"DdbkZ6Z1[%9/c'N?ru)+apk,Xnpg]6]gb62KJ)H@hJj6^-:O7:Ir..'># - l`JH5K,M!R?a30#Q?+opg\CEBPY$F#Q?+rpg[P-PT!0YY6KL7O"u;O[cJ%uLVY)-UR9u#%J - `qu.r'@Z!e9-h@Y-g&jH$H=pZU!Lp]H!['E#*YIi=dpi=Gi`;Xh_-r2H]uC2%C55O_07_&l - nOVnTDWrkKV0"b1@*T*f'sbI7S'i/a%Q5;qI2rH7$/r)$eia85``1R,ip_=XXDli&u+nAi8+_&m=]r]:ek-BKAar?b-SXRFg9O1ZR)((>\a[.nA.L]/FtP$OX'W - M$+2ls\QEd.p>[0&a/gEnTc*?LP#]h-]!1dfB+CF;Bu[SXhet'7NZLp=Iik/)>DO\TIf?VB - 26T,Z?;>VQVsLGMEaq,3':RVG - rN/_BG^l<\+8Jji=Nde[WEu@MSBF$=+o;>=3),o1[):W'hO]";-7=-3*kGip&(_l9<5"[8T - -(ijHLTnj6iCK701L5J)X0C_El8bWHo]2-]#;-=a#Qe1A+W7*Rsmd`7JXNB:7>olV6Q3IBO - QreJ$^QiP7Adt5L&l3g>'c.aJc0g:9h@7oKVcGYM]3TU#0p;n*k[G.tg.\k0MEA6V$NSq*R - dto#DonJIHOUFctP"R#Sg=Hja]sBaM_7DX3Rq[d?kITqo5q)?.Dnp]H"pG^oAYqDJfbJ)Ck - 8JNjq@Vp&9:kaT$[^$l:+^`W>]+b/bqle4N.r..*%#Q@iB+8RX?l9X>=r..*%`tJA.[QOL% - 8+6c_^`W=r]0G#\-beaRJ)Ck8JNoIlVpnO8YeV@U"+T7T&+@hCJ*[Sni",a:5F1f:4[4+E5 - O\pW^`V8.0n7pQd1\b3%"I1Gr..(uo>fKkm*J&V&,^uCn4+7D.V^IpH4]iRJ)Ck8Oi8CK^L - @KJ^`W=RJ,'#MrB4*S"+T5>r3;>J&tZbo&,^uCn;$I.M8#dbV$[.:+8>qfIX$?5!6GIs5O\ - pW^`Nm3MGJA#2uVh>!!!*\l5ilkl?LOrM)UoOS!DF:rK*]QmH2-HQj,!Oi.*qs - P(^U.F!c.$:(W3*#"X5p'6Bl=dD4IL4F-oHfZBNjU9O^J%VfH:-6i\/O>S>j()gWgb62[ - Q6$h30S35&B%6+EeUKaXf8i^W/A5!EkgH+]3Sdd`2fe*&iSMKLHh?dihN&Nd6ruOdSuSC[T - 8ZVLg^!aflVCjZI$)kc*\U1sR/Hg;f3d;0RcL@7R8,c\3k/O:-U8rT8l0B>[QV;J4gQUoUG - >2W%kAaal11:3Rtr2!D/>f?N02;UlFL^SLm,P=J$7oSX^7Cl37F)LWq39)p%"/Bs569PRJl - Yfq.&7n=Gd%(&kF'n4C=K:5O#R=s3D6O)ko9iYGfPSagib.DS*>/XWf?lb%umpdOWd(=q1C - DcbLXt"+T6iW.@2&#QDL\;$2=+&,^uCU'CV4+8>qf6k0'F5O\pWL`6$jJ)Ck8&N0%_rkKWN - ,AQ-Ir..*%8(>r - "1gU#QDL\p`jP3&,^uCn42!D+8>qfi/agf5O\pW^`QDUJ)Ck8JNok5rkKWN"+N^u/H4-Xe] - [&M86X*foWt(5AXltMIc[2_s7u+2s3?H,)Ws%sR+F"n7!`1Wkm4D;0XIeXG6kJQlM`Gc]7C - -`_&e4VqG/A"VX9ekBklSU%5AgW'&6aP>pZs+I`pKIrs54GI;,M2c^bMI_=?a!6T9d2h - H1EnhbaL\VDi]>NJP;AhMRM@YpEkpb`5,2KYH!,/tI)jSG#'g[skV8/4*FXK=JDP1Wq=San - e(:]T1HGfhn^=k4Qjq_,jHmK-q<%Z8hq7s`jF4H(2j@7BP@pVfq\==sdIPEF\ek(tZ+2$3> - 1f<$dR!.:la1ent - k;b)ZdO4g&]3OHl_*4OTIeqK;dN7628UQLt,i>LUuke_8o1S%IY/tWpNF?a - >+FGRXZm;(cc7l<.@l4(`;,H'7Mg0AK48m+c$Gq--6.49qEKD,jrg372eRYtr^?!+Ka8p[N - kXT%eGQZGOVc4/nN13Z%JJgFhuQn8YN[/SL"cRYUT$1=>=in]48K&\\-0tB$/U3!&cN`&5Y - M7uB(L6=<%=]7slo:opoP7uM!5OcbR&)DR(-9VsZa]NK*Y0'Kl9/c75bNi9YZ2Jn@'1%dCE - ::U!4*q9qPu0%l&RVo$RN45S(*:]^LWj*F[[>abd"Ph4r6"&WLH7`G8JSEiPBqr`\Wh/l)6 - I5dOCEVm-PDB(/kgXr"VZ;Qg=O+^p]H$(O/RUon-As.+&N,ii",a:5GAA]^`W=RImabDJNr - W/reMKf"+T5>r"1gU#QDL\p`jP3&,^uCn42!D+8>qfi/agf5O\pW^`QDUJ)Ck8JNok5rkKW - N"+N]Jr..*%#QBMtp]H$(&,["sn-As.+8@'qi",a:5O_1m^`W=RJ)HBdJNrW/rkKUQ"+T5> - r..),#6)C[p]H"6NOASG<15(b5KUt>h:]:;c@3kI6$mM<+[&joX]TRLH:6-NaQ9a[Ae@>1J - nNC;RC=Sd&[MWa$eATgkC1C_JH[FQbH>L+\GfaW^.=[j0sLR)W[WNa2EGmS_o.>1D5b^Yc6 - 5=lPWdrh>+U$qjlD!)#<84A*4g?M'O#]ph3n^e33Bm(Q3a4]bC\(EbSHgVr)rf-ba+i+\hS - Nk[]A.FYBY).:N2Y3ruERbRqLT-a&!tjf(BOq+8B?X_fq&oDIKj;,$.cG?LF.sn8^&fuH - 5BI[KhLh3C=^=s5;e':51[0c+`C9VI:3;.UI3Ca_jQZ6FC%LPgB9oaG0@?+celFPG-Jj^k7 - _2aP2ficj6C0p2:L9")&)G<;=[hZBoqVgK]"\n!r!I0GEj`IUb_U!)psIOt/a\pj)9N51d_ - jRF@R`9spk,i+JNrW/ra8,'r..*%#:"!A&,^uC(f:Bbi",b%L`6$jJ)Ci,eA*1$"+N#!#6) - C[pk'/9n-As.+,m.L5O\pW?pkJ%JNrW/,AQ-Ir..'L;$2=+&,a8")YaDai"&<,^`W=RIk[T - $rkKWNK+U!1#QDL\N2V:ln-Ar#6k0'F5O\n&C>]X#JNjuK"+T5>r5"1-Abr12,(DH")B&M4 - iracbehbn;:JJNJruL\P3X0IiO@H/,Q`&$MfB"e5JkGY]k0;:u&OFE+,M#(;2ua=Q<)sJ~> + Gb"0WGB=Pn\c6Z%%44*ud^u(.KMYC`,R3VR5&gnKp2Q7Z`&D[F0HFt^*I:WdZt^9],XaScL + oN'NJJs^89a14HlgALhd]2?KYb?*knl:OVYAefG'qBe?I+8CICT2DI + M[:\J1-)3\,s'V'Jn-As.+,m.L5O\pW?pkJ%JNrW/,AQ-Ir..'L;$2=+&,a8")YaDai"&<, + ^`W=RIk[T$rkKWNK+U!1#QDL\N2V:ln-Ar#6k0'F5O\n&C>]X#JNjuK"+T5>r5"1-p]H$(% + n>'a+8>qf0VSdN^`W?(&N0%_rkKV7W.@2&#QA+!%K(cAn-:1Qi",a:58[8Hp8EuCBRSCQN% + U+D`nFSu.jCok?Y\AJLCkPX#Vsi%/`9!l-$(GWiPEc=>a%qpYT\!,_DE*Je. + danp)M$1q,@^:MF-0X\CY^2;IMuY,\e11.Rn!UZ&&pa]<&Pb2>c_.9RF/Zp#a + eLjE[X/^;:XDef_.3eHQG!$c(MB[B'/Wn02pRFO7HkGiTebEGlI;`HqAG^)..lo4[RnO`A8 + h?0CFpPs.$Z=.%W](MEAF'9TuH==V+7=gAa/Ic%_H-#0mQ3T)#'")<6NLd6(>WFfOF7rE(O + 3N`FcAQ;#YKefMQ-iO(j@X/[kQl;%i^_11d9!,dNElJH$p(7MFcCOse_,djM*qo]dn-Hugc + IiQ8FG./@u'In,!,]@rm-(#OrbWPA_>.$rB?e82ij7(]EJMqTh9X0i`2Ll`9k + 'rH@b^Eb`.kBQ(0@(SiX(2&BPZ&MohTgQ6uB:EFW&KjOE69Ppa]5cdDVZ6R;ENK5V7W41ln + f%0==[9uNK#@B0Zqq>TuRd3<=>cr+J6!uGm7gure'`@8W[P@M8c`O(OsJ(i)tjHnH'r+TO< + ^`Q2hp]H$(&,["sn-As.+8@'qi",a:5O_1m^`W=RJ)HBdJNrW/rkKUQ"+T5>r..),#6)C[p + ]H"6%K(cAn-ArK)YaDai",_t2=LkM^`W=rC>]X#JNrWoeA*1$"+T6iW.@2&#QDL\;$2=+&, + ^uCU'CV4+8>qf6k0'F5O\pWL`6$jJ)Ck8&N0%_rkKWN,AQ-Ir..*%8(>_"Ji#WPB$Vn)WUfY%kA\V4-P&H$p"=F32\"qNi]k*NW:5V,e#[@m8oXgqCLR2chr%lI'tG + BdB]EVba&AafbD!=ot%2FJhncS]LnLgGG9,Q$8_q+rHE!LV5Ggqf9`u4_=a]@r]Ik;4k^sb + 0r'@X1cMB!eX?[OVclH)C.7EiU0Dh6m]\1T6hko^o('NT/\ToqqekhsV9>4!?ZK$PilLRdW1bFi>p@(]lI" + .8a_p$]Sh2nqP:IJQ"ujeh<)fOaO[DD)EB*R8Adl`_h0;e@&?2=oa":9P^`F`ZVUA&efD8!SOMROojUVF@!eN4[3F0e=ZmL1FT?mM)Q9`Y@Gto + PN`d!I$6q8*T4@kba#LUAKkier0a,C:up"50a?R14_d3<>*f@8IYB+mSTa<+U$36!sNDF`K + BS_&(K*ZA&EJ)Ck8JNok5rkKWN"+N]Jr..*%#QBMtp]H$(&,["sn-As. + +8@'qi",a:5O_1m^`W=RJ)HBdJNrW/rkKUQ"+T5>r..),#6)C[p]H"6%K(cAn-ArK)YaDai + ",_t2=LkM^`W=rC>]X#JNrWoeA*1$"+T6iW.@2&#QDL\;$2=+&,^uCU'CV4+8>qf6k0'F5O + \pWL`6$jJ)Ck8&N+V)UQK9jS\Xm5OGj`GiE>,T,M)kUggC&(iG[.+Deu`2jO*BHJ&^ms91M + gM5L^72+gUl!D?$up%_Xdtctph3qm//+X)kq[.S_9M,&#U$?.aYiBhfUoL`f'CTrOfdg/-% + N"M7r[qeXF.Ml@LQd0aVgfW%12rdgA#bU2\G0QPL\;=:2=d1M.YkeH<#SlG0S_rR1S0lRoA^Lioq[Z'1\pN%HJH2aQKlgD/.0/5+3ec6>FHq + \Ydh50j!O4oZXAO4rlgl^?/$Pcr?0Vk2'lQT<%Ia3m!;4.kDYGj7Xk+.`CeY'RZF%s/l6XJ + ^KmBC2:=es9N!gMVnM]3TW0oHO$Mkf&V6R<62gH1/q>)o$UpFRSI9&oNW`jq-Pq3SS6%rO; + M;l\Z'b$TR6^nuD#Vl2EES(hp1b + [_eJOq\NejI1Kl9tn++fX4kIrU>Beiibau(b7ZkfE(qn!Y&>pRm4XaKHNqF5N`dNg,82,AL + h@dAQ!3qBdOkf*?c22DG/(,0[I@4$:-F^"hauoVOd?R\\;Fg,-$EH&#MSYNSH[;gtm$=OQ=E+N1'#mR8-*/ZW + OT:jQJKOMa)L@4EL4OdPbi##D#,/#QDL\$.J;Qe^3(oq&o/3rkKWN,@S=XVbCGEq+WpF"+T + 5>r8IE<qfi/\G!-u!V?_]SXUJ)CjpT2L*:KT@V!+8>qfi"+]ej&9&IQ_2.Q&,^u + Cn4+7D.+/+p^`W=RIt[UGeb?r=&,^uCn4,*\.()>MJ)Ck8JNlL!TaWLWp]H$(&,[%!:O-5+ + _#f,E&,^uC:Z!8SpVI?0qHb$:JNrW/rlFLs,<'[IIsM!a + )ggeGS>4BgPZ.;Z'BNW8S9b;-<>.;Z%uH/T,9>JN + rWo8\lKAnV>ONB]0L";,Zo(K)@'s/YPZgp]H!['E#*XJ#KrU3qrf,'GLDL/:Yh8jmWNS-mn + _!=oK,e(2Fkrn-Aq@,PYbgrfl!"_B8QJN8Tc9q3.l)c)Ad85O_/D_&nU,-`8:=WB:Ld^`N^ + _Ik9!>Rmf9p&,]9inAc1?8LR,#X%J"/nAit058NP1^!_8s#Q@hOnAdlo\Q3[/o3:G;%l/pu + O!XXDXmd6o>b911-S8`?hu"cd9/s$Z=Y]gCC`3.jJj7hRD#SZRSaJY3ld&.t5NbtkF7N'!< + QtG(O7u/8':0dW?P%^fpf;!LVR5nOq=ISbrPIG.pb1R4,]LlL6SnU!p)0MA^hI3Rr]BVib* + '`9?4OXqHVUFackNZ4P+/[o_VC>rB`[7`ilgRSie`?*KSJUn>/5*W2:OOl1s,&ZSi/5lPJ` + 6;OAq?a1@aG)`)ERt:1)/T'V6$lTpWJ0VY[:gNS?P_l`dQoKrs@A4F$[q]8+Q%j[HRGS-h(V\jd^6nVB?cq3.l$jnk6DT'5d] + LBsUUdK>:J#te;P0e5UmI + NJDI-[HsrE9@5s;lD;/6r,5<">Y%JcDI+sk"g)*oT#t6`d*NGBq84:bc+tKQr^_A=K0Mm]_&n7-8:(Jl5=d + NF+0kf9GS^ef+N4J5`;4QSiHb3NX>]huet:F5Vo(:%p':kPOCEf+@fa$0)Z%]rZK/_Ilci?_hs3rod;F<&H3ef$iY7rT-O)kR+.`9W>FJ'ZF8[e- + iNjrh`jg"c^C$CIL6A[+N4J53pFsdG5fFUrcJ-`EPNZlR,CafrHZEVRE3$3PPPrW&HmTF<< + [k5muL2M%F^rFMPLoB-[s./:4gM=%qRGN_iEol9=Sg3rU[TjOr.?GU>Q^`r7%hIX: + (!_i\C;s_7dYLOcY5n>If8hRe,rE@UVm8pcti`2&a.Gi24[au0SFSF.>ckXIRL5jhAK%!A> + ?L"L8I,Bf.3mFWBW+G$rt_i + 4L2j>?(kj6sX/r,#34+*i9-e342Q8o&YO@-/aEiG&?1MWe`u($:j-&=%Ius=+J%F]:GJ!9 + %95^3D+'"g%QA0o'aY50s%CSFE!EP(?;IuoouJ%Fo@YALt9/!65=dNF*sqpC_3r-:o;CW0!5TbQR,F#^J%Y5pS'E,="D@L^X>@"IS?l1cKZn-:Bhp`n%U5FhP%l2775&$grJO#2@C4SGXDrTW5bd,`ODOQGs:.3SC1 + bo[6662YX#SaBLf5;DmOmR5E31[`!k,1$6Mn-=eE7Xs8Trm"a*]Z&!opYW7Q2h,/frDHN^; + O=-XWn;pU%?VSf7rNicNq&.Tf:3r;XrG"8#L":N3b\<6iLF6,q8.VArYPJu@cMCAgYK;b?c + Tme%aCGfBI5*1<31pYPHE,*WD)8=(OK/1PtCr=kL<:Z5;Dsgl`[DI'Xd1^D3*H>Pi22dG$a + &d045k!6-;_NIqBalqF;`&2X)+bo`Q'+4L$q + +);C:O>R/jV"o)m_%@@WEu"ZsWsJ\qo4S*jrMK2,0%LO3+DV8^8Fsm^Q3[>=5DU3TOlh_ZF + Z9'tIh4F2,L%=)^i]S.`n8-MA.??sXFK.=&f(>,jpuq?"+LkHp`n$*YM3CQ#Q>iXn4/p3q" + t[#62]Z%1k1N5*]KcWq"uqRNo5sTIsb1B:$3&$s+RO"p`n$*I/b5d+F`fMRQnVk_=.^/+D1 + +5RQkeGq#-!;#B>A\')_!3hu/$"IJIulMjJ#)nH.<:X:r,IU\+LIf_D-:$7R1Jd_KBee%bAk0KUH4/VVim@#!l6.QVFOl4PNkCHpXVb@$U]" + SRO+DV8^8FU8Tf-HRVZhsT:pk,Xkp`mVI3@EsVe/sa@*q0D;6h!Tt5DoU[S(+&?55Yh=OHP + 2[')Za]k%6Z(*9/##^^'i],mAcDp`nbBpWiZbnV7Y6%oQ5=eA^T.]q,0,GL83e(+K*W>+Bpk'7rr99A/,PR.]L/02qH/b<#n-:Bkp + sUO=rcIj0-M?PYDgoMEUnlpEQi&!N9^5Eoh`QTW87B?,0)60H'uomd+iZh=2c&0am_:!%59 + k?4kj@"ArZHd6kg)?2hdQ@kf)+pg8Gok[s50HKoP6nI[U7jnMH;sT30i*4Y:"MWm\(Ah.u( + SD,="4srI]dTmS>Kk?C]*/_9R\rSk/B(lpDP0!4sooF"DeJsIChHa$a^?DoI:<1khi2KN";Xd2@5LLUeW2SK"nn4Ki\B4UpF!?*g3b/ + 11,dQn4hr3)!s3=]4^A9VW927s+m`FCJ.JDXB4o#"%(O?&-l8d)3R&1\cTu7Ao$.kCE`+=S5Vc)n1%HlL)Sh2)J + =cHtTp3^PKT?H0*$pJ7@>2+;O!p.^p$@d\s"$YLQQj`E!:e@!//%F#>CK8 + rs,Y,S8^>?OH;Iqj#f01[CU-YM%A98dm$9Fq'Fs0JQ<90&..ZObCMle%PLpDN$Kk]qsHT7K + 4-Q7W%5B\STFH;0su4al&`Bp+tH3U+F[*sSMG>YMcNl"nMqS&I;qbm&TkSlp,MZ[)D2:lms + Acn^@9&&$KarT>g?>/FuBa9m4jEJ?^/Qu)cgT?>!Jh4G3QIR"\Q/V,mP8NCjekXIp%?r`WuJc1C=%_W'@8HWUL/l=F0JW')f@IE1MUlr'=p-r38Y>'$CpH&,]9inAc1?8 + ZtgC5O_/D_&nU,VkgaHX#^Xf^`N^_Ik8keFhJ^m^`R+jIk8d810kdre_gu9;+C&qZM`*-lJ + N3N;,$K"K)@(K(:U`[_B8QJR,F#/q4hZN4F6W*rkKVp"FhuATAnSak*BWS"+QOLr36rcC?f + Tgg=kHqr)$A]a&;hLCi4njn4-p'+,fWd;IA,NYCSq-7nLN=Mt!X<\H2]F+6.bl+Nf=1!Boi",`_;Xh`NrLpt3r..()%K;a$5F$AVD>ug" + j%kYNr[\Oab_c7cF_FgJ*IX=S$uFgN`tnYV=#0P\DqohTMUrOtA?+6\a\DDtpq`p?r6tT&c + Z^7q%KGZ'rBgW38"IPsW4"*&,A9s4U?0573NW]oq>0(%qlBQ(FrnkALs$>jMb&osG28Pm^S + Xijlflc[mQ7KiHN&0DO'hF+AVTr<)$cs-/#\M(Imb*,?^`(7o;Unk(#LdmrHCkU-(V6N^A> + )>Bm\@,Dpm5pf5WRQ(eC@9oY?QI&5JRu05la'o]FO[ht;8]di=u?5MQ.ChN#;6;2uOM;S1\ + ,k9p2]1e%?(AUF%nF5=(hfu'u5cZfD:N5q=5m\;'eb'hdXI1nPGfsi>C$`=G82RqX`0@1Eo + WcL2^eE"td$`t(Gg%50E9:rp_c#&NnO8PtqY%Q>7Is^V*\ + X_m2HfUc\I2r.V;tXW)J"D6Vo\dX]L\eu]?]=Rn2qDK?jG5I8pp,maFbk1EO0t>0eu5]W\% + PCK9+&(\SFY5+eTKD17iQdXqP.q)s3O'mnY`cL:QlT\)&A>T_'p]aLAo6,p!$_0STp4Eq^: + :!>9WpL2Le3a*]\*kb9'mhOZtbO2IqVLN1K%<7W7t4j]Phm9M6L'FBbXj(Q8!b<+"AUW"o< + I+8>qfT"[G#-=$i]JNrW/rkKThq,:U$roi*srk7e+rkKWN@j6C@=rKFc#lK68#X6 + f+;i",a:5O_/YIjFrL+nu.hi",bEqpY*09]6N[JNrYE7/jRnOu2q9J)Ck8H-6,Nl:P:7>,3 + /ArkKWN"+N<0O8tFS!e9,=r..'FVB):tL)S?'i",a:5O_1[[/YHW:4COTLBdan+8>qf,RjL + tf[@i'Ln+HTEW(a>N@'.(HgJ4_I-,C:HXa.:qsSX5kluW28e?LPSK;r;W0)ErOs+>ja%26g + RHuf!:-p_S2VM2P>GARIi_Q.,cYWQZD62h@7)o1L_R%+_jBUYc2?0\)N;`3mIeVg#%lsUFp + \1H<8($U5T^tELs4U!9=hA==[H/ae;Sh.O@<#u4SR1A`%__XsnJF(0F5hk$'-oMcn+c/EVp + e5IrTa)nZ.gNoI!OW>k3p&P"I\ZV4*$<3Il0TOTrPi,\tPRV`h9kJI/@0k3PSNX:4[,1?\N + q$8)HQ1jm6?Ad5adB*Pn\B$SU[o".TVghU.f74dkkBL3/Nnc@]JCHXXGME)+;UL=)/ + ok+S3H*dX+#Y7CPHrK['1Y&<:dgLV1:k7mBL<0+8%nD53D\^.Fr!Q5Gi-'UfWV.3^9O?,X=8c[G`5KhnLghABN:ZmM[iX5q:g*BVtbo + O-:eK3SU8,D,-ZkLWecQiE7(C5C&pTBuKtMf(1gHGT9.l/'lr5P"Yd7p_D7m)-.YRi!*2"; + _,-EiOB7mMideX4"=hZ8/I:-'qK";4oLSBl,#FR'jAFiToQj3-+hiDGV!4S9.K:#A"EZoto + M)R@XQ5L9IH(bMUVFN>!aOoZ)UGO)[Zt-2Ku%+F3m)HXYY-Bs/QV+%&#)M;$sZi(Skrc8"0 + qf8X'MF;`i",_t2=LkM^`W=rC>]X#JNrWoeA*1$"+T6iW.@2& + #QDL\;$2=+&,^uCU'CV4+8>qf6k0'F5O\pWL`6$jJ)Ck8&N0%_rkKWN,AQ-Ir..*%8(>r"1gU#QDL\p`jP3&, + ^uCn42!D+8>qfi/ah!`Lk/97_/F'8.,\u&*@s_o?s)s21Ig]kJ*Vc.jlNSP1QL,m\:%!l+) + kP,IHdQbcLNq+iFRH%t=%U)d%%tmQ,a\ri)^6XM_A(brdh(9Cs!tGULNk8'1.SgTgAH:@a0 + m&,b\?aL2#pYkR&&2F];"t^/87h<6(uPWBO+AQVrs/>+*P"Cs#m + )aQJ*bVka!HY.q8kmH(`k,G_g:o")"XID$b2_#f*m!=KYJ/m]U+SCu?+V4/\g]-Xtt`4Ku/ + MKA>ao8h.WNR!IE'U9V+FHl+7bB.=De!F(aIY1['W;:3DP=3 + Aih=(!4I$FF![/q&[.tN!PU%nH[*qgcIiQXGL&sM>3f6e9o%,[.%J3-t(H.TOl?5">8ALAW + pn]X)kp0-WQ]%@d6A`,Vh1^P3p]H$(O/RUon-As.+&N + ,ii",a:5GAA]^`W=RImabDJNrW/reMKf"+T5>r"1gU#QDL\p`jP3&,^uCn42!D+8>qfi/ag + f5O\pW^`QDUJ)Ck8JNok5rkKWN"+N]Jr..*%#QBMtp]H$(&,["sn-As.+8@'qi",a:5O_1m + ^`W=RJ)HBdJNrW/rkKUQ"+T5>r..),#6)C[p]H"6NOekK<1Y@f5KUW0mi=Q7n!N0lTYksaP + 9-bL>7nMWCXgp@f6=bO\8cPGGEHJ:*NDA'@,-6%?RE>Woa`,dpg^kPGDq8 + o@C#5D=49uEb2AVac7?%LAlb]ZdMOf`c#rk0 + 4gUI*QF9;#X_k21kQmod!0"l-SOMr?+D,^rf/D42)H^\_g/]X#JNjuK"+T5 + >r5"1-p]H$(%n>'a+8>qf0VSdN^`W?(&N0%_rkKV7W.@2&#QA+!%K(cAn-:1Qi",a:58[6" + J)Ck8_2'"@qP,hkJ+D.c1A\o/#QA,TB?\Dgg.(-%42sF*o\>7qHu2'^!c:5s[ZD@=<2M0%D + N;_p#c_[C_tqF5k5PG^TeWJ~> Q Q Q showpage diff --git a/testfiles/cli_tests/testcases/export-id_expected.svg b/testfiles/cli_tests/testcases/export-id_expected.svg index 6dfe9e703c01cb1d699c9108bf8d8a4f2c548266..a6dc45ffbcceddd7151f573f33a5096a1d50dc59 100644 --- a/testfiles/cli_tests/testcases/export-id_expected.svg +++ b/testfiles/cli_tests/testcases/export-id_expected.svg @@ -1,110 +1,93 @@ - - - - image/svg+xml - - - - + version="1.1" + id="svg1" + sodipodi:docname="theta.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> - - + fill="red" + id="red" /> + height="100" + fill="green" + id="green" /> + + + width="100" + height="100" + fill="purple" + id="purple" /> - - + fill="white" + id="red-cover" /> + height="50" + fill="white" + id="green-cover" /> + + + width="50" + height="50" + fill="white" + id="purple-cover" /> diff --git a/testfiles/cli_tests/testcases/export-id_export-id-only_expected.svg b/testfiles/cli_tests/testcases/export-id_export-id-only_expected.svg index 138e29cf0006bd941543c64a5beaf6c31b72a632..677f008cf9b9264c88dd5341c13dfef0a598ed8f 100644 --- a/testfiles/cli_tests/testcases/export-id_export-id-only_expected.svg +++ b/testfiles/cli_tests/testcases/export-id_export-id-only_expected.svg @@ -1,47 +1,30 @@ - - - - image/svg+xml - - - - + version="1.1" + id="svg1" + sodipodi:docname="theta.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> + width="100" + height="100" + fill="blue" + id="blue" /> diff --git a/testfiles/cli_tests/testcases/export-id_export-id-only_export-area-drawing_expected.svg b/testfiles/cli_tests/testcases/export-id_export-id-only_export-area-drawing_expected.svg index d0cd1906ffb8ec1cdd5bf50eb5b3b296114ab67b..8d6c72f87a27699f23f0c160be1abfe02be6e0e0 100644 --- a/testfiles/cli_tests/testcases/export-id_export-id-only_export-area-drawing_expected.svg +++ b/testfiles/cli_tests/testcases/export-id_export-id-only_export-area-drawing_expected.svg @@ -1,47 +1,30 @@ - - - - image/svg+xml - - - - + version="1.1" + id="svg1" + sodipodi:docname="theta.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + id="defs1" /> + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> + width="100" + height="100" + fill="yellow" + id="yellow" /> diff --git a/testfiles/cli_tests/testcases/export-png-color-mode-gray-8_expected.png b/testfiles/cli_tests/testcases/export-png-color-mode-gray-8_expected.png index c53d9932803b33a8b7f72cf0ff8b724daa7756e6..5fe0ad73058e9adde5a2f41de22d8a992cc01dfc 100644 Binary files a/testfiles/cli_tests/testcases/export-png-color-mode-gray-8_expected.png and b/testfiles/cli_tests/testcases/export-png-color-mode-gray-8_expected.png differ diff --git a/testfiles/cli_tests/testcases/export-png-color-mode-rgb-8_expected.png b/testfiles/cli_tests/testcases/export-png-color-mode-rgb-8_expected.png index 0677fba713d915bf22bcd89e9ee02e72590b1534..2373e4fd78c4d7e036ba181eb6d25e03a63bf97b 100644 Binary files a/testfiles/cli_tests/testcases/export-png-color-mode-rgb-8_expected.png and b/testfiles/cli_tests/testcases/export-png-color-mode-rgb-8_expected.png differ diff --git a/testfiles/cli_tests/testcases/export-png-color-mode-rgba-8_expected.png b/testfiles/cli_tests/testcases/export-png-color-mode-rgba-8_expected.png index 479838206ad85a080adce1fd3655f9aaf0ed1303..ed761204b78654a9414698760e69552ad3508671 100644 Binary files a/testfiles/cli_tests/testcases/export-png-color-mode-rgba-8_expected.png and b/testfiles/cli_tests/testcases/export-png-color-mode-rgba-8_expected.png differ diff --git a/testfiles/data/colors/SwappedRedAndGreen.icc b/testfiles/data/colors/SwappedRedAndGreen.icc new file mode 100644 index 0000000000000000000000000000000000000000..1b8c69a11bc8d3771df8005e98c181350fc0d3e6 Binary files /dev/null and b/testfiles/data/colors/SwappedRedAndGreen.icc differ diff --git a/testfiles/data/colors/cms-in-defs.svg b/testfiles/data/colors/cms-in-defs.svg new file mode 100644 index 0000000000000000000000000000000000000000..e70c7f3409a1600734665ebc76f653bbaec6fbf9 --- /dev/null +++ b/testfiles/data/colors/cms-in-defs.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testfiles/data/colors/cms-in-objs.svg b/testfiles/data/colors/cms-in-objs.svg new file mode 100644 index 0000000000000000000000000000000000000000..dcbdfb89efb4dde64a5b23175c25aa44be2c620c --- /dev/null +++ b/testfiles/data/colors/cms-in-objs.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testfiles/data/colors/default_cmyk.icc b/testfiles/data/colors/default_cmyk.icc new file mode 100644 index 0000000000000000000000000000000000000000..fce61bf2bcbc9a821364d7fff85a2a4680af3078 Binary files /dev/null and b/testfiles/data/colors/default_cmyk.icc differ diff --git a/testfiles/data/colors/display.icc b/testfiles/data/colors/display.icc new file mode 100644 index 0000000000000000000000000000000000000000..12cb9c8b167b92335474155b4ac07c44b0cdabf6 Binary files /dev/null and b/testfiles/data/colors/display.icc differ diff --git a/testfiles/src/color-profile-test.cpp b/testfiles/src/color-profile-test.cpp deleted file mode 100644 index 40899204163b5cb3a3e3931d0d30efa6e24b4b6c..0000000000000000000000000000000000000000 --- a/testfiles/src/color-profile-test.cpp +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Unit tests for color profile. - * - * Author: - * Jon A. Cruz - * - * Copyright (C) 2015 Authors - * - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ - -#include "gtest/gtest.h" - -#include "attributes.h" -#include "color/cms-system.h" -#include "object/color-profile.h" -#include "doc-per-case-test.h" - -namespace { - -/** - * Test fixture to inherit a shared doc and create a color profile instance per test. - */ -class ProfTest : public DocPerCaseTest -{ -public: - ProfTest() : - DocPerCaseTest(), - _prof(0) - { - } - -protected: - void SetUp() override - { - DocPerCaseTest::SetUp(); - _prof = new Inkscape::ColorProfile(); - ASSERT_TRUE( _prof != NULL ); - _prof->document = _doc.get(); - } - - void TearDown() override - { - if (_prof) { - delete _prof; - _prof = NULL; - } - DocPerCaseTest::TearDown(); - } - - Inkscape::ColorProfile *_prof; -}; - -typedef ProfTest ColorProfileTest; - -TEST_F(ColorProfileTest, SetRenderingIntent) -{ - struct { - gchar const *attr; - guint intVal; - } - const cases[] = { - {"auto", (guint)Inkscape::RENDERING_INTENT_AUTO}, - {"perceptual", (guint)Inkscape::RENDERING_INTENT_PERCEPTUAL}, - {"relative-colorimetric", (guint)Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC}, - {"saturation", (guint)Inkscape::RENDERING_INTENT_SATURATION}, - {"absolute-colorimetric", (guint)Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC}, - {"something-else", (guint)Inkscape::RENDERING_INTENT_UNKNOWN}, - {"auto2", (guint)Inkscape::RENDERING_INTENT_UNKNOWN}, - }; - - for (auto i : cases) { - _prof->setKeyValue( SPAttr::RENDERING_INTENT, i.attr); - ASSERT_EQ( (guint)i.intVal, _prof->rendering_intent ) << i.attr; - } -} - -TEST_F(ColorProfileTest, SetLocal) -{ - gchar const* cases[] = { - "local", - "something", - }; - - for (auto & i : cases) { - _prof->setKeyValue( SPAttr::LOCAL, i); - ASSERT_TRUE( _prof->local != NULL ); - if ( _prof->local ) { - ASSERT_EQ( std::string(i), _prof->local ); - } - } - _prof->setKeyValue( SPAttr::LOCAL, NULL); - ASSERT_EQ( (gchar*)0, _prof->local ); -} - -TEST_F(ColorProfileTest, SetName) -{ - gchar const* cases[] = { - "name", - "something", - }; - - for (auto & i : cases) { - _prof->setKeyValue( SPAttr::NAME, i); - ASSERT_TRUE( _prof->name != NULL ); - if ( _prof->name ) { - ASSERT_EQ( std::string(i), _prof->name ); - } - } - _prof->setKeyValue( SPAttr::NAME, NULL ); - ASSERT_EQ( (gchar*)0, _prof->name ); -} - - -} // namespace - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/cms-test.cpp b/testfiles/src/colors/cms-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d1bfdce59f9abefc1ab2ba52aac58244c5b9a30e --- /dev/null +++ b/testfiles/src/colors/cms-test.cpp @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for color objects. + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include +#include + +#include "colors/cms/profile.h" +#include "colors/cms/system.h" +#include "colors/cms/transform.h" +#include "colors/color.h" +#include "colors/manager.h" +#include "colors/utils.h" +#include "preferences.h" + +static std::string icc_dir = INKSCAPE_TESTS_DIR "/data/colors"; +static std::string grb_profile = INKSCAPE_TESTS_DIR "/data/colors/SwappedRedAndGreen.icc"; +static std::string cmyk_profile = INKSCAPE_TESTS_DIR "/data/colors/default_cmyk.icc"; +static std::string display_profile = INKSCAPE_TESTS_DIR "/data/colors/display.icc"; +static std::string not_a_profile = INKSCAPE_TESTS_DIR "/data/colors/color-cms.svg"; + +using namespace Inkscape::Colors; + +namespace { + +// ================= CMS::System ================= // + +class ColorCmsSystem : public ::testing::Test +{ +protected: + void SetUp() override + { + cms = &CMS::System::get(); + cms->clearDirectoryPaths(); + cms->addDirectoryPath(icc_dir, false); + cms->refreshProfiles(); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString("/options/displayprofile/uri", display_profile); + prefs->setBool("/options/displayprofile/enabled", true); + } + void TearDown() override {} + Inkscape::Colors::CMS::System *cms = nullptr; +}; + +TEST_F(ColorCmsSystem, getDirectoryPaths) +{ + ASSERT_EQ(cms->getDirectoryPaths().size(), 1); + ASSERT_EQ(cms->getDirectoryPaths()[0].first, icc_dir); +} + +TEST_F(ColorCmsSystem, addDirectoryPath) +{ + cms->clearDirectoryPaths(); + cms->addDirectoryPath("nope", false); + cms->addDirectoryPath("yep", true); + ASSERT_EQ(cms->getDirectoryPaths().size(), 2); + ASSERT_EQ(cms->getDirectoryPaths()[0].first, "nope"); + ASSERT_EQ(cms->getDirectoryPaths()[1].first, "yep"); +} + +TEST_F(ColorCmsSystem, clearDirectoryPaths) +{ + cms->clearDirectoryPaths(); + ASSERT_GE(cms->getDirectoryPaths().size(), 2); +} + +TEST_F(ColorCmsSystem, getProfiles) +{ + auto profiles = cms->getProfiles(); + ASSERT_EQ(profiles.size(), 3); + + ASSERT_EQ(profiles[0]->getName(), "Artifex CMYK SWOP Profile"); + ASSERT_EQ(profiles[1]->getName(), "C.icc"); + ASSERT_EQ(profiles[2]->getName(), "Swapped Red and Green"); +} + +TEST_F(ColorCmsSystem, getProfileByName) +{ + auto profile = cms->getProfile("Swapped Red and Green"); + ASSERT_TRUE(profile); + ASSERT_EQ(profile->getPath(), grb_profile); +} + +TEST_F(ColorCmsSystem, getProfileByID) +{ + auto profile = cms->getProfile("f9eda5a42a222a28f0adb82a938eeb0e"); + ASSERT_TRUE(profile); + ASSERT_EQ(profile->getName(), "Swapped Red and Green"); +} + +TEST_F(ColorCmsSystem, getProfileByPath) +{ + auto profile = cms->getProfile(grb_profile); + ASSERT_TRUE(profile); + ASSERT_EQ(profile->getId(), "f9eda5a42a222a28f0adb82a938eeb0e"); +} + +TEST_F(ColorCmsSystem, getDisplayProfiles) +{ + auto profiles = cms->getDisplayProfiles(); + ASSERT_EQ(profiles.size(), 1); + ASSERT_EQ(profiles[0]->getName(), "C.icc"); +} + +TEST_F(ColorCmsSystem, getDisplayProfile) +{ + bool updated = false; + auto profile = cms->getDisplayProfile(updated); + ASSERT_TRUE(updated); + ASSERT_TRUE(profile); + ASSERT_EQ(profile->getName(), "C.icc"); +} + +TEST_F(ColorCmsSystem, getDisplayTransform) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + bool updated = false; + auto profile = cms->getDisplayProfile(updated); + ASSERT_TRUE(profile); + + ASSERT_TRUE(cms->getDisplayTransform()); + prefs->setBool("/options/displayprofile/enabled", false); + ASSERT_FALSE(cms->getDisplayTransform()); + prefs->setBool("/options/displayprofile/enabled", true); + ASSERT_TRUE(cms->getDisplayTransform()); + prefs->setString("/options/displayprofile/uri", ""); + ASSERT_FALSE(cms->getDisplayTransform()); +} + +TEST_F(ColorCmsSystem, getOutputProfiles) +{ + auto profiles = cms->getOutputProfiles(); + ASSERT_EQ(profiles.size(), 1); + ASSERT_EQ(profiles[0]->getName(), "Artifex CMYK SWOP Profile"); +} + +TEST_F(ColorCmsSystem, refreshProfiles) +{ + cms->clearDirectoryPaths(); + cms->refreshProfiles(); + ASSERT_GE(cms->getProfiles().size(), 3); +} + +// ================= CMS::Profile ================= // + +TEST(ColorCmsProfile, create) +{ + auto rgb_profile = cmsCreate_sRGBProfile(); + auto profile = CMS::Profile::create(rgb_profile, "", false); + ASSERT_TRUE(profile); + ASSERT_EQ(profile->getId(), ""); + ASSERT_EQ(profile->getName(), "sRGB built-in"); + ASSERT_EQ(profile->getPath(), ""); + ASSERT_FALSE(profile->inHome()); + ASSERT_EQ(profile->getHandle(), rgb_profile); +} + +TEST(ColorCmsProfile, create_from_uri) +{ + auto profile = CMS::Profile::create_from_uri(grb_profile); + + ASSERT_EQ(profile->getId(), "f9eda5a42a222a28f0adb82a938eeb0e"); + ASSERT_EQ(profile->getName(), "Swapped Red and Green"); + ASSERT_EQ(profile->getName(true), "Swapped-Red-and-Green"); + ASSERT_EQ(profile->getPath(), grb_profile); + ASSERT_EQ(profile->getColorSpace(), cmsSigRgbData); + ASSERT_EQ(profile->getProfileClass(), cmsSigDisplayClass); + + ASSERT_FALSE(profile->inHome()); + ASSERT_FALSE(profile->isForDisplay()); +} + +TEST(ColorCmsProfile, create_from_data) +{ + // Prepare some memory first + cmsUInt32Number len = 0; + auto rgb_profile = cmsCreate_sRGBProfile(); + ASSERT_TRUE(cmsSaveProfileToMem(rgb_profile, nullptr, &len)); + auto buf = std::vector(len); + cmsSaveProfileToMem(rgb_profile, &buf.front(), &len); + std::string data(buf.begin(), buf.end()); + + auto profile = CMS::Profile::create_from_data(data); + ASSERT_TRUE(profile); +} + +TEST(ColorCmsProfile, create_srgb) +{ + auto profile = CMS::Profile::create_srgb(); + ASSERT_TRUE(profile); +} + +TEST(ColorCmsProfile, equalTo) +{ + auto profile1 = CMS::Profile::create_from_uri(grb_profile); + auto profile2 = CMS::Profile::create_from_uri(grb_profile); + auto profile3 = CMS::Profile::create_from_uri(cmyk_profile); + ASSERT_EQ(*profile1, *profile2); + ASSERT_NE(*profile1, *profile3); +} + +TEST(ColorCmsProfile, isIccFile) +{ + ASSERT_TRUE(CMS::Profile::isIccFile(grb_profile)); + ASSERT_FALSE(CMS::Profile::isIccFile(not_a_profile)); + ASSERT_FALSE(CMS::Profile::isIccFile(icc_dir + "not_existing.icc")); +} + +TEST(ColorCmsProfile, cmsDumpBase64) +{ + auto profile = CMS::Profile::create_from_uri(grb_profile); + // First 100 bytes taken from the base64 of the icc profile file on the command line + ASSERT_EQ(profile->dumpBase64().substr(0, 100), + "AAA9aGxjbXMEMAAAbW50clJHQiBYWVogB+YAAgAWAA0AGQAuYWNzcEFQUEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPbWAAEA"); +} + +// ================= CMS::Transform ================= // + +TEST(ColorCmsTransform, applyTransformColor) +{ + auto srgb = CMS::Profile::create_srgb(); + auto profile = CMS::Profile::create_from_uri(grb_profile); + auto tr = CMS::Transform::create_for_cms(srgb, profile, RenderingIntent::RELATIVE_COLORIMETRIC); + + std::vector output = {0.1, 0.2, 0.3, 1.0}; + tr->do_transform(output); + ASSERT_NEAR(output[0], 0.2, 0.01); + ASSERT_NEAR(output[1], 0.1, 0.01); + ASSERT_NEAR(output[2], 0.3, 0.01); + ASSERT_EQ(output[3], 1.0); +} + +TEST(ColorCmsTransform, gamutCheckColor) +{ + auto srgb = CMS::Profile::create_srgb(); + auto profile = CMS::Profile::create_from_uri(cmyk_profile); + ASSERT_TRUE(srgb); + ASSERT_TRUE(profile); + + auto tr1 = CMS::Transform::create_for_cms_checker(srgb, profile); + ASSERT_TRUE(tr1); + + // An RGB color which is within the cmyk color profile gamut + EXPECT_FALSE(tr1->check_gamut({0.83, 0.19, 0.49})); + + // An RGB color (magenta) which is outside the cmyk color profile + EXPECT_TRUE(tr1->check_gamut({1.0, 0.0, 1.0})); +} + +::testing::AssertionResult CairoPixelIs(Cairo::RefPtr &cs, unsigned expected_color) +{ + auto data = cs->get_data(); + // BGRA8 cairo surfaces, (aka ARGB32) + unsigned found_color = SP_RGBA32_U_COMPOSE(data[2], data[1], data[0], data[3]); + + if (found_color != expected_color) { + return ::testing::AssertionFailure() << "\n" + << Inkscape::Colors::rgba_to_hex(found_color) << "\n != \n" + << Inkscape::Colors::rgba_to_hex(expected_color); + } + return ::testing::AssertionSuccess(); +} + +TEST(ColorCmsTransform, applyTransformCairo) +{ + auto srgb = CMS::Profile::create_srgb(); + auto profile = CMS::Profile::create_from_uri(grb_profile); + auto tr = CMS::Transform::create_for_cairo(srgb, profile); + + auto cs = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, 1, 1); + auto cr = Cairo::Context::create(cs); + + cr->set_source_rgb(0.5, 0, 0); + cr->paint(); + ASSERT_TRUE(CairoPixelIs(cs, 0x800000ff)); + tr->do_transform(cs, cs); + ASSERT_TRUE(CairoPixelIs(cs, 0x008000ff)); +} + +TEST(ColorCmsTransform, applyWithProofing) +{ + auto srgb = CMS::Profile::create_srgb(); + auto profile = CMS::Profile::create_from_uri(cmyk_profile); + auto proofed = CMS::Transform::create_for_cairo(srgb, srgb, profile, RenderingIntent::AUTO, false); + + auto cs = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, 1, 1); + auto cr = Cairo::Context::create(cs); + + cr->set_source_rgb(1.0, 0, 1.0); + cr->paint(); + + ASSERT_TRUE(CairoPixelIs(cs, 0xff00ffff)); + proofed->do_transform(cs, cs); + // Value should be + ASSERT_TRUE(CairoPixelIs(cs, 0xba509dff)); + + cr->set_source_rgb(0.83, 0.19, 0.49); + cr->paint(); + + ASSERT_TRUE(CairoPixelIs(cs, 0xd4307dff)); + proofed->do_transform(cs, cs); + ASSERT_TRUE(CairoPixelIs(cs, 0xd42279ff)); +} + +TEST(ColorCmsTransform, applyWithGamutWarn) +{ + auto srgb = CMS::Profile::create_srgb(); + auto profile = CMS::Profile::create_from_uri(cmyk_profile); + auto warned = CMS::Transform::create_for_cairo(srgb, srgb, profile, RenderingIntent::AUTO, true); + warned->set_gamut_warn({0.0, 1.0, 0.0}); + + auto cs = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, 1, 1); + auto cr = Cairo::Context::create(cs); + + cr->set_source_rgb(1.0, 0, 1.0); + cr->paint(); + + ASSERT_TRUE(CairoPixelIs(cs, 0xff00ffff)); + warned->do_transform(cs, cs); + ASSERT_TRUE(CairoPixelIs(cs, 0x00ff00ff)); + + cr->set_source_rgb(0.83, 0.19, 0.49); + cr->paint(); + + ASSERT_TRUE(CairoPixelIs(cs, 0xd4307dff)); + warned->do_transform(cs, cs); + ASSERT_TRUE(CairoPixelIs(cs, 0xd42279ff)); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/color-set-test.cpp b/testfiles/src/colors/color-set-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..985dab1c6cc61f4753dfb6070ca659ae480e7763 --- /dev/null +++ b/testfiles/src/colors/color-set-test.cpp @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for testing selected colors + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#include "../test-utils.h" + +#include "colors/color.h" +#include "colors/color-set.h" +#include "colors/manager.h" +#include "colors/spaces/base.h" +#include "colors/spaces/components.h" + +using namespace Inkscape::Colors; + +namespace { + +::testing::AssertionResult signalIsCalled(sigc::signal &signal, std::function then_run) +{ + unsigned was_called = 0; + sigc::connection ret = signal.connect([&was_called]() { was_called++; }); + then_run(); + ret.disconnect(); + if (was_called == 0) + return ::testing::AssertionFailure() << " Signal not called."; + if (was_called > 1) + return ::testing::AssertionFailure() << " Signal called too many times."; + return ::testing::AssertionSuccess(); +} + +TEST(ColorSetTest, setColors) +{ + auto colors = ColorSet(); + ASSERT_TRUE(colors.isEmpty()); + ASSERT_FALSE(colors.getAlphaConstraint()); + ASSERT_FALSE(colors.getSpaceConstraint()); + + colors.set("i1", *Color::parse("red")); + colors.set("i2", *Color::parse("#ff0000")); + ASSERT_FALSE(colors.isEmpty()); + ASSERT_FALSE(colors.isSame()); + + colors.set("i3", *Color::parse("#0000ffff")); + ASSERT_FALSE(colors.isSame()); + + EXPECT_EQ(colors.get("i1")->toString(), "red"); + EXPECT_EQ(colors.get("i2")->toString(), "#ff0000"); + EXPECT_EQ(colors.get("i3")->toString(), "#0000ffff"); + EXPECT_EQ(colors.size(), 3); + EXPECT_FALSE(colors.get("i4")); + + colors.set("i1", *Color::parse("green")); + EXPECT_EQ(colors.size(), 3); + EXPECT_EQ(colors.get("i1")->toString(), "green"); + + ASSERT_FALSE(colors.getAlphaConstraint()); + ASSERT_FALSE(colors.getSpaceConstraint()); +} + +TEST(ColorSetTest, setSingleColor) +{ + auto color = ColorSet(); + ASSERT_FALSE(color.get()); + + color.set(*Color::parse("red")); + EXPECT_FALSE(color.isEmpty()); + ASSERT_EQ(color.size(), 1); + ASSERT_TRUE(color.get()); + ASSERT_EQ(color.get()->toString(), "red"); + ASSERT_EQ(color.getAverage().toString(), "#ff0000ff"); + + color.set(*Color::parse("blue")); + ASSERT_EQ(color.size(), 1); + ASSERT_TRUE(color.get()); + ASSERT_EQ(color.get()->toString(), "blue"); + ASSERT_EQ(color.getAverage().toString(), "#0000ffff"); +} + +TEST(ColorSetTest, setColorsConstrained) +{ + auto space = Manager::get().find(Space::Type::RGB); + auto colors = ColorSet(space, false); + ASSERT_TRUE(colors.getSpaceConstraint()); + ASSERT_FALSE(*colors.getAlphaConstraint()); + + colors.set("i1", *Color::parse("red")); + colors.set("i2", *Color::parse("#ff000080")); + ASSERT_TRUE(colors.isSame()); + colors.set("i3", *Color::parse("#0000ffff")); + ASSERT_FALSE(colors.isSame()); + + EXPECT_EQ(colors.get("i1")->toString(), "#ff0000"); + EXPECT_EQ(colors.get("i2")->toString(), "#ff0000"); + EXPECT_EQ(colors.get("i3")->toString(), "#0000ff"); +} + +TEST(ColorSetTest, setColorsHsl) +{ + auto space = Manager::get().find(Space::Type::HSL); + auto colors = ColorSet(space, true); + ASSERT_TRUE(colors.getSpaceConstraint()); + ASSERT_TRUE(colors.getAlphaConstraint()); + ASSERT_TRUE(*colors.getAlphaConstraint()); + + colors.set("i1", *Color::parse("red")); + ASSERT_EQ(colors.get("i1")->toString(), "hsla(0, 1, 0.5, 1)"); +} + +TEST(ColorSetTest, setAllColors) +{ + auto colors_a = ColorSet(); + colors_a.set("i1", *Color::parse("red")); + colors_a.set("i2", *Color::parse("blue")); + + auto colors_b = ColorSet(); + colors_b.set("i1", *Color::parse("green")); + colors_b.set("i3", *Color::parse("purple")); + + EXPECT_TRUE(signalIsCalled(colors_b.signal_changed, [&colors_a, &colors_b]() { colors_b.setAll(colors_a); })); + + // No change to other object + EXPECT_EQ(colors_a.size(), 2); + EXPECT_EQ(colors_a.get("i1")->toString(), "red"); + EXPECT_EQ(colors_a.get("i2")->toString(), "blue"); + + // Addative changes to B + EXPECT_EQ(colors_b.size(), 3); + EXPECT_EQ(colors_b.get("i1")->toString(), "red"); + EXPECT_EQ(colors_b.get("i2")->toString(), "blue"); + EXPECT_EQ(colors_b.get("i3")->toString(), "purple"); +} + +TEST(ColorSetTest, clearColors) +{ + auto colors = ColorSet(); + colors.set("i1", *Color::parse("red")); + colors.set("i2", *Color::parse("green")); + ASSERT_EQ(colors.size(), 2); + colors.clear(); + ASSERT_EQ(colors.size(), 0); +} + +TEST(ColorSetTest, iterateColors) +{ + auto colors = ColorSet(); + colors.set("i1", *Color::parse("red")); + + for (auto &[id, color] : colors) { + EXPECT_EQ(id, "i1"); + EXPECT_EQ(color.toString(), "red"); + } +} + +TEST(ColorSetTest, signalGrabRelease) +{ + auto space = Manager::get().find(Space::Type::RGB); + auto colors = ColorSet(space, false); + ASSERT_TRUE(colors.getSpaceConstraint()); + ASSERT_TRUE(colors.getAlphaConstraint()); + ASSERT_FALSE(*colors.getAlphaConstraint()); + + EXPECT_TRUE(signalIsCalled(colors.signal_grabbed, [&colors]() { colors.grab(); })); + EXPECT_FALSE(signalIsCalled(colors.signal_grabbed, [&colors]() { colors.grab(); })); + EXPECT_TRUE(signalIsCalled(colors.signal_released, [&colors]() { colors.release(); })); + EXPECT_FALSE(signalIsCalled(colors.signal_released, [&colors]() { colors.release(); })); +} + +TEST(ColorSetTest, signalChanged) +{ + auto space = Manager::get().find(Space::Type::RGB); + auto colors = ColorSet(space); + colors.set("0", Color(0xff0000ff)); + colors.set("1", Color(0x00ff00ff)); + + auto comp = colors.getBestSpace()->getComponents(); + + EXPECT_TRUE(signalIsCalled(colors.signal_changed, [&colors, &comp]() { ASSERT_EQ(colors.setAll(comp[0], 0.5), 2); })); + EXPECT_FALSE(signalIsCalled(colors.signal_changed, [&colors, &comp]() { ASSERT_EQ(colors.setAll(comp[0], 0.5), 0); })); + EXPECT_TRUE(signalIsCalled(colors.signal_changed, [&colors]() { ASSERT_TRUE(colors.set("0", *Color::parse("blue"))); })); + EXPECT_FALSE(signalIsCalled(colors.signal_changed, [&colors]() { ASSERT_FALSE(colors.set("0", *Color::parse("blue"))); })); + EXPECT_TRUE(signalIsCalled(colors.signal_changed, [&colors]() { ASSERT_EQ(colors.setAll(*Color::parse("blue")), 1); })); + EXPECT_FALSE(signalIsCalled(colors.signal_changed, [&colors]() { ASSERT_EQ(colors.setAll(*Color::parse("blue")), 0); })); + EXPECT_TRUE(signalIsCalled(colors.signal_changed, [&colors, &comp]() { + colors.grab(); + colors.setAll(comp[0], 0.75); + ; + })); +} + +TEST(ColorSetTest, signalModified) +{ + auto colors = ColorSet(); + EXPECT_FALSE(signalIsCalled(colors.signal_cleared, [&colors]() { colors.clear(); })); + EXPECT_FALSE(signalIsCalled(colors.signal_cleared, [&colors]() { colors.set("new", *Color::parse("red")); })); + EXPECT_TRUE(signalIsCalled(colors.signal_cleared, [&colors]() { colors.clear(); })); +} + +TEST(ColorSetTest, colorAverages) +{ + auto space = Manager::get().find(Space::Type::RGB); + auto colors = ColorSet(space, false); + colors.set("0", Color(space, {0.4, 0.5, 1.0})); + colors.set("1", Color(space, {0.5, 0.5, 0.5})); + colors.set("2", Color(space, {0.6, 0.5, 0.0})); + auto comp = colors.getBestSpace()->getComponents(); + + // Red starts off in the middle, Green is our control + EXPECT_NEAR(colors.getAverage(comp[0]), 0.5, 0.01); + EXPECT_NEAR(colors.getAverage(comp[1]), 0.5, 0.01); + EXPECT_TRUE(VectorIsNear(colors.getAll(comp[0]), {0.4, 0.5, 0.6}, 0.05)); + + // Move red to 0.75, green doesn't change + EXPECT_TRUE(signalIsCalled(colors.signal_changed, [&colors, &comp]() { colors.setAverage(comp[0], 0.75); })); + + EXPECT_NEAR(colors.getAverage(comp[0]), 0.75, 0.01); + EXPECT_NEAR(colors.getAverage(comp[1]), 0.5, 0.01); + EXPECT_TRUE(VectorIsNear(colors.getAll(comp[0]), {0.65, 0.75, 0.85}, 0.05)); + + // Move red to 1.0 pushing some values out of bounds + colors.setAverage(comp[0], 1.0); + EXPECT_NEAR(colors.getAverage(comp[0]), 1.0, 0.01); + EXPECT_TRUE(VectorIsNear(colors.getAll(comp[0]), {0.9, 1, 1}, 0.05)); + + // Move red to 0.25, green doesn't change, but red remembers it's previous range + colors.setAverage(comp[0], 0.25); + EXPECT_NEAR(colors.getAverage(comp[0]), 0.25, 0.01); + EXPECT_NEAR(colors.getAverage(comp[1]), 0.5, 0.01); + EXPECT_TRUE(VectorIsNear(colors.getAll(comp[0]), {0.15, 0.25, 0.35}, 0.05)); +} + +TEST(ColorSetTest, getAverage) +{ + auto colors = ColorSet({}, false); + colors.set("c1", *Color::parse("black")); + colors.set("c2", *Color::parse("white")); + EXPECT_EQ(colors.getBestSpace()->getName(), "CSSNAME"); + EXPECT_EQ(colors.getAverage().toString(), "gray"); + EXPECT_FALSE(colors.isSame()); + + colors.set("c1", *Color::parse("red")); + colors.set("c2", *Color::parse("red")); + EXPECT_EQ(colors.getBestSpace()->getName(), "CSSNAME"); + EXPECT_EQ(colors.getAverage().toString(), "red"); + EXPECT_TRUE(colors.isSame()); + + colors.clear(); + colors.set("c1", *Color::parse("hsl(180,1,1)")); + colors.set("c2", *Color::parse("hsla(60,0,0, 0.5)")); + EXPECT_EQ(colors.getBestSpace()->getName(), "HSL"); + EXPECT_EQ(colors.getAverage().toString(), "hsl(120, 0.5, 0.5)"); + + colors.set("c1", *Color::parse("hsl(180,1,1)")); + colors.set("c2", *Color::parse("hsl(0,0.5,1)")); + colors.set("c3", *Color::parse("blue")); + EXPECT_EQ(colors.getBestSpace()->getName(), "HSL"); + EXPECT_EQ(colors.getAverage().toString(), "hsl(139, 0.833, 0.833)"); +} + +TEST(ColorSetTest, getCmykAverage) +{ + auto colors = ColorSet({}, false); + colors.set("cmyk1", *Color::parse("device-cmyk(0.5 0.5 0.0 0.2 / 0.5)")); + colors.set("rgb1", *Color::parse("red")); + EXPECT_EQ(colors.getBestSpace()->getName(), "DeviceCMYK"); + EXPECT_EQ(colors.getAverage().toString(), "device-cmyk(0.25 0.75 0.5 0.1)"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/color-test.cpp b/testfiles/src/colors/color-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bcc4e4448e0abeb19edb0495f5c06e5c6eed97da --- /dev/null +++ b/testfiles/src/colors/color-test.cpp @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for color objects. + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/color.h" + +#include +#include "../test-utils.h" + +#include "colors/manager.h" +#include "colors/spaces/base.h" + +using namespace Inkscape::Colors; + +namespace { + +TEST(ColorsColor, construct_space_obj) +{ + auto space = Manager::get().find(Space::Type::HSL); + ASSERT_TRUE(space); + + ASSERT_EQ(Color(space, {0, 1, 0.5}).toString(), "hsl(0, 1, 0.5)"); +} + +TEST(ColorsColor, construct_space_type) +{ + ASSERT_EQ(Color(Space::Type::HSL, {0, 1, 0.5}).toString(), "hsl(0, 1, 0.5)"); +} + +TEST(ColorsColor, construct_css_string) +{ + ASSERT_EQ(Color::parse("red")->toString(), "red"); + // document tested in cms tests +} + +TEST(ColorsColor, construct_rgba) +{ + ASSERT_EQ(Color(0xff00ff00, false).toString(), "#ff00ff"); + ASSERT_EQ(Color(0xff00ff00, true).toString(), "#ff00ff00"); +} + +TEST(ColorsColor, construct_other) +{ + auto color = *Color::parse("red"); + auto other = Color(color); + ASSERT_EQ(other.toString(), "red"); +} + +TEST(ColorsColor, parse) +{ + ASSERT_TRUE(Color::parse("red")); + ASSERT_FALSE(Color::parse("none")); + ASSERT_FALSE(Color::parse(nullptr)); +} + +TEST(ColorsColor, setter) +{ + auto color = *Color::parse("purple"); + color = *Color::parse("green"); + ASSERT_EQ(color.toString(), "green"); + + EXPECT_TRUE(color.set(Color(0x0000ffff), true)); + ASSERT_EQ(color.toString(), "blue"); + EXPECT_TRUE(color.set(Color(0x0000ffff, false), false)); + ASSERT_EQ(color.toString(), "#0000ff"); + + EXPECT_TRUE(color.set(1, 1.0)); + EXPECT_FALSE(color.set(1, 1.0)); + ASSERT_EQ(color.toString(), "#00ffff"); + + EXPECT_TRUE(color.set("red", true)); + EXPECT_FALSE(color.set("red", true)); + ASSERT_EQ(color.toString(), "#ff0000"); + EXPECT_TRUE(color.set("red", false)); + EXPECT_FALSE(color.set("red", false)); + ASSERT_EQ(color.toString(), "red"); + + EXPECT_TRUE(color.set(0x0, false)); + EXPECT_FALSE(color.set(0x0, false)); + ASSERT_EQ(color.toString(), "#000000"); + EXPECT_TRUE(color.set(0x00ff00ff, true)); + ASSERT_EQ(color.toString(), "#00ff00ff"); + EXPECT_TRUE(color.set(0x00ff00, false)); + ASSERT_EQ(color.toString(), "#0000ff"); + + color.setValues({0.2, 1.0, 0.5}); + ASSERT_EQ(color.toString(), "#33ff80"); +} + +TEST(ColorsColor, conditionals) +{ + // == + ASSERT_EQ(*Color::parse("red"), *Color::parse("red")); + // != + ASSERT_NE(*Color::parse("green"), Color(0xff0000)); + // bool + ASSERT_TRUE(Color::parse("blue")); +} + +TEST(ColorsColor, getSpace) +{ + auto color = *Color::parse("red"); + ASSERT_TRUE(color.getSpace()); + ASSERT_EQ(color.getSpace()->getName(), "CSSNAME"); +} + +TEST(ColorsColor, values) +{ + auto color = *Color::parse("red"); + EXPECT_TRUE(VectorIsNear(color.getValues(), {1.0, 0.0, 0.0}, 0.01)); +} + +TEST(ColorsColor, Opacity) +{ + auto color = *Color::parse("red"); + ASSERT_FALSE(color.hasOpacity()); + ASSERT_FALSE(color.converted(Space::Type::HSL)->hasOpacity()); + EXPECT_TRUE(color.setOpacity(1.0)); + EXPECT_FALSE(color.setOpacity(1.0)); + EXPECT_FALSE(color.addOpacity(1.0)); + ASSERT_TRUE(color.hasOpacity()); + ASSERT_EQ(color.getOpacity(), 1.0); + ASSERT_EQ(color.toString(), "#ff0000ff"); + EXPECT_TRUE(color.setOpacity(0.5)); + EXPECT_FALSE(color.setOpacity(0.5)); + ASSERT_TRUE(color.hasOpacity()); + ASSERT_EQ(color.getOpacity(), 0.5); + ASSERT_EQ(color.toString(), "#ff000080"); + EXPECT_TRUE(color.addOpacity(0.5)); + ASSERT_EQ(color.getOpacity(), 0.25); + ASSERT_EQ(color.toString(), "#ff000040"); + color.enableOpacity(false); + ASSERT_FALSE(color.hasOpacity()); + ASSERT_EQ(color.toString(), "red"); + EXPECT_TRUE(color.addOpacity(0.5)); + ASSERT_TRUE(color.hasOpacity()); + ASSERT_EQ(color.getOpacity(), 0.5); + ASSERT_EQ(color.stealOpacity(), 0.5); + ASSERT_FALSE(color.hasOpacity()); +} + +TEST(ColorsColor, colorOpacityPin) +{ + auto color = Color::parse("red"); + ASSERT_EQ(color->getOpacityChannel(), 3); + ASSERT_EQ(color->getPin(3), 8); + color->convert(Space::Type::CMYK); + ASSERT_EQ(color->getOpacityChannel(), 4); + ASSERT_EQ(color->getPin(4), 16); +} + +TEST(ColorsColor, difference) +{ + auto color = Color::parse("green"); + ASSERT_NEAR(color->difference(*Color::parse("red")), 1.251, 0.001); + ASSERT_NEAR(color->difference(*Color::parse("blue")), 1.251, 0.001); + ASSERT_NEAR(color->difference(*Color::parse("black")), 0.251, 0.001); +} + +TEST(ColorsColor, similarAndClose) +{ + double one_hex_away = 0.004; + auto c1 = Color(0xff0000ff, false); + auto c2 = Color(0x0000ffff, false); + ASSERT_FALSE(c1.isClose(c2)); + ASSERT_FALSE(c1.isSimilar(c2)); + + ASSERT_TRUE(c1.isClose(c1)); + ASSERT_TRUE(c1.isSimilar(c1)); + + c2 = *Color::parse("red"); + ASSERT_FALSE(c1.isClose(c2)); + ASSERT_TRUE(c1.isSimilar(c2)); + + c2 = Color(0xfe0101ff, false); + ASSERT_TRUE(c1.isClose(c2, one_hex_away)); + ASSERT_TRUE(c1.isSimilar(c2, one_hex_away)); + + c2 = Color(0xfe0102ff, false); + ASSERT_FALSE(c1.isClose(c2, one_hex_away)); + ASSERT_FALSE(c1.isSimilar(c2, one_hex_away)); +} + +TEST(ColorsColor, convert_other) +{ + auto other = *Color::parse("red"); + auto color = Color::parse("hsl(120, 1, 0.251)"); + color->convert(other); + EXPECT_EQ(color->toString(), "green"); + other.addOpacity(); + color->convert(other); + EXPECT_EQ(color->toString(), "#008000ff"); +} + +TEST(ColorsColor, convert_space_obj) +{ + auto space = Manager::get().find(Space::Type::HSL); + ASSERT_TRUE(space); + + auto color = Color(0xff0000ff, false); + color.convert(space); + ASSERT_EQ(color.toString(), "hsl(0, 1, 0.5)"); +} + +TEST(ColorsColor, convert_space_type) +{ + auto color = Color(0xff0000ff, false); + ASSERT_TRUE(color.convert(Space::Type::HSL)); + ASSERT_EQ(color.toString(), "hsl(0, 1, 0.5)"); + ASSERT_FALSE(color.convert(Space::Type::NONE)); + ASSERT_EQ(color.toString(), "hsl(0, 1, 0.5)"); +} + +TEST(ColorsColor, converted_other) +{ + auto other = *Color::parse("red"); + ASSERT_EQ(Color::parse("hsl(120, 1, 0.251)")->converted(other)->toString(), "green"); + other.addOpacity(); + ASSERT_EQ(Color::parse("hsl(120, 1, 0.251)")->converted(other)->toString(), "#008000ff"); +} + +TEST(ColorsColor, converted_space_obj) +{ + auto space = Manager::get().find(Space::Type::HSL); + ASSERT_TRUE(space); + ASSERT_EQ(Color::parse("red")->converted(space)->toString(), "hsl(0, 1, 0.5)"); +} + +TEST(ColorsColor, converted_space_type) +{ + auto color = Color::parse("red"); + ASSERT_EQ(color->converted(Space::Type::HSL)->toString(), "hsl(0, 1, 0.5)"); + + auto none = color->converted(Space::Type::NONE); + ASSERT_FALSE(none); +} + +TEST(ColorsColor, toString) +{ + ASSERT_EQ(Color::parse("red")->toString(), "red"); + ASSERT_EQ(Color::parse("#ff0")->toString(), "#ffff00"); + ASSERT_EQ(Color::parse("rgb(80,90,255 / 128)")->toString(true), "#505aff80"); + ASSERT_EQ(Color::parse("rgb(80,90,255 / 128)")->toString(false), "#505aff"); + // Each type of space tested in it's own testcase here after. +} + +TEST(ColorsColor, toRGBA) +{ + ASSERT_EQ(Color(0x123456cc).toRGBA(1.0), 0x123456cc); + ASSERT_EQ(Color(0x123456cc).toRGBA(0.5), 0x12345666); + // Each type of space tested in it's own testcase here after. +} + +TEST(ColorsColor, toARGB) +{ + ASSERT_EQ(Color(0x123456cc).toARGB(1.0), 0xcc123456); + ASSERT_EQ(Color(0x123456cc).toARGB(0.5), 0x66123456); +} + +TEST(ColorsColor, toABGR) +{ + ASSERT_EQ(Color(0x123456cc).toABGR(1.0), 0xcc563412); + ASSERT_EQ(Color(0x123456cc).toABGR(0.5), 0x66563412); +} + +TEST(ColorsColor, name) +{ + auto color = *Color::parse("red"); + ASSERT_FALSE(color.getName().size()); + color.setName("Rouge"); + ASSERT_EQ(color.getName(), "Rouge"); + + color.setName("Rouge"); + color.convert(Space::Type::HSL); + ASSERT_FALSE(color.getName().size()); +} + +TEST(ColorsColor, normalizeColor) +{ + auto color = *Color::parse("rgb(0, 0, 0)"); + color.set(0, 2.0); + ASSERT_EQ(color[0], 2.0); + color.set(1, 1.0); + color.set(2, -0.5); + color.normalize(); + ASSERT_EQ(color[0], 1.0); + ASSERT_EQ(color[1], 1.0); + ASSERT_EQ(color[2], 0.0); + + color.convert(Space::Type::HSL); + color.set(0, 4.1); + color.normalize(); + ASSERT_NEAR(color[0], 0.1, 0.001); + + color.set(0, -0.2); + color.normalize(); + ASSERT_NEAR(color[0], 0.8, 0.001); + + color.set(0, -2.2); + color.normalize(); + ASSERT_NEAR(color[0], 0.8, 0.001); + + color.setOpacity(4.2); + auto copy = color.normalized(); + ASSERT_NEAR(color[3], 4.2, 0.001); + ASSERT_NEAR(copy[3], 1.0, 0.001); +} + +TEST(ColorsColor, invertColor) +{ + auto color = *Color::parse("red"); + color.invert(); + ASSERT_EQ(color.toString(), "aqua"); + color.invert(); + ASSERT_EQ(color.toString(), "red"); + + color = *Color::parse("hsl(90,0.5,0.1)"); + color.invert(); + ASSERT_EQ(color.toString(), "hsl(270, 0.5, 0.9)"); + + color.invert(2); + ASSERT_EQ(color.toString(), "hsl(90, 0.5, 0.1)"); + + color = *Color::parse("rgb(255 255 255 0.2)"); + ASSERT_NEAR(color[0], 1, 0.001); + color.invert(); + ASSERT_NEAR(color[0], 0, 0.001); + ASSERT_NEAR(color[3], 0.2, 0.001); + + color.invert(0); + ASSERT_NEAR(color[0], 1, 0.001); + ASSERT_NEAR(color[3], 0.8, 0.001); +} + +TEST(ColorsColor, jitterColor) +{ + auto color = *Color::parse("gray"); + + std::srand(1); // fixed random seed + + color.jitter(0.1, 0xff); + EXPECT_EQ(color.toString(), "gray"); + +#ifdef __APPLE__ + // The hasher on llvm works differently so the random results are different + color.jitter(0.1); + EXPECT_EQ(color.toString(), "#737787"); + color.jitter(0.2); + EXPECT_EQ(color.toString(), "#717878"); + color.jitter(0.2, 0x02); + EXPECT_EQ(color.toString(), "#5a7881"); +#else + color.jitter(0.1); + EXPECT_EQ(color.toString(), "#897d87"); + color.jitter(0.2); + EXPECT_EQ(color.toString(), "#989278"); + color.jitter(0.2, 0x02); + EXPECT_EQ(color.toString(), "#8f9285"); +#endif + + color.setOpacity(0.5); + color.jitter(0.5, color.getPin(color.getOpacityChannel())); + EXPECT_EQ(color.getOpacity(), 0.5); +} + +TEST(ColorsColor, average) +{ + auto c1 = *Color::parse("#ff0000"); + auto c2 = *Color::parse("#0000ff"); + ASSERT_EQ(c1.averaged(c2).toString(), "#800080"); + ASSERT_EQ(c2.averaged(c1).toString(), "#800080"); + c1.setOpacity(0.5); + ASSERT_EQ(c1.averaged(c2, 0.25).toString(), "#bf00409f"); + c1.enableOpacity(false); + c2.setOpacity(0.5); + ASSERT_EQ(c1.averaged(c2, 0.75).toString(), "#4000bf"); + + c1 = Color(0x0); + c1.average(Color(0xffffffff), 0.25, 1); + EXPECT_EQ(c1.toString(), "#00404040"); + + c1 = Color(0x0); + c1.average(Color(0xffffffff), 0.25, 2); + EXPECT_EQ(c1.toString(), "#40004040"); + + c1 = Color(0x0); + c1.average(Color(0xffffffff), 0.25, 4 + 2); + EXPECT_EQ(c1.toString(), "#40000040"); + + c1 = Color(0x0); + c1.average(Color(0xffffffff), 0.25, c1.getPin(3)); + EXPECT_EQ(c1.toString(), "#40404000"); + + auto c3 = Color(0x1a1a1a1a); + c3.average(Color(0xffffffff), 0.2, 2); + EXPECT_EQ(c3.toString(), "#481a4848"); + c3.average(Color(0xffffffff), 0.3, 4 + 2); + EXPECT_EQ(c3.toString(), "#7f1a487f"); + c3.average(Color(0xffffffff), 0.5, c3.getPin(3)); + EXPECT_EQ(c3.toString(), "#bf8da37f"); + + c1 = Color(0x00000000); + c1.average(Color(0xffffffff), 0.1, 0); + ASSERT_NEAR(c1[0], 0.1, 0.001); + ASSERT_EQ(c1.toString(), "#1a1a1a1a"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/document-cms-test.cpp b/testfiles/src/colors/document-cms-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..096047d23c2ec0fdb9ee8ac81ba8c3fa2af22940 --- /dev/null +++ b/testfiles/src/colors/document-cms-test.cpp @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for tracking icc profiles in a document. + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/document-cms.h" + +#include + +#include "colors/color.h" +#include "colors/manager.h" +#include "colors/spaces/cms.h" +#include "document.h" +#include "inkscape.h" +#include "object/color-profile.h" +#include "object/sp-stop.h" + +static std::string icc_dir = INKSCAPE_TESTS_DIR "/data/colors/"; +static std::string svg_objs_file = INKSCAPE_TESTS_DIR "/data/colors/cms-in-objs.svg"; +static std::string svg_defs_file = INKSCAPE_TESTS_DIR "/data/colors/cms-in-defs.svg"; +static std::string grb_profile = INKSCAPE_TESTS_DIR "/data/colors/SwappedRedAndGreen.icc"; + +using namespace Inkscape; +using namespace Inkscape::Colors; + +namespace { + +class ColorDocumentCMSObjsTest : public ::testing::Test +{ +protected: + void SetUp() override + { + // Setup inkscape dependency + Inkscape::Application::create(false); + + // Allow lookup by ID and name with test icc profiles + auto &cms = Inkscape::Colors::CMS::System::get(); + cms.clearDirectoryPaths(); + cms.addDirectoryPath(icc_dir, false); + cms.refreshProfiles(); + + // Load the test svg file with a bunch of icc profiles + doc = SPDocument::createNewDoc(getDocFilename().c_str(), false); + } + std::unique_ptr doc; + + virtual std::string const &getDocFilename() const + { + return svg_objs_file; + } +}; + +TEST_F(ColorDocumentCMSObjsTest, loadDocument) +{ + auto &cm = Manager::get(); + auto &tr = doc->getDocumentCMS(); + + ASSERT_FALSE(tr.getSpace("nonsense")); + + // Internal spaces + ASSERT_TRUE(cm.find(Space::Type::CSSNAME)); + ASSERT_TRUE(cm.find(Space::Type::RGB)); + ASSERT_TRUE(cm.find(Space::Type::HSL)); + + // Document icc profiles + ASSERT_TRUE(tr.getSpace("grb")); + ASSERT_TRUE(tr.getSpace("cmyk-rcm")); + ASSERT_TRUE(tr.getSpace("cmyk-acm")); + + ASSERT_TRUE(cm.find(Space::Type::CMYK)); + ASSERT_EQ(cm.find(Space::Type::CMYK)->getName(), "DeviceCMYK"); + ASSERT_EQ(cm.find(Space::Type::RGB)->getName(), "RGB"); +} + +TEST_F(ColorDocumentCMSObjsTest, updateIntent) +{ + auto &tr = doc->getDocumentCMS(); + auto space = tr.getSpace("grb"); + ASSERT_TRUE(space); + + auto cp = tr.getColorProfileForSpace(space); + ASSERT_TRUE(cp); + + ASSERT_EQ(space->getIntent(), RenderingIntent::PERCEPTUAL); + ASSERT_EQ(cp->getRenderingIntent(), RenderingIntent::UNKNOWN); + ASSERT_EQ(cp->getAttribute("rendering-intent"), nullptr); + tr.setRenderingIntent("grb", RenderingIntent::PERCEPTUAL); + ASSERT_EQ(space->getIntent(), RenderingIntent::PERCEPTUAL); + ASSERT_EQ(cp->getRenderingIntent(), RenderingIntent::PERCEPTUAL); + ASSERT_EQ(std::string(cp->getAttribute("rendering-intent")), "perceptual"); + + space = tr.getSpace("cmyk-acm"); + ASSERT_EQ(space->getIntent(), RenderingIntent::ABSOLUTE_COLORIMETRIC); + + space = tr.getSpace("cmyk-rcm"); + ASSERT_EQ(space->getIntent(), RenderingIntent::RELATIVE_COLORIMETRIC); +} + +TEST_F(ColorDocumentCMSObjsTest, createColorProfile) +{ + auto &tr = doc->getDocumentCMS(); + ASSERT_FALSE(tr.getSpace("C.icc")); + + tr.attachProfileToDoc("C.icc", ColorProfileStorage::LOCAL_ID, RenderingIntent::AUTO); + ASSERT_TRUE(tr.getSpace("C.icc")); + auto space = tr.getSpace("C.icc"); + + ASSERT_TRUE(space); + ASSERT_EQ(space->getIntent(), RenderingIntent::AUTO); +} + +TEST_F(ColorDocumentCMSObjsTest, deleteColorProfile) +{ + auto &tr = doc->getDocumentCMS(); + auto cp0 = doc->getObjectById("cp2"); + ASSERT_TRUE(cp0); + + ASSERT_TRUE(tr.getSpace("cmyk-rcm")); + auto cp = tr.getColorProfileForSpace("cmyk-rcm"); + ASSERT_TRUE(cp); + cp->deleteObject(); + ASSERT_FALSE(tr.getSpace("cmyk-rcm")); +} + +TEST_F(ColorDocumentCMSObjsTest, cmsAddMultiple) +{ + auto &tr = doc->getDocumentCMS(); + ASSERT_TRUE(tr.getSpace("grb")); + ASSERT_EQ(tr.getSpace("grb")->getType(), Space::Type::RGB); + EXPECT_THROW(tr.addProfileURI(grb_profile, "grb", RenderingIntent::RELATIVE_COLORIMETRIC), ColorError); +} + +TEST_F(ColorDocumentCMSObjsTest, cmsParsingRGB) +{ + auto &tr = doc->getDocumentCMS(); + + auto space = tr.getSpace("grb"); + ASSERT_TRUE(space); + EXPECT_TRUE(space->isValid()); + EXPECT_EQ(space->getType(), Space::Type::RGB); + + auto color = *tr.parse("#000001 icc-color(grb, 1, 0.8, 0.6)"); + EXPECT_EQ(color.toString(), "#ccff99 icc-color(grb, 1, 0.8, 0.6)"); + EXPECT_EQ(color.toRGBA(), 0xccff99ff); + EXPECT_EQ(*color.getSpace(), *space); +} + +TEST_F(ColorDocumentCMSObjsTest, cmsParsingCMYK1) +{ + auto &tr = doc->getDocumentCMS(); + + auto space = tr.getSpace("cmyk-rcm"); + ASSERT_TRUE(space); + EXPECT_TRUE(space->isValid()); + EXPECT_EQ(space->getType(), Space::Type::CMYK); + + auto color = *tr.parse("#000002 icc-color(cmyk-rcm, 0.5, 0, 0, 0)"); + EXPECT_EQ(color.toString(), "#6dcff6 icc-color(cmyk-rcm, 0.5, 0, 0, 0)"); + EXPECT_EQ(color.toRGBA(), 0x6dcff6ff); + EXPECT_EQ(*color.getSpace(), *space); +} + +TEST_F(ColorDocumentCMSObjsTest, cmsParsingCMYK2) +{ + auto &tr = doc->getDocumentCMS(); + + auto space = tr.getSpace("cmyk-acm"); + ASSERT_TRUE(space); + EXPECT_TRUE(space->isValid()); + EXPECT_EQ(space->getType(), Space::Type::CMYK); + + auto color = *tr.parse("#000003 icc-color(cmyk-acm, 0.5, 0, 0, 0)"); + EXPECT_EQ(color.toString(), "#69b6d1 icc-color(cmyk-acm, 0.5, 0, 0, 0)"); + EXPECT_EQ(color.toRGBA(), 0x69b6d1ff); + EXPECT_EQ(*color.getSpace(), *space); +} + +TEST_F(ColorDocumentCMSObjsTest, applyConversion) +{ + auto &tr = doc->getDocumentCMS(); + auto grb = tr.getSpace("grb"); + auto rcm = tr.getSpace("cmyk-rcm"); + auto acm = tr.getSpace("cmyk-acm"); + ASSERT_TRUE(grb); + ASSERT_TRUE(rcm); + ASSERT_TRUE(acm); + + auto color = *Color::parse("red"); + ASSERT_EQ(color.toString(), "red"); + + // Converting an anonymous color fails + auto other = tr.parse("icc-color(bad, 1.0, 0.8, 0.6)"); + color.convert(other->getSpace()); + ASSERT_EQ(color.toString(), "red"); + ASSERT_FALSE(color.converted(other->getSpace())); + + // Specifying the space properly works + EXPECT_EQ(color.converted(grb)->toString(), "#ff0000 icc-color(grb, 0, 1, 0)"); + + // Double conversion does nothing + color.convert(grb); + color.convert(grb); + EXPECT_EQ(color.toString(), "#ff0000 icc-color(grb, 0, 1, 0)"); + + // Converting from one icc profile to another is possible + EXPECT_EQ(color.converted(rcm)->toString(), "#ed1c24 icc-color(cmyk-rcm, 0, 1, 1, 0)"); + // Same icc profile should keep the same cmyk values, but + // because the render intent is different the RGB changes + EXPECT_EQ(color.converted(acm)->toString(), "#cf2c2d icc-color(cmyk-acm, 0, 1, 1, 0)"); +} + +class ColorDocumentCMSDefsTest : public ColorDocumentCMSObjsTest +{ + std::string const &getDocFilename() const override + { + return svg_defs_file; + } +}; + +TEST_F(ColorDocumentCMSDefsTest, loadDocument) +{ + auto &tracker = doc->getDocumentCMS(); + auto spaces = tracker.getSpaces(); + + ASSERT_EQ(spaces.size(), 1); + ASSERT_EQ(spaces[0]->getName(), "Artifex-CMYK-SWOP-Profile"); + ASSERT_TRUE(spaces[0]->isValid()); + + auto objects = tracker.getObjects(); + ASSERT_EQ(objects.size(), 1); + ASSERT_EQ(std::string(objects[0]->getId()), "artefact-cmyk"); + + auto stop = cast(doc->getObjectById("stop2212")); + ASSERT_TRUE(stop); + auto color = stop->getColor(); + + // Test the expected values actually return + std::string expected = "#2c292a icc-color(Artifex-CMYK-SWOP-Profile, 0, 0, 0, 1)"; + EXPECT_TRUE(tracker.parse(expected)->getSpace()->isValid()); + EXPECT_EQ(tracker.parse(expected)->toString(), expected); + EXPECT_EQ(color.toString(), expected); + + auto space = dynamic_pointer_cast(color.getSpace()); + EXPECT_TRUE(space->isValid()); + EXPECT_EQ(space, spaces[0]); + EXPECT_NEAR(color[0], 0, 0.01); + EXPECT_NEAR(color[1], 0, 0.01); + EXPECT_NEAR(color[2], 0, 0.01); + EXPECT_NEAR(color[3], 1, 0.01); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ + +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/dragndrop-test.cpp b/testfiles/src/colors/dragndrop-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e56135fa0b45ca2fe954ffcb8df6e8ea9f9a0bee --- /dev/null +++ b/testfiles/src/colors/dragndrop-test.cpp @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for color xml conversions. + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/dragndrop.h" + +#include + +#include "colors/color.h" + +using namespace Inkscape; +using namespace Inkscape::Colors; + +namespace { + +TEST(ColorDragAndDrop, test_getMimeData_none) +{ + auto data = getMIMEData(*Color::parse("red"), "text/bad-format"); + ASSERT_EQ(data.size(), 0); + data = getMIMEData(NoColor(), "text/bad-format"); + ASSERT_EQ(data.size(), 0); +} + +TEST(ColorDragAndDrop, test_getMimeData_oswb) +{ + auto data = getMIMEData(*Color::parse("red"), "application/x-oswb-color"); + ASSERT_EQ(data.size(), 138); + ASSERT_EQ(data[0], '<'); + ASSERT_EQ(data[10], 'i'); + ASSERT_EQ(data[20], 'e'); + ASSERT_EQ(data[30], 'U'); +} + +TEST(ColorDragAndDrop, test_getMimeData_x_color) +{ + auto data = getMIMEData(*Color::parse("red"), "application/x-color"); + ASSERT_EQ(data.size(), 8); + ASSERT_EQ(data[0], '\xFF'); + ASSERT_EQ(data[2], '\x0'); + ASSERT_EQ(data[4], '\x0'); + + data = getMIMEData(NoColor(), "application/x-color"); + ASSERT_EQ(data.size(), 8); + ASSERT_EQ(data[0], '\x0'); + ASSERT_EQ(data[2], '\x0'); + ASSERT_EQ(data[4], '\x0'); +} + +TEST(ColorDragAndDrop, test_getMimeData_text) +{ + auto data = getMIMEData(*Color::parse("red"), "text/plain"); + ASSERT_EQ(data.size(), 3); + ASSERT_EQ(data[0], 'r'); + ASSERT_EQ(data[2], 'd'); + + data = getMIMEData(NoColor(), "text/plain"); + ASSERT_EQ(std::string(data.begin(), data.end()), "none"); +} + +TEST(ColorDragAndDrop, test_fromMimeData) +{ + auto data = getMIMEData(*Color::parse("red"), "application/x-oswb-color"); + auto paint = fromMIMEData(data, "application/x-oswb-color"); + ASSERT_EQ(std::get(paint).toString(), "red"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/manager-test.cpp b/testfiles/src/colors/manager-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..35e0e28fbe3800c10889187d4e2d21d3cd8399f8 --- /dev/null +++ b/testfiles/src/colors/manager-test.cpp @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for color objects. + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/manager.h" + +#include + +#include "colors/cms/system.h" +#include "colors/color.h" +#include "colors/spaces/base.h" +#include "colors/spaces/cms.h" +#include "colors/spaces/rgb.h" +#include "colors/spaces/components.h" +#include "colors/spaces/enum.h" +#include "document.h" +#include "inkscape.h" +#include "object/color-profile.h" + +using namespace Inkscape; +using namespace Inkscape::Colors; + +namespace { + +class TestManager : public Manager +{ +public: + TestManager() = default; + ~TestManager() = default; + + std::shared_ptr testAddSpace(Space::AnySpace *space) + { + return addSpace(space); + } + + bool testRemoveSpace(std::shared_ptr space) + { + return removeSpace(std::move(space)); + } + +}; + +TEST(ColorManagerTest, spaceComponents) +{ + auto &cm = Manager::get(); + + ASSERT_TRUE(cm.find(Space::Type::RGB)); + auto comp = cm.find(Space::Type::RGB)->getComponents(); + ASSERT_EQ(comp.size(), 3); + ASSERT_EQ(comp[0].name, "_R:"); + ASSERT_EQ(comp[1].name, "_G:"); + ASSERT_EQ(comp[2].name, "_B:"); + + ASSERT_TRUE(cm.find(Space::Type::HSL)); + comp = cm.find(Space::Type::HSL)->getComponents(true); + ASSERT_EQ(comp.size(), 4); + ASSERT_EQ(comp[0].name, "_H:"); + ASSERT_EQ(comp[1].name, "_S:"); + ASSERT_EQ(comp[2].name, "_L:"); + ASSERT_EQ(comp[3].name, "_A:"); + + ASSERT_TRUE(cm.find(Space::Type::CMYK)); + comp = cm.find(Space::Type::CMYK)->getComponents(false); + ASSERT_EQ(comp.size(), 4); + ASSERT_EQ(comp[0].name, "_C:"); + ASSERT_EQ(comp[1].name, "_M:"); + ASSERT_EQ(comp[2].name, "_Y:"); + ASSERT_EQ(comp[3].name, "_K:"); +} + +TEST(ColorManagerTest, addAndRemoveSpaces) +{ + auto cm = TestManager(); + + auto rgb = cm.find(Space::Type::RGB); + ASSERT_TRUE(rgb); + + EXPECT_THROW(cm.testAddSpace(rgb.get()), ColorError); + + ASSERT_TRUE(cm.testRemoveSpace(rgb)); + ASSERT_FALSE(cm.testRemoveSpace(rgb)); + ASSERT_FALSE(cm.find(Space::Type::RGB)); + + cm.testAddSpace(new Space::RGB()); + ASSERT_TRUE(cm.find(Space::Type::RGB)); +} + + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/parser-test.cpp b/testfiles/src/colors/parser-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1d59118c146fd740ecd40083793e913c12611986 --- /dev/null +++ b/testfiles/src/colors/parser-test.cpp @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for color objects. + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/parser.h" + +#include + +#include "../test-utils.h" + +using namespace Inkscape; +using namespace Inkscape::Colors; + +namespace { + +TEST(ColorsParser, test_prefix_parsing) +{ + std::istringstream tests("#rgb(hsl( color( srgb icc-color(profile"); + ASSERT_EQ(Parser::getCssPrefix(tests), "#"); + ASSERT_EQ(Parser::getCssPrefix(tests), "rgb"); + ASSERT_EQ(Parser::getCssPrefix(tests), "hsl"); + ASSERT_EQ(Parser::getCssPrefix(tests), "srgb"); + ASSERT_EQ(Parser::getCssPrefix(tests), "icc-color"); + + std::istringstream fails("rgb fail"); + ASSERT_EQ(Parser::getCssPrefix(fails), ""); +} + +void testCssNumber(std::istringstream &ss, double in_value, std::string in_unit, bool in_end = false) +{ + double out_value; + std::string out_unit; + bool out_end = false; + + ASSERT_TRUE(Parser::css_number(ss, out_value, out_unit, out_end, ',')) << in_value << in_unit; + EXPECT_NEAR(out_value, in_value, 0.001); + EXPECT_EQ(out_unit, in_unit); + EXPECT_EQ(out_end, in_end) << in_value << in_unit << " '" << ss.peek() << "'"; +} + +TEST(ColorsParser, test_number_parsing) +{ + try { + std::locale::global(std::locale("C")); + } catch (std::runtime_error &e) { + ASSERT_TRUE(false) << "Locale 'C' not available for testing"; + } + + std::istringstream tests("1.2 .2 5turn 120deg 20%,5,5, 2cm ,4 9000) 0.0002 5t) 42 ) "); + + testCssNumber(tests, 1.2, ""); + testCssNumber(tests, 0.2, ""); + testCssNumber(tests, 5, "turn"); + testCssNumber(tests, 120, "deg"); + testCssNumber(tests, 20, "%"); + testCssNumber(tests, 5, ""); + testCssNumber(tests, 5, ""); + testCssNumber(tests, 2, "cm"); + testCssNumber(tests, 4, ""); + testCssNumber(tests, 9000, "", true); + testCssNumber(tests, 0.0002, ""); + testCssNumber(tests, 5, "t", true); + testCssNumber(tests, 42, "", true); +} + +TEST(ColorsParser, test_alt_locale) +{ + std::locale was; + try { + was = std::locale::global(std::locale("de_DE.UTF8")); + } catch (std::runtime_error &e) { + GTEST_SKIP() << "Skipping all locale test, locale not available"; + } + + std::istringstream tests("1.2 .2 5turn 120deg 20%,5,5, 2cm ,4 9000) 0.0002 5t) 42 ) "); + + testCssNumber(tests, 1.2, ""); + testCssNumber(tests, 0.2, ""); + testCssNumber(tests, 5, "turn"); + testCssNumber(tests, 120, "deg"); + testCssNumber(tests, 20, "%"); + testCssNumber(tests, 5, ""); + testCssNumber(tests, 5, ""); + testCssNumber(tests, 2, "cm"); + testCssNumber(tests, 4, ""); + testCssNumber(tests, 9000, "", true); + testCssNumber(tests, 0.0002, ""); + testCssNumber(tests, 5, "t", true); + testCssNumber(tests, 42, "", true); + + std::locale::global(was); +} + +void testCssValue(std::string test) +{ + std::istringstream tests("2.0 200% .3, 20 / 5.0)"); + std::vector output; + bool end = false; + ASSERT_TRUE(Parser::append_css_value(tests, output, end, ',', 2) // Value 1 + && Parser::append_css_value(tests, output, end, ',', 3) // Value 2 + && Parser::append_css_value(tests, output, end, ',', 0.1) // Value 3 + && Parser::append_css_value(tests, output, end, '/', 5) // Value 4 + && Parser::append_css_value(tests, output, end)); // Opacity + ASSERT_TRUE(end); + ASSERT_EQ(output.size(), 5); + for (auto i = 0; i < 5; i++) { + EXPECT_NEAR(output[i], (double)(i + 1), 0.001); + } +} + +TEST(ColorsParser, parse_append_css_value) +{ + testCssValue("2.0 200% .3, 20 / 5.0)"); + testCssValue("2.0 200% .3, 20)"); + testCssValue("360deg 3turn .3, 20)"); +} + +TEST(ColorsParser, parse_hex) +{ + auto parser = HexParser(); + ASSERT_EQ(parser.getPrefix(), "#"); + + bool more = false; + std::vector output; + std::istringstream p("000001 icc-profile(foo"); + + ASSERT_EQ(parser.parseColor(p, output, more), ""); + ASSERT_TRUE(more); +} + +TEST(ColorsParser, parse) +{ + Space::Type space_type; + std::string cms_name; + std::vector values; + std::vector fallback; + ASSERT_TRUE(Parsers::get().parse("rgb(128, 255, 255)", space_type, cms_name, values, fallback)); + + ASSERT_EQ(space_type, Space::Type::RGB); + ASSERT_EQ(cms_name, ""); + ASSERT_EQ(fallback.size(), 0); + EXPECT_TRUE(VectorIsNear(values, {0.5, 1.0, 1.0}, 0.01)); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/printer-test.cpp b/testfiles/src/colors/printer-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6e906c0231816d0115a05df846d6e367e0b1a59c --- /dev/null +++ b/testfiles/src/colors/printer-test.cpp @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for printer base class + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/printer.h" + +#include + +using namespace Inkscape::Colors; + +namespace { + +TEST(ColorsPrinter, PrinterBasics) +{ + std::stringstream number; + try { + number.imbue(std::locale("C")); + } catch (std::runtime_error &e) { + ASSERT_TRUE(false) << "Locale 'C' not available for testing"; + } + + number.precision(12); + number << std::fixed << 3.1415; + + auto oo = CssPrinter(3, "prefix", "", " "); + oo << (int)1 << (double)3.3 << (double)0.0; + ASSERT_EQ(std::string(oo), "prefix(1 3.3 0)"); +} + +TEST(ColorsPrinter, PrinterLocale) +{ + std::locale was; + try { + was = std::locale::global(std::locale("de_DE.utf8")); + } catch (std::runtime_error &e) { + GTEST_SKIP() << "Skipping all locale test, locale not available"; + } + + auto oo = CssPrinter(3, "prefix", "", " "); + oo << 1.2 << 3.3 << 0.0234; + ASSERT_EQ(std::string(oo), "prefix(1.2 3.3 0.023)"); + std::locale::global(was); +} + +TEST(ColorsPrinter, LegacyPrinter) +{ + auto oo = CssLegacyPrinter(3, "leg", false); + oo << 1.0 << 3.3 << 0.0; + ASSERT_EQ(std::string(oo), "leg(1, 3.3, 0)"); + + oo = CssLegacyPrinter(3, "leg", true); + oo << 1.2 << 3.3 << 0.0 << 0.5; + ASSERT_EQ(std::string(oo), "lega(1.2, 3.3, 0, 0.5)"); +} + +TEST(ColorsPrinter, FuncPrinter) +{ + auto oo = CssFuncPrinter(4, "func"); + oo << 1.0 << 3.3 << 0.0 << 1.2; + ASSERT_EQ(std::string(oo), "func(1 3.3 0 1.2)"); + + oo = CssFuncPrinter(4, "func"); + oo << 1.0 << 3.3 << 0.0 << 1.2 << 0.5; + ASSERT_EQ(std::string(oo), "func(1 3.3 0 1.2 / 50%)"); +} + +TEST(ColorsPrinter, ColorPrinter) +{ + auto oo = CssColorPrinter(3, "ident"); + oo << 1.0 << 3.3 << 0.0 << 0.5; + ASSERT_EQ(std::string(oo), "color(ident 1 3.3 0 / 50%)"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-cms-test.cpp b/testfiles/src/colors/spaces-cms-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1a8cd6f004adbc6570dc12a19f948ee30bb23666 --- /dev/null +++ b/testfiles/src/colors/spaces-cms-test.cpp @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for color objects. + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include + +#include "colors/cms/profile.h" +#include "colors/color.h" +#include "colors/parser.h" +#include "colors/spaces/cms.h" +#include "colors/document-cms.h" + +using namespace Inkscape::Colors; + +static std::string cmyk_icc = INKSCAPE_TESTS_DIR "/data/colors/default_cmyk.icc"; + +namespace { + +// Get access to protected members for testing +class CMS : public Space::CMS +{ +public: + CMS(unsigned size, Space::Type type = Space::Type::CMYK) + : Space::CMS("test-profile", size, type){}; + CMS(std::shared_ptr profile) + : Space::CMS(profile){}; + std::string toString(std::vector const &values, bool opacity = true) const + { + return Space::CMS::toString(values, opacity); + } + bool testOverInk(double black) { return overInk({1.0, 1.0, 1.0, black}); } + bool testOutOfGamut(std::vector values, std::shared_ptr space) + { + return outOfGamut(values, space); + } + void testName(std::string const &name) { setName(name); } +}; + +TEST(ColorsSpacesCms, parseColor) +{ + auto parser = Space::CMS::CmsParser(); + ASSERT_EQ(parser.getPrefix(), "icc-color"); + + bool more = false; + std::vector output; + std::istringstream ss("stress-test, 0.2, 90%,2, .3 5%)"); + auto name = parser.parseColor(ss, output, more); + ASSERT_EQ(name, "stress-test"); + ASSERT_EQ(output.size(), 5); + ASSERT_EQ(output[0], 0.2); + ASSERT_EQ(output[1], 0.9); + ASSERT_EQ(output[2], 2.0); + ASSERT_EQ(output[3], 0.3); + ASSERT_EQ(output[4], 0.05); +} + +TEST(ColorsSpacesCms, realColor) +{ + auto cmyk_profile = Inkscape::Colors::CMS::Profile::create_from_uri(cmyk_icc); + auto cmyk = std::make_shared(cmyk_profile); + auto color = Color(cmyk, {0, 0, 0, 1}); + + EXPECT_EQ(color.toString(), "#2c292a icc-color(Artifex-CMYK-SWOP-Profile, 0, 0, 0, 1)"); + EXPECT_EQ(color.toRGBA(), 0x2c292aff); + EXPECT_EQ(color.toRGBA(0.5), 0x2c292a80); + EXPECT_EQ(color.converted(Space::Type::RGB)->toString(), "#2c292a"); + EXPECT_FALSE(color.hasOpacity()); + EXPECT_EQ(color.getOpacity(), 1.0); + + color.addOpacity(0.5); + // Opacity isn't stored in the icc-color string, because it's not supported. + EXPECT_TRUE(color.hasOpacity()); + EXPECT_EQ(color.getOpacity(), 0.5); + EXPECT_EQ(color.toString(), "#2c292a icc-color(Artifex-CMYK-SWOP-Profile, 0, 0, 0, 1)"); + EXPECT_EQ(color.toRGBA(), 0x2c292a80); + EXPECT_EQ(color.toRGBA(0.5), 0x2c292a40); + EXPECT_EQ(color.converted(Space::Type::RGB)->toString(), "#2c292a80"); + + color = Color(0x2c292aff); + EXPECT_TRUE(color.convert(cmyk)); + EXPECT_EQ(color.toString(), "#201c1c icc-color(Artifex-CMYK-SWOP-Profile, 0.697, 0.696, 0.658, 0.856)"); +} + +TEST(ColorsSpacesCms, fallbackColor) +{ + auto tr = DocumentCMS(nullptr); + auto color = *tr.parse("#0080ff icc-color(missing-profile, 1, 2, 3)"); + EXPECT_EQ(color.toString(), "#0080ff icc-color(missing-profile, 1, 2, 3)"); + EXPECT_EQ(color.toRGBA(), 0x0080ffff); + EXPECT_EQ(color.toRGBA(0.5), 0x0080ff80); + EXPECT_EQ(color.converted(Space::Type::RGB)->toString(), "#0080ff"); + EXPECT_FALSE(color.hasOpacity()); + EXPECT_EQ(color.getOpacity(), 1.0); + + color.addOpacity(0.5); + EXPECT_EQ(color.toString(), "#0080ff icc-color(missing-profile, 1, 2, 3)"); + EXPECT_EQ(color.toRGBA(), 0x0080ff80); + EXPECT_EQ(color.toRGBA(0.5), 0x0080ff40); +} + +TEST(ColorsSpacesCms, renderingIntent) +{ + auto cmyk_profile = Inkscape::Colors::CMS::Profile::create_from_uri(cmyk_icc); + auto cmyk = std::make_shared(cmyk_profile); + cmyk->testName("vals"); + + auto color1 = Color(cmyk, {0, 0, 0, 1}); + auto color2 = Color(cmyk, {0.5, 0, 0, 0}); + EXPECT_EQ(cmyk->getIntent(), RenderingIntent::UNKNOWN); + + cmyk->setIntent(RenderingIntent::PERCEPTUAL); + EXPECT_EQ(cmyk->getIntent(), RenderingIntent::PERCEPTUAL); + EXPECT_EQ(color1.toString(), "#2c292a icc-color(vals, 0, 0, 0, 1)"); + EXPECT_EQ(color1.converted(Space::Type::RGB)->toString(), "#2c292a"); + EXPECT_EQ(color2.toString(), "#70d0f6 icc-color(vals, 0.5, 0, 0, 0)"); + + cmyk->setIntent(RenderingIntent::RELATIVE_COLORIMETRIC); + EXPECT_EQ(cmyk->getIntent(), RenderingIntent::RELATIVE_COLORIMETRIC); + EXPECT_EQ(color1.toString(), "#231f20 icc-color(vals, 0, 0, 0, 1)"); + EXPECT_EQ(color1.converted(Space::Type::RGB)->toString(), "#231f20"); + EXPECT_EQ(color2.toString(), "#6dcff6 icc-color(vals, 0.5, 0, 0, 0)"); + + cmyk->setIntent(RenderingIntent::SATURATION); + EXPECT_EQ(cmyk->getIntent(), RenderingIntent::SATURATION); + EXPECT_EQ(color1.toString(), "#2c292a icc-color(vals, 0, 0, 0, 1)"); + EXPECT_EQ(color1.converted(Space::Type::RGB)->toString(), "#2c292a"); + EXPECT_EQ(color2.toString(), "#70d0f6 icc-color(vals, 0.5, 0, 0, 0)"); + + cmyk->setIntent(RenderingIntent::ABSOLUTE_COLORIMETRIC); + EXPECT_EQ(cmyk->getIntent(), RenderingIntent::ABSOLUTE_COLORIMETRIC); + EXPECT_EQ(color1.toString(), "#2f2d2c icc-color(vals, 0, 0, 0, 1)"); + EXPECT_EQ(color1.converted(Space::Type::RGB)->toString(), "#2f2d2c"); + EXPECT_EQ(color2.toString(), "#69b6d1 icc-color(vals, 0.5, 0, 0, 0)"); + + cmyk->setIntent(RenderingIntent::RELATIVE_COLORIMETRIC_NOBPC); + EXPECT_EQ(cmyk->getIntent(), RenderingIntent::RELATIVE_COLORIMETRIC_NOBPC); + EXPECT_EQ(color1.toString(), "#373535 icc-color(vals, 0, 0, 0, 1)"); + EXPECT_EQ(color1.converted(Space::Type::RGB)->toString(), "#373535"); + EXPECT_EQ(color2.toString(), "#73d1f6 icc-color(vals, 0.5, 0, 0, 0)"); + + // These should be the same as PERCEPTUAL + cmyk->setIntent(RenderingIntent::UNKNOWN); + EXPECT_EQ(cmyk->getIntent(), RenderingIntent::UNKNOWN); + EXPECT_EQ(color1.toString(), "#2c292a icc-color(vals, 0, 0, 0, 1)"); + EXPECT_EQ(color1.converted(Space::Type::RGB)->toString(), "#2c292a"); + EXPECT_EQ(color2.toString(), "#70d0f6 icc-color(vals, 0.5, 0, 0, 0)"); + + cmyk->setIntent(RenderingIntent::AUTO); + EXPECT_EQ(cmyk->getIntent(), RenderingIntent::AUTO); + EXPECT_EQ(color1.toString(), "#2c292a icc-color(vals, 0, 0, 0, 1)"); + EXPECT_EQ(color1.converted(Space::Type::RGB)->toString(), "#2c292a"); + EXPECT_EQ(color2.toString(), "#70d0f6 icc-color(vals, 0.5, 0, 0, 0)"); +} + +TEST(ColorsSpacesCms, printColor) +{ + auto space = CMS(4); + + ASSERT_FALSE(space.isValid()); + ASSERT_EQ(space.toString({}), ""); + ASSERT_EQ(space.toString({1}), ""); + ASSERT_EQ(space.toString({1, 2, 3, 4}), ""); + ASSERT_EQ(space.toString({0, 0.5, 1, 1, 2, 3, 4}), "#0080ff icc-color(test-profile, 1, 2, 3, 4)"); + + space = CMS(2); + ASSERT_FALSE(space.isValid()); + ASSERT_EQ(space.toString({1}), ""); + ASSERT_EQ(space.toString({0, 0.5, 1, 1, 2}), "#0080ff icc-color(test-profile, 1, 2)"); + ASSERT_EQ(space.toString({0, 0, 0, 1, 2, 3}), "#000000 icc-color(test-profile, 1, 2)"); + + auto srgb = Inkscape::Colors::CMS::Profile::create_srgb(); + space = CMS(srgb); + space.setIntent(RenderingIntent::AUTO); + ASSERT_TRUE(space.isValid()); + ASSERT_EQ(space.toString({1}), ""); + ASSERT_EQ(space.toString({0, 0.5001, 1}), "#0080ff icc-color(sRGB-built-in, 0, 0.5, 1)"); +} + +TEST(ColorsSpacesCms, outOfGamut) +{ + auto srgb = Inkscape::Colors::CMS::Profile::create_srgb(); + auto cmyk = Inkscape::Colors::CMS::Profile::create_from_uri(cmyk_icc); + auto space = CMS(srgb); + auto to_space = std::make_shared(cmyk); + + EXPECT_FALSE(space.testOutOfGamut({0.83, 0.19, 0.49}, to_space)); + // An RGB color (magenta) which is outside the cmyk color profile + EXPECT_TRUE(space.testOutOfGamut({1.0, 0.0, 1.0}, to_space)); + + auto from_space = std::make_shared(srgb); + auto pink = Inkscape::Colors::Color(from_space, {0.83, 0.19, 0.49}); + EXPECT_FALSE(pink.isOutOfGamut(to_space)); + + auto magenta = Inkscape::Colors::Color(from_space, {1.0, 0.0, 1.0}); + EXPECT_TRUE(magenta.isOutOfGamut(to_space)); +} + +TEST(ColorsSpacesCms, overInk) +{ + auto space = CMS(4, Space::Type::CMYK); + ASSERT_TRUE(space.testOverInk(0.21)); + ASSERT_FALSE(space.testOverInk(0.19)); + space = CMS(4, Space::Type::RGB); + ASSERT_FALSE(space.testOverInk(0.21)); + ASSERT_FALSE(space.testOverInk(0.19)); +} + +}; // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-cmyk-test.cpp b/testfiles/src/colors/spaces-cmyk-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c4744ebd6d839fbc3340b078eee369ed93cf71bc --- /dev/null +++ b/testfiles/src/colors/spaces-cmyk-test.cpp @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for the device-cmyk css color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/spaces/cmyk.h" +#include "spaces-testbase.h" + +namespace { + +using Space::Type::CMYK; +using Space::Type::RGB; + +class ColorsSpacesCmykProtected + : public Inkscape::Colors::Space::DeviceCMYK + , public testing::Test +{ +}; // Access protected + +// clang-format off +INSTANTIATE_TEST_SUITE_P(ColorsSpacesCmyk, fromString, testing::Values( + // Taken from the w3c device-cmyk example chart + _P(in, "device-cmyk(0 0.2 0.2 0.2)", { 0, 0.2, 0.2, 0.2 }, 0xcca3a3ff), + _P(in, "device-cmyk(30% 0.2 0.2 0.0)", { 0.3, 0.2, 0.2, 0 }, 0xb3ccccff), + _P(in, "device-cmyk(0 0.4 0.4 0.3)", { 0, 0.4, 0.4, 0.3 }, 0xb36b6bff), + _P(in, "device-cmyk(0 0.6 60% 0.5)", { 0, 0.6, 0.6, 0.5 }, 0x803333ff), + _P(in, "device-cmyk(0.3 60% 0.6 10%)", { 0.3, 0.6, 0.6, 0.1 }, 0xa15c5cff), + _P(in, " device-cmyk(90% 0.6 0.6 0) ", { 0.9, 0.6, 0.6, 0 }, 0x196666ff), + _P(in, "device-cmyk(0 0.8 0.8 0.2)", { 0.0, 0.8, 0.8, 0.2 }, 0xcc2929ff), + _P(in, "device-cmyk(0 1.0 1.0 0.1 / 0.5)", { 0.0, 1.0, 1.0, 0.1, 0.5 }, 0xe6000080) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesCmyk, badColorString, testing::Values( + "device-cmyk", "device-cmyk(", "device-cmyk(10%,", + "device-cmyk(1.0, 1.0, 1.0)" +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesCmyk, toString, testing::Values( + _P(out, CMYK, { 0.1, 0.2, 0.8, 0.1 }, "device-cmyk(0.1 0.2 0.8 0.1)"), + _P(out, CMYK, { 0.2, 0.1, 0.2, 0.1 }, "device-cmyk(0.2 0.1 0.2 0.1)"), + _P(out, CMYK, { 0.3, 0.3, 0.0, 0.5 }, "device-cmyk(0.3 0.3 0 0.5)"), + _P(out, CMYK, { 0.9, 0.0, 0.2, 0.6, 0.8 }, "device-cmyk(0.9 0 0.2 0.6 / 80%)"), + _P(out, CMYK, { 0.9, 0.0, 0.2, 0.6, 0.8 }, "device-cmyk(0.9 0 0.2 0.6)", false) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesCmyk, convertColorSpace, testing::Values( + _P(inb, CMYK, {1.000, 0.000, 0.000, 0.000}, RGB, {0.000, 1.000, 1.000}), + _P(inb, CMYK, {0.000, 1.000, 0.000, 0.000}, RGB, {1.000, 0.000, 1.000}), + _P(inb, CMYK, {0.000, 0.000, 1.000, 0.000}, RGB, {1.000, 1.000, 0.000}), + _P(inb, CMYK, {0.000, 0.000, 0.000, 1.000}, RGB, {0.000, 0.000, 0.000}), + _P(inb, CMYK, {1.000, 1.000, 0.000, 0.000}, RGB, {0.000, 0.000, 1.000}), + _P(inb, CMYK, {0.000, 1.000, 1.000, 0.000}, RGB, {1.000, 0.000, 0.000}), + _P(inb, CMYK, {1.000, 0.000, 1.000, 0.000}, RGB, {0.000, 1.000, 0.000}), + + // No conversion + _P(inb, CMYK, {1.000, 0.400, 0.200, 0.300}, CMYK, {1.000, 0.400, 0.200, 0.300}, false) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesCmyk, normalize, testing::Values( + _P(inb, CMYK, { 0.5, 0.5, 0.5, 0.5, 0.5 }, CMYK, { 0.5, 0.5, 0.5, 0.5, 0.5 }), + _P(inb, CMYK, { 1.2, 1.2, 1.2, 1.2, 1.2 }, CMYK, { 1.0, 1.0, 1.0, 1.0, 1.0 }), + _P(inb, CMYK, {-0.2, -0.2, -0.2, -0.2, -0.2 }, CMYK, { 0.0, 0.0, 0.0, 0.0, 0.0 }), + _P(inb, CMYK, { 0.0, 0.0, 0.0, 0.0, 0.0 }, CMYK, { 0.0, 0.0, 0.0, 0.0, 0.0 }), + _P(inb, CMYK, { 1.0, 1.0, 1.0, 1.0, 1.0 }, CMYK, { 1.0, 1.0, 1.0, 1.0, 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesCmyk, randomConversion) +{ + GTEST_SKIP(); // cmyk isn't reflective + EXPECT_TRUE(RandomPassthrough(CMYK, RGB, 1)); +} + +TEST(ColorsSpacesCmyk, components) +{ + auto c = Manager::get().find(CMYK)->getComponents(); + ASSERT_EQ(c.size(), 4); + ASSERT_EQ(c[0].id, "c"); + ASSERT_EQ(c[1].id, "m"); + ASSERT_EQ(c[2].id, "y"); + ASSERT_EQ(c[3].id, "k"); +} + +TEST_F(ColorsSpacesCmykProtected, overInk) +{ + ASSERT_TRUE(overInk({1.0, 1.0, 1.0, 0.21})); + ASSERT_FALSE(overInk({0.0, 1.0, 1.0, 0.19})); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-gray-test.cpp b/testfiles/src/colors/spaces-gray-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1a0af29c331e83466e7120135e6378c0f858695d --- /dev/null +++ b/testfiles/src/colors/spaces-gray-test.cpp @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for a Grayscale color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "spaces-testbase.h" + +namespace { + +using Space::Type::Gray; +using Space::Type::RGB; + +// There is no CSS for Gray, it was remoed from css color module 4 draft in 2018 +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(fromString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(badColorString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(toString); + +// clang-format off +INSTANTIATE_TEST_SUITE_P(ColorsSpacesGray, convertColorSpace, testing::Values( + _P(inb, Gray, {0.7}, RGB, {0.7, 0.7, 0.7}), + + // No conversion + _P(inb, Gray, {0.2}, Gray, {0.2}) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesGray, normalize, testing::Values( + _P(inb, Gray, { 0.5 }, Gray, { 0.5 }), + _P(inb, Gray, { 1.2 }, Gray, { 1.0 }), + _P(inb, Gray, {-0.2 }, Gray, { 0.0 }), + _P(inb, Gray, { 0.0 }, Gray, { 0.0 }), + _P(inb, Gray, { 1.0 }, Gray, { 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesGray, randomConversion) +{ + EXPECT_TRUE(RandomPassthrough(Gray, RGB, 100)); +} + +TEST(ColorsSpacesGray, components) +{ + auto c = Manager::get().find(Gray)->getComponents(); + ASSERT_EQ(c.size(), 1); + ASSERT_EQ(c[0].id, "gray"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-hsl-test.cpp b/testfiles/src/colors/spaces-hsl-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..24581e8935a03aba02010627a898dd46f1992f4e --- /dev/null +++ b/testfiles/src/colors/spaces-hsl-test.cpp @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for the HSL color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "spaces-testbase.h" + +namespace { + +using Space::Type::HSL; +using Space::Type::RGB; + +// clang-format off +INSTANTIATE_TEST_SUITE_P(ColorsSpacesHsl, fromString, testing::Values( + _P(in, "hsl(80, 1, 0.5)", { 0.222, 1, 0.5 }, 0xaaff00ff), + _P(in, "hsl(360,0.5,0)", { 1.0, 0.5, 0 }, 0x000000ff), + _P(in, "hsl(180deg, 100%, 50%)", { 0.5, 1, 0.5 }, 0x00ffffff), + _P(in, "hsl(0.5turn 100% 50%)", { 0.5, 1, 0.5 }, 0x00ffffff), + _P(in, " hsl(20, 1, 0.5)", { 0.055, 1, 0.5 }, 0xff5500ff), + _P(in, "hsl(50%, 100%, 50% / 50%)", { 0.5, 1, 0.5, 0.5 }, 0x00ffff80), + _P(in, "hsla(30, 0, 0.5, 0.5)", { 0.083, 0, 0.5, 0.5 }, 0x80808080) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesHsl, badColorString, testing::Values( + "hsl", "hsl(", "hsl(360," +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesHsl, toString, testing::Values( + _P(out, HSL, { 0.333, 0.2, 0.8 }, "hsl(119, 0.2, 0.8)"), + _P(out, HSL, { 0.333, 0.8, 0.258 }, "hsl(119, 0.8, 0.258)"), + _P(out, HSL, { 1.0, 0.5, 0.004 }, "hsl(360, 0.5, 0.004)"), + _P(out, HSL, { 0, 1, 0.2, 0.8 }, "hsla(0, 1, 0.2, 0.8)", true), + _P(out, HSL, { 0, 1, 0.2, 0.8 }, "hsl(0, 1, 0.2)", false) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesHsl, convertColorSpace, testing::Values( + // 20 random tests generated by python3 colorsys.hls_to_rgb() + _P(inb, HSL, {0.248, 0.225, 0.453}, RGB, {0.455, 0.554, 0.351}), + _P(inb, HSL, {0.257, 0.011, 0.403}, RGB, {0.403, 0.407, 0.399}, false), + // XXX GOT {0.250, 0.001, 0.403} in inverse (RGB to HSL) + _P(inb, HSL, {0.415, 0.514, 0.565}, RGB, {0.341, 0.789, 0.561}), + _P(inb, HSL, {0.528, 0.949, 0.408}, RGB, {0.021, 0.664, 0.795}), + _P(inb, HSL, {0.182, 0.455, 0.152}, RGB, {0.209, 0.222, 0.083}), + _P(inb, HSL, {0.334, 0.320, 0.265}, RGB, {0.181, 0.350, 0.181}), + _P(inb, HSL, {0.942, 0.401, 0.881}, RGB, {0.929, 0.833, 0.866}), + _P(inb, HSL, {0.845, 0.925, 0.707}, RGB, {0.978, 0.436, 0.942}), + _P(inb, HSL, {0.889, 0.190, 0.973}, RGB, {0.978, 0.968, 0.974}, false), + // XXX GOT {0.900, 0.185, 0.973} in inverse (RGB to HSL) + _P(inb, HSL, {0.182, 0.870, 0.172}, RGB, {0.295, 0.322, 0.022}), + _P(inb, HSL, {0.474, 0.305, 0.388}, RGB, {0.270, 0.507, 0.470}), + _P(inb, HSL, {0.070, 0.507, 0.513}, RGB, {0.760, 0.474, 0.266}), + _P(inb, HSL, {0.087, 0.713, 0.089}, RGB, {0.153, 0.092, 0.026}), + _P(inb, HSL, {0.537, 0.286, 0.749}, RGB, {0.677, 0.789, 0.821}), + _P(inb, HSL, {0.314, 0.688, 0.858}, RGB, {0.783, 0.956, 0.761}), + _P(inb, HSL, {0.385, 0.802, 0.797}, RGB, {0.634, 0.960, 0.736}), + _P(inb, HSL, {0.544, 0.265, 0.126}, RGB, {0.093, 0.142, 0.160}), + _P(inb, HSL, {0.793, 0.659, 0.998}, RGB, {0.999, 0.997, 0.999}, false), + // XXX GOT {0.833, 0.500, 0.998} in inverse (RGB to HSL) + _P(inb, HSL, {0.884, 0.984, 0.538}, RGB, {0.993, 0.084, 0.719}), + _P(inb, HSL, {0.730, 0.175, 0.475}, RGB, {0.455, 0.392, 0.558}), + + // No conversion + _P(inb, HSL, {1.000, 0.400, 0.200}, HSL, {1.000, 0.400, 0.200}) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesHsl, normalize, testing::Values( + // Note HSL is special in that it's hue component is radial so -0.2 == +0.8 + _P(inb, HSL, { 0.5, 0.5, 0.5, 0.5 }, HSL, { 0.5, 0.5, 0.5, 0.5 }), + _P(inb, HSL, { 1.2, 1.2, 1.2, 1.2 }, HSL, { 0.2, 1.0, 1.0, 1.0 }), + _P(inb, HSL, {-0.2, -0.2, -0.2, -0.2 }, HSL, { 0.8, 0.0, 0.0, 0.0 }), + _P(inb, HSL, { 0.0, 0.0, 0.0, 0.0 }, HSL, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, HSL, { 1.0, 1.0, 1.0, 1.0 }, HSL, { 1.0, 1.0, 1.0, 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesHsl, randomConversion) +{ + EXPECT_TRUE(RandomPassthrough(HSL, RGB, 1000)); +} + +TEST(ColorsSpacesHsl, components) +{ + auto c = Manager::get().find(HSL)->getComponents(); + ASSERT_EQ(c.size(), 3); + ASSERT_EQ(c[0].id, "h"); + ASSERT_EQ(c[1].id, "s"); + ASSERT_EQ(c[2].id, "l"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-hsluv-test.cpp b/testfiles/src/colors/spaces-hsluv-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0916eb25af724aaf5bf20e997766ce4818146499 --- /dev/null +++ b/testfiles/src/colors/spaces-hsluv-test.cpp @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for the HSLuv color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "spaces-testbase.h" + +namespace { + +using Space::Type::HSLUV; +using Space::Type::RGB; +using Space::Type::LUV; + +// clang-format off +// There is no CSS string for HSLuv colors +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(fromString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(badColorString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(toString); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesHSLuv, convertColorSpace, testing::Values( + // No conversion + _P(inb, HSLUV, {1.000, 0.400, 0.200}, HSLUV, {1.000, 0.400, 0.200}), + // Generated by hsluv.org a sample of low and high saturation hues + _P(inb, HSLUV, {0.946, 0.182, 0.500}, RGB, {0.565, 0.431, 0.490}), + _P(inb, HSLUV, {0.016, 0.723, 0.500}, RGB, {0.835, 0.247, 0.349}), + _P(inb, HSLUV, {0.760, 0.787, 0.500}, RGB, {0.517, 0.372, 0.878}), + _P(inb, HSLUV, {0.748, 0.768, 0.232}, RGB, {0.207, 0.137, 0.549}), + _P(inb, HSLUV, {0.018, 0.752, 0.232}, RGB, {0.415, 0.094, 0.149}), + _P(inb, HSLUV, {0.358, 0.454, 0.232}, RGB, {0.149, 0.239, 0.152}), + _P(inb, HSLUV, {0.344, 0.821, 0.848}, RGB, {0.458, 0.933, 0.352}), + _P(inb, HSLUV, {0.874, 0.526, 0.848}, RGB, {0.921, 0.792, 0.901}), + _P(inb, HSLUV, {0.205, 0.788, 0.848}, RGB, {0.921, 0.831, 0.384}) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesHSLuv, normalize, testing::Values( + _P(inb, HSLUV, { 0.5, 0.5, 0.5, 0.5 }, HSLUV, { 0.5, 0.5, 0.5, 0.5 }), + _P(inb, HSLUV, { 1.2, 1.2, 1.2, 1.2 }, HSLUV, { 0.2, 1.0, 1.0, 1.0 }), + _P(inb, HSLUV, {-0.2, -0.2, -0.2, -0.2 }, HSLUV, { 0.8, 0.0, 0.0, 0.0 }), + _P(inb, HSLUV, { 0.0, 0.0, 0.0, 0.0 }, HSLUV, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, HSLUV, { 1.0, 1.0, 1.0, 1.0 }, HSLUV, { 1.0, 1.0, 1.0, 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesHSLuv, randomConversion) +{ + EXPECT_TRUE(RandomPassthrough(HSLUV, RGB, 1000)); +} + +TEST(ColorsSpacesHSLuv, components) +{ + auto c = Manager::get().find(HSLUV)->getComponents(); + ASSERT_EQ(c.size(), 3); + ASSERT_EQ(c[0].id, "h"); + ASSERT_EQ(c[1].id, "s"); + ASSERT_EQ(c[2].id, "l"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-hsv-test.cpp b/testfiles/src/colors/spaces-hsv-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6baf0b4c770a0a06d02fb829082f6216b5149bcf --- /dev/null +++ b/testfiles/src/colors/spaces-hsv-test.cpp @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for the HSV color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "spaces-testbase.h" + +namespace { + +using Space::Type::HSV; +using Space::Type::RGB; + +// clang-format off +INSTANTIATE_TEST_SUITE_P(ColorsSpacesHsv, fromString, testing::Values( + // Taken from the w3c hwb example chart + _P(in, "hwb(0, 0.2, 0.2)", { 0.0, 0.75, 0.8 }, 0xcc3333ff), + _P(in, "hwb(30, 0.2, 0.2)", { 0.083, 0.75, 0.8 }, 0xcc7f33ff), + _P(in, "hwb(90, 0.2, 0.2)", { 0.25, 0.75, 0.8 }, 0x80cc33ff), + _P(in, "hwb(0, 0.4, 0.4)", { 0.0, 0.333, 0.6 }, 0x996666ff), + _P(in, "hwb(30deg, 0.4, 0.4)", { 0.083, 0.333, 0.6 }, 0x997f66ff), + _P(in, "hwb(0.25turn, 0.4, 0.4)", { 0.25, 0.333, 0.6 }, 0x809966ff), + _P(in, "hwb(0, 0.6, 60%)", { 0.0, 0, 0.5 }, 0x808080ff), + _P(in, "hwb(30, 60%, 0.6)", { 0.083, 0, 0.5 }, 0x808080ff), + _P(in, " hwb(90, 0.6, 0.6) ", { 0.25, 0, 0.5 }, 0x808080ff), + _P(in, "hwb(0, 0.8, 0.8)", { 0.0, 0, 0.5 }, 0x808080ff), + _P(in, "hwb(0, 1.0, 1.0 / 0.5)", { 0.0, 0, 0.5, 0.5 }, 0x80808080) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesHsv, badColorString, testing::Values( + "hwb", "hwb(", "hwb(360," +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesHsv, toString, testing::Values( + _P(out, HSV, { 0.333, 0.2, 0.8 }, "hwb(119, 0.64, 0.2)"), + _P(out, HSV, { 0.333, 0.8, 0.258 }, "hwb(119, 0.052, 0.742)"), + _P(out, HSV, { 1.0, 0.5, 0.004 }, "hwb(360, 0.002, 0.996)"), + _P(out, HSV, { 0, 1, 0.2, 0.8 }, "hwba(0, 0, 0.8, 0.8)"), + _P(out, HSV, { 0, 1, 0.2, 0.8 }, "hwb(0, 0, 0.8)", false) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesHsv, convertColorSpace, testing::Values( + // 20 random tests generated by python3 colorsys.rgb_to_hsv() + _P(inb, HSV, {0.132, 0.333, 0.633}, RGB, {0.633, 0.590, 0.422}), + _P(inb, HSV, {0.590, 0.814, 0.225}, RGB, {0.042, 0.126, 0.225}), + _P(inb, HSV, {0.351, 0.643, 0.888}, RGB, {0.317, 0.888, 0.379}), + _P(inb, HSV, {0.160, 0.718, 0.993}, RGB, {0.993, 0.966, 0.280}), + _P(inb, HSV, {0.565, 0.905, 0.411}, RGB, {0.039, 0.265, 0.411}), + _P(inb, HSV, {0.264, 0.981, 0.860}, RGB, {0.368, 0.860, 0.016}), + _P(inb, HSV, {0.883, 0.628, 0.817}, RGB, {0.817, 0.304, 0.664}), + _P(inb, HSV, {0.183, 0.676, 0.788}, RGB, {0.737, 0.788, 0.256}), + _P(inb, HSV, {0.685, 0.769, 0.830}, RGB, {0.263, 0.192, 0.830}), + _P(inb, HSV, {0.691, 0.876, 0.976}, RGB, {0.248, 0.121, 0.976}), + _P(inb, HSV, {0.843, 0.118, 0.803}, RGB, {0.803, 0.708, 0.797}), + _P(inb, HSV, {0.393, 0.732, 0.885}, RGB, {0.237, 0.885, 0.467}), + _P(inb, HSV, {0.923, 0.762, 0.654}, RGB, {0.654, 0.155, 0.385}), + _P(inb, HSV, {0.940, 0.294, 0.387}, RGB, {0.387, 0.273, 0.315}), + _P(inb, HSV, {0.707, 0.348, 0.989}, RGB, {0.728, 0.645, 0.989}), + _P(inb, HSV, {0.043, 0.541, 0.907}, RGB, {0.907, 0.542, 0.416}), + _P(inb, HSV, {0.322, 0.639, 0.043}, RGB, {0.017, 0.043, 0.016}, false), + // XXX GOT {0.327, 0.628, 0.043} in inverse (RGB to HSV) + _P(inb, HSV, {0.035, 0.991, 0.422}, RGB, {0.422, 0.092, 0.004}), + _P(inb, HSV, {0.871, 0.910, 0.735}, RGB, {0.735, 0.066, 0.583}), + _P(inb, HSV, {0.625, 0.931, 0.824}, RGB, {0.057, 0.250, 0.824}), + + // No conversion + _P(inb, HSV, {1.000, 0.400, 0.200}, HSV, {1.000, 0.400, 0.200}) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesHsv, normalize, testing::Values( + // Note HSV is special in that it's hue component is radial so -0.2 == +0.8 + _P(inb, HSV, { 0.5, 0.5, 0.5, 0.5 }, HSV, { 0.5, 0.5, 0.5, 0.5 }), + _P(inb, HSV, { 1.2, 1.2, 1.2, 1.2 }, HSV, { 0.2, 1.0, 1.0, 1.0 }), + _P(inb, HSV, {-0.2, -0.2, -0.2, -0.2 }, HSV, { 0.8, 0.0, 0.0, 0.0 }), + _P(inb, HSV, { 0.0, 0.0, 0.0, 0.0 }, HSV, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, HSV, { 1.0, 1.0, 1.0, 1.0 }, HSV, { 1.0, 1.0, 1.0, 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesHsv, randomConversion) +{ + EXPECT_TRUE(RandomPassthrough(HSV, RGB, 1000)); +} + +TEST(ColorsSpacesHsv, components) +{ + auto c = Manager::get().find(HSV)->getComponents(); + ASSERT_EQ(c.size(), 3); + ASSERT_EQ(c[0].id, "h"); + ASSERT_EQ(c[1].id, "s"); + ASSERT_EQ(c[2].id, "v"); + ASSERT_EQ(c[0].index, 0); + ASSERT_EQ(c[1].index, 1); + ASSERT_EQ(c[2].index, 2); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-lab-test.cpp b/testfiles/src/colors/spaces-lab-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3eb654cf15edb7a0e10fdb03197f5edb488ec8bd --- /dev/null +++ b/testfiles/src/colors/spaces-lab-test.cpp @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for the Lab color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/spaces/lab.h" +#include "spaces-testbase.h" + +namespace { + +using Space::Type::LAB; +using Space::Type::RGB; + +// clang-format off +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLAB, fromString, testing::Values( + _P(in, "lab(50% -20 0.5)", { 0.5, 0.42, 0.502 }, 0x4c8175ff), + _P(in, "lab(75 -125 125)", { 0.75, 0.0, 1.0 }, 0x4ce3d9ff), + _P(in, "lab(0 0 0)", { 0.0, 0.5, 0.5 }, 0x000000ff), + _P(in, "lab(20% 20 20 / 20%)", { 0.2, 0.58, 0.58, 0.2 }, 0x51231333) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLAB, badColorString, testing::Values( + "lab", "lab(", "lab(100" +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLAB, toString, testing::Values( + _P(out, LAB, { 0.3, 0.2, 0.8 }, "lab(30 -75 75)"), + _P(out, LAB, { 0.3, 0.8, 0.258 }, "lab(30 75 -60.5)"), + _P(out, LAB, { 1.0, 0.5, 0.004 }, "lab(100 0 -124)"), + _P(out, LAB, { 0, 1, 0.2, 0.8 }, "lab(0 125 -75 / 80%)", true), + _P(out, LAB, { 0, 1, 0.2, 0.8 }, "lab(0 125 -75)", false) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLAB, convertColorSpace, testing::Values( + // Example from w3c css-color-4 documentation + _P(inb, LAB, {0.462, 0.309, 0.694}, RGB, {0.097, 0.499, 0.006}), + // No conversion + _P(inb, LAB, {1.000, 0.400, 0.200}, LAB, {1.000, 0.400, 0.200}) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLAB, normalize, testing::Values( + _P(inb, LAB, { 0.5, 0.5, 0.5, 0.5 }, LAB, { 0.5, 0.5, 0.5, 0.5 }), + _P(inb, LAB, { 1.2, 1.2, 1.2, 1.2 }, LAB, { 1.0, 1.0, 1.0, 1.0 }), + _P(inb, LAB, {-0.2, -0.2, -0.2, -0.2 }, LAB, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, LAB, { 0.0, 0.0, 0.0, 0.0 }, LAB, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, LAB, { 1.0, 1.0, 1.0, 1.0 }, LAB, { 1.0, 1.0, 1.0, 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesLAB, randomConversion) +{ + // Isolate conversion functions + EXPECT_TRUE(RandomPassFunc(Space::Lab::fromXYZ, Space::Lab::toXYZ, 1000)); + + // Full stack conversion + EXPECT_TRUE(RandomPassthrough(LAB, RGB, 1000)); +} + +TEST(ColorsSpacesLAB, components) +{ + auto c = Manager::get().find(LAB)->getComponents(); + ASSERT_EQ(c.size(), 3); + EXPECT_EQ(c[0].id, "l"); + EXPECT_EQ(c[1].id, "a"); + EXPECT_EQ(c[2].id, "b"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-lch-test.cpp b/testfiles/src/colors/spaces-lch-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c429b7f4be42da34e4cc757f3608884196513b81 --- /dev/null +++ b/testfiles/src/colors/spaces-lch-test.cpp @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for the LCH color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/spaces/lch.h" +#include "spaces-testbase.h" + +namespace { + +using Space::Type::LCH; +using Space::Type::RGB; + +// clang-format off +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLCH, fromString, testing::Values( + _P(in, "lch(50% 20 180)", { 0.5, 0.133, 0.5 }, 0x557f79ff), + _P(in, "lch(100 150 360)", { 1.0, 1.0, 1.0 }, 0x95b4ecff), + _P(in, "lch(0 0 0)", { 0.0, 0.0, 0.0 }, 0x000000ff), + _P(in, "lch(20% 20 72 / 20%)", { 0.2, 0.133, 0.2, 0.2 }, 0x38300933) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLCH, badColorString, testing::Values( + "lch", "lch(", "lch(100" +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLCH, toString, testing::Values( + _P(out, LCH, { 0.0, 0.667, 0.945 }, "lch(0 100.05 340.2)"), + _P(out, LCH, { 0.3, 0.8, 0.258 }, "lch(30 120 92.88)"), + _P(out, LCH, { 1.0, 0.5, 0.004 }, "lch(100 75 1.44)"), + _P(out, LCH, { 0, 1, 0.2, 0.8 }, "lch(0 150 72 / 80%)", true), + _P(out, LCH, { 0, 1, 0.2, 0.8 }, "lch(0 150 72)", false) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLCH, convertColorSpace, testing::Values( + // Example from w3c css-color-4 documentation + // None of these conversions match, so a manual comparison was done between + // the old hsluv conversion and the new code, these match ok. So our lch code + // never matched the expected output in css land and this might be a future bug. + //_P(inb, LCH, { 0.0, 0.667, 0.945 }, RGB, { 0.0, 0.14, 0.5 }), + //_P(inb, LCH, { 1.0, 0.667, 0.945 }, RGB, { 0.0, 1.0, 1.0 }), + //_P(inb, LCH, { 0.5, 0.867, 0.055 }, RGB, { 1.0, 0.0, 0.230 }), + //_P(inb, LCH, { 1.0, 0.2, 0.055 }, RGB, { 1.0, 0.918, 0.926 }), + //_P(inb, LCH, { 0.5, 0.88, 0.361 }, RGB, { 0.0, 0.574, 0.0 }), + //_P(inb, LCH, { 0.5, 0.88, 0.5 }, RGB, { 0.0, 0.609, 0.453 }), + // No conversion + _P(inb, LCH, { 1.0, 0.400, 0.200 }, LCH, { 1.0, 0.400, 0.200 }) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLCH, normalize, testing::Values( + _P(inb, LCH, { 0.5, 0.5, 0.5, 0.5 }, LCH, { 0.5, 0.5, 0.5, 0.5 }), + _P(inb, LCH, { 1.2, 1.2, 1.2, 1.2 }, LCH, { 1.0, 1.0, 0.2, 1.0 }), + _P(inb, LCH, {-0.2, -0.2, -0.2, -0.2 }, LCH, { 0.0, 0.0, 0.8, 0.0 }), + _P(inb, LCH, { 0.0, 0.0, 0.0, 0.0 }, LCH, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, LCH, { 1.0, 1.0, 1.0, 1.0 }, LCH, { 1.0, 1.0, 1.0, 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesLCH, randomConversion) +{ + // Isolate conversion functions + EXPECT_TRUE(RandomPassFunc(Space::Lch::fromLuv, Space::Lch::toLuv, 1000)); + + // Full stack conversion + EXPECT_TRUE(RandomPassthrough(LCH, RGB, 1000)); +} + +TEST(ColorsSpacesLCH, components) +{ + auto c = Manager::get().find(LCH)->getComponents(); + ASSERT_EQ(c.size(), 3); + EXPECT_EQ(c[0].id, "l"); + EXPECT_EQ(c[1].id, "c"); + EXPECT_EQ(c[2].id, "h"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-linear-rgb-test.cpp b/testfiles/src/colors/spaces-linear-rgb-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3da08f1662ed3dc9028dfc028949d83ccaa217f6 --- /dev/null +++ b/testfiles/src/colors/spaces-linear-rgb-test.cpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for the Linear RGB color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/spaces/linear-rgb.h" +#include "spaces-testbase.h" + +namespace { + +using Space::Type::linearRGB; +using Space::Type::RGB; + +// clang-format off +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLinearRGB, fromString, testing::Values( + _P(in, "color(srgb-linear 0.1 1 0.5)", { 0.1, 1, 0.5 }, 0x59ffbcff) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLinearRGB, badColorString, testing::Values( + "color(srgb-linear", "color(srgb-linear", "color(srgb-linear 360" +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLinearRGB, toString, testing::Values( + _P(out, linearRGB, { 0.3, 0.2, 0.8 }, "color(srgb-linear 0.3 0.2 0.8)"), + _P(out, linearRGB, { 0.3, 0.8, 0.258 }, "color(srgb-linear 0.3 0.8 0.258)"), + _P(out, linearRGB, { 1.0, 0.5, 0.004 }, "color(srgb-linear 1 0.5 0.004)"), + _P(out, linearRGB, { 0, 1, 0.2, 0.8 }, "color(srgb-linear 0 1 0.2 / 80%)", true), + _P(out, linearRGB, { 0, 1, 0.2, 0.8 }, "color(srgb-linear 0 1 0.2)", false) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLinearRGB, convertColorSpace, testing::Values( + // Example from w3c css-color-4 documentation + _P(inb, linearRGB, {0.435, 0.017, 0.055}, RGB, {0.691, 0.139, 0.259}), + // No conversion + _P(inb, linearRGB, {1.000, 0.400, 0.200}, linearRGB, {1.000, 0.400, 0.200}) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLinearRGB, normalize, testing::Values( + _P(inb, linearRGB, { 0.5, 0.5, 0.5, 0.5 }, linearRGB, { 0.5, 0.5, 0.5, 0.5 }), + _P(inb, linearRGB, { 1.2, 1.2, 1.2, 1.2 }, linearRGB, { 1.0, 1.0, 1.0, 1.0 }), + _P(inb, linearRGB, {-0.2, -0.2, -0.2, -0.2 }, linearRGB, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, linearRGB, { 0.0, 0.0, 0.0, 0.0 }, linearRGB, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, linearRGB, { 1.0, 1.0, 1.0, 1.0 }, linearRGB, { 1.0, 1.0, 1.0, 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesLinearRGB, randomConversion) +{ + // Using the functions directly + EXPECT_TRUE(RandomPassFunc(Space::LinearRGB::fromRGB, Space::LinearRGB::toRGB, 1000)); + + // Using the color conversion stack + EXPECT_TRUE(RandomPassthrough(linearRGB, RGB, 1000)); +} + +TEST(ColorsSpacesLinearRGB, components) +{ + auto c = Manager::get().find(linearRGB)->getComponents(); + ASSERT_EQ(c.size(), 3); + EXPECT_EQ(c[0].id, "r"); + EXPECT_EQ(c[1].id, "g"); + EXPECT_EQ(c[2].id, "b"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-luv-test.cpp b/testfiles/src/colors/spaces-luv-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..60c942278ba4e130ae136737513eb63d867bd1a4 --- /dev/null +++ b/testfiles/src/colors/spaces-luv-test.cpp @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for the LUV color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/spaces/luv.h" +#include "spaces-testbase.h" + +namespace { + +using Space::Type::LUV; +using Space::Type::RGB; + +// clang-format off +// There is no CSS string for Luv colors +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(fromString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(badColorString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(toString); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLuv, convertColorSpace, testing::Values( + // No conversion + _P(inb, LUV, {1.000, 0.400, 0.200}, LUV, {1.000, 0.400, 0.200}) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesLuv, normalize, testing::Values( + _P(inb, LUV, { 0.5, 0.5, 0.5, 0.5 }, LUV, { 0.5, 0.5, 0.5, 0.5 }), + _P(inb, LUV, { 1.2, 1.2, 1.2, 1.2 }, LUV, { 1.0, 1.0, 1.0, 1.0 }), + _P(inb, LUV, {-0.2, -0.2, -0.2, -0.2 }, LUV, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, LUV, { 0.0, 0.0, 0.0, 0.0 }, LUV, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, LUV, { 1.0, 1.0, 1.0, 1.0 }, LUV, { 1.0, 1.0, 1.0, 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesLuv, manualConversion) +{ + // This output is unscaled, so Luv values are between L:0..100 and etc. + EXPECT_TRUE(ManualPassFunc(Space::Luv::fromXYZ, {0.5, 0.2, 0.4}, Space::Luv::toXYZ, {51.837, 153.445, -57.51})); +} + +TEST(ColorsSpacesLuv, randomConversion) +{ + // Isolate conversion functions + EXPECT_TRUE(RandomPassFunc(Space::Luv::fromXYZ, Space::Luv::toXYZ, 1000)); + + // Full stack conversion + EXPECT_TRUE(RandomPassthrough(LUV, RGB, 1000)); +} + +TEST(ColorsSpacesLuv, components) +{ + auto c = Manager::get().find(LUV)->getComponents(); + ASSERT_EQ(c.size(), 3); + ASSERT_EQ(c[0].id, "l"); + ASSERT_EQ(c[1].id, "u"); + ASSERT_EQ(c[2].id, "v"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-named-test.cpp b/testfiles/src/colors/spaces-named-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f346c372e82eda3ac7c27741257392a949a1f0ff --- /dev/null +++ b/testfiles/src/colors/spaces-named-test.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for color objects. + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include + +#include "colors/color.h" + +using namespace Inkscape::Colors; + +namespace { + +TEST(ColorsSpacesRgb, fromString) +{ + ASSERT_EQ(Color::parse(" red ")->toRGBA(), 0xff0000ff); + ASSERT_EQ(Color::parse("BLUE ")->toRGBA(0.5), 0x0000ff80); +} + +TEST(ColorsSpaceRgb, fromStringFailures) +{ + ASSERT_FALSE(Color::parse("réd")); +} + +TEST(ColorsSpaceRgb, toString) +{ + ASSERT_EQ(Color(0xff000000, false).converted(Space::Type::CSSNAME)->toString(), "red"); + ASSERT_EQ(Color::parse("mediumpurple")->toString(), "mediumpurple"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-okhsl-test.cpp b/testfiles/src/colors/spaces-okhsl-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4224f103ae910b20340c3ef779366eabdf66d753 --- /dev/null +++ b/testfiles/src/colors/spaces-okhsl-test.cpp @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for the OkHsl color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/spaces/okhsl.h" +#include "spaces-testbase.h" + +namespace { + +using Space::Type::RGB; +using Space::Type::OKHSL; + +// clang-format off +// There is no CSS for OkHsl +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(fromString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(badColorString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(toString); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesOkHSL, convertColorSpace, testing::Values( + // No conversion + _P(inb, OKHSL, { 1.0, 0.400, 0.200 }, OKHSL, { 1.0, 0.400, 0.200 }) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesOkHSL, normalize, testing::Values( + _P(inb, OKHSL, { 0.5, 0.5, 0.5, 0.5 }, OKHSL, { 0.5, 0.5, 0.5, 0.5 }), + _P(inb, OKHSL, { 1.2, 1.2, 1.2, 1.2 }, OKHSL, { 0.2, 1.0, 1.0, 1.0 }), + _P(inb, OKHSL, {-0.2, -0.2, -0.2, -0.2 }, OKHSL, { 0.8, 0.0, 0.0, 0.0 }), + _P(inb, OKHSL, { 0.0, 0.0, 0.0, 0.0 }, OKHSL, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, OKHSL, { 1.0, 1.0, 1.0, 1.0 }, OKHSL, { 1.0, 1.0, 1.0, 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesOkHSL, randomConversion) +{ + // Isolate conversion functions + // EXPECT_TRUE(RandomPassFunc(Space::OkHsl::toOkLab, Space::OkHsl::toOkLab, 1000)); + + // Full stack conversion + EXPECT_TRUE(RandomPassthrough(OKHSL, RGB, 1000, true)); +} + +TEST(ColorsSpacesOkHSL, components) +{ + auto c = Manager::get().find(OKHSL)->getComponents(); + ASSERT_EQ(c.size(), 3); + EXPECT_EQ(c[0].id, "h"); + EXPECT_EQ(c[1].id, "s"); + EXPECT_EQ(c[2].id, "l"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-oklab-test.cpp b/testfiles/src/colors/spaces-oklab-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cfcc8373bdd6fedda7c5cbbf4840453432c3de37 --- /dev/null +++ b/testfiles/src/colors/spaces-oklab-test.cpp @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for the LAB color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/spaces/oklab.h" +#include "spaces-testbase.h" + +namespace { + +using Space::Type::OKLAB; + +// clang-format off +INSTANTIATE_TEST_SUITE_P(ColorsSpacesOkLAB, fromString, testing::Values( + _P(in, "oklab(50% -0.4 -0.4)", { 0.5, 0.0, 0.0 }, 0x0045ffff), + _P(in, "oklab(1 0.4 0.4)", { 1.0, 1.0, 1.0 }, 0xff0000ff), + _P(in, "oklab(0 0 0)", { 0.0, 0.5, 0.5 }, 0x000000ff), + _P(in, "oklab(20% 0.2 0.2 / 20%)", { 0.2, 0.75, 0.75, 0.2 }, 0x62000033) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesOkLAB, badColorString, testing::Values( + "oklab", "oklab(", "oklab(100" +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesOkLAB, toString, testing::Values( + _P(out, OKLAB, { 0.0, 0.667, 0.945 }, "oklab(0 0.134 0.356)"), + _P(out, OKLAB, { 0.3, 0.8, 0.258 }, "oklab(0.3 0.24 -0.194)"), + _P(out, OKLAB, { 1.0, 0.5, 0.004 }, "oklab(1 0 -0.397)"), + _P(out, OKLAB, { 0, 1, 0.2, 0.8 }, "oklab(0 0.4 -0.24 / 80%)", true), + _P(out, OKLAB, { 0, 1, 0.2, 0.8 }, "oklab(0 0.4 -0.24)", false) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesOkLAB, convertColorSpace, testing::Values( + //_P(inb, OKLAB, { 0.6, 0.125, 0.0 }, RGB, { 0.0, 0.196, 1.0 }), + // No conversion + _P(inb, OKLAB, { 1.0, 0.400, 0.200 }, OKLAB, { 1.0, 0.400, 0.200 }) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesOkLAB, normalize, testing::Values( + _P(inb, OKLAB, { 0.5, 0.5, 0.5, 0.5 }, OKLAB, { 0.5, 0.5, 0.5, 0.5 }), + _P(inb, OKLAB, { 1.2, 1.2, 1.2, 1.2 }, OKLAB, { 1.0, 1.0, 1.0, 1.0 }), + _P(inb, OKLAB, {-0.2, -0.2, -0.2, -0.2 }, OKLAB, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, OKLAB, { 0.0, 0.0, 0.0, 0.0 }, OKLAB, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, OKLAB, { 1.0, 1.0, 1.0, 1.0 }, OKLAB, { 1.0, 1.0, 1.0, 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesOkLAB, randomConversion) +{ + // Isolate conversion functions + EXPECT_TRUE(RandomPassFunc(Space::OkLab::fromLinearRGB, Space::OkLab::toLinearRGB, 1000)); + + // Full stack conversion + // EXPECT_TRUE(RandomPassthrough(OKLAB, RGB, 1)); +} + +TEST(ColorsSpacesOkLAB, components) +{ + auto c = Manager::get().find(OKLAB)->getComponents(); + ASSERT_EQ(c.size(), 3); + EXPECT_EQ(c[0].id, "h"); + EXPECT_EQ(c[1].id, "s"); + EXPECT_EQ(c[2].id, "l"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-oklch-test.cpp b/testfiles/src/colors/spaces-oklch-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f446f592f007eaa851a2c6ce3454fced70b1d52a --- /dev/null +++ b/testfiles/src/colors/spaces-oklch-test.cpp @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for the LCH color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/spaces/oklch.h" +#include "spaces-testbase.h" + +namespace { + +using Space::Type::OKLCH; +using Space::Type::RGB; + +// clang-format off +// Run out of time before the rest of the features could be done +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(fromString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(badColorString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(toString); + +/*INSTANTIATE_TEST_SUITE_P(ColorsSpacesOkLCH, fromString, testing::Values( + _P(in, "oklch(50% 0.1 180)", { 0.5, 0.133, 0.5 }, 0x557f79ff), + _P(in, "oklch(100 0.4 360)", { 1.0, 1.0, 1.0 }, 0x95b4ecff), + _P(in, "oklch(0 0 0)", { 0.0, 0.0, 0.0 }, 0x000000ff), + _P(in, "oklch(20% 0.2 72 / 20%)", { 0.2, 0.133, 0.2, 0.2 }, 0x38300933) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesOkLCH, badColorString, testing::Values( + "oklch", "oklch(", "oklch(100" +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesOkLCH, toString, testing::Values( + _P(out, OKLCH, { }, "", true), + _P(out, OKLCH, { }, "", false), + _P(out, OKLCH, { 0.0, 0.667, 0.945 }, "oklch(0 100.05 340.2)"), + _P(out, OKLCH, { 0.3, 0.8, 0.258 }, "oklch(30 120 92.88)"), + _P(out, OKLCH, { 1.0, 0.5, 0.004 }, "oklch(100 75 1.44)"), + _P(out, OKLCH, { 0, 1, 0.2, 0.8 }, "oklch(0 150 72 / 80%)", true), + _P(out, OKLCH, { 0, 1, 0.2, 0.8 }, "oklch(0 150 72)", false) +));*/ + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesOkLCH, convertColorSpace, testing::Values( + // No conversion + _P(inb, OKLCH, { 1.0, 0.400, 0.200 }, OKLCH, { 1.0, 0.400, 0.200 }) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesOkLCH, normalize, testing::Values( + _P(inb, OKLCH, { 0.5, 0.5, 0.5, 0.5 }, OKLCH, { 0.5, 0.5, 0.5, 0.5 }), + _P(inb, OKLCH, { 1.2, 1.2, 1.2, 1.2 }, OKLCH, { 1.0, 1.0, 0.2, 1.0 }), + _P(inb, OKLCH, {-0.2, -0.2, -0.2, -0.2 }, OKLCH, { 0.0, 0.0, 0.8, 0.0 }), + _P(inb, OKLCH, { 0.0, 0.0, 0.0, 0.0 }, OKLCH, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, OKLCH, { 1.0, 1.0, 1.0, 1.0 }, OKLCH, { 1.0, 1.0, 1.0, 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesOkLCH, randomConversion) +{ + // Isolate conversion functions + // EXPECT_TRUE(RandomPassFunc(Space::OkLch::toOkLab, Space::OkLch::toOkLab, 1000)); + + // Full stack conversion + // EXPECT_TRUE(RandomPassthrough(OKLCH, RGB, 1000)); +} + +TEST(ColorsSpacesOkLCH, components) +{ + auto c = Manager::get().find(OKLCH)->getComponents(); + ASSERT_EQ(c.size(), 3); + EXPECT_EQ(c[0].id, "l"); + EXPECT_EQ(c[1].id, "c"); + EXPECT_EQ(c[2].id, "h"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-rgb-test.cpp b/testfiles/src/colors/spaces-rgb-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..65bcd9ad7b6ea8bb4b10b9f9137fdc5ab8e23af6 --- /dev/null +++ b/testfiles/src/colors/spaces-rgb-test.cpp @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for the RGB color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/spaces/components.h" +#include "spaces-testbase.h" + +namespace { + +using Space::Type::RGB; +using Space::Type::HSL; + +// clang-format off +INSTANTIATE_TEST_SUITE_P(ColorsSpacesRgb, fromString, testing::Values( + _P(in, "#f0f", { 1, 0, 1 }, 0xff00ffff), + _P(in, "#FFC", { 1, 1, 0.8 }, 0xffffccff), + _P(in, "#0F3c", { 0, 1, 0.2, 0.8 }, 0x00ff33cc), + _P(in, "#5533Cc", { 0.333, 0.2, 0.8 }, 0x5533ccff), + _P(in, "#5533Cc66", { 0.333, 0.2, 0.8, 0.4 }, 0x5533cc66), + _P(in, " #55Cc42 ", { 0.333, 0.8, 0.258 }, 0x55cc42ff), + _P(in, "rgb(100%, 50%, 1)", { 1.0, 0.5, 0.004 }, 0xff8001ff), + _P(in, "rgb(100% 50% 51)", { 1.0, 0.5, 0.2 }, 0xff8033ff), + _P(in, "rgb(100% ,50% , 51 )", { 1.0, 0.5, 0.2 }, 0xff8033ff), + _P(in, "rgb(100% ,50% , 102 / 50%)", { 1.0, 0.5, 0.4, 0.5 }, 0xff806680), + _P(in, " rgb(128, 128, 128)", { 0.501, 0.501, 0.501 }, 0x808080ff), + _P(in, "rgba(255, 255, 128, 0.5) ", { 1.0, 1.0, 0.501, 0.5 }, 0xffff8080), + _P(in, "color(srgb 1 0.5 0.4 / 50%)", { 1.0, 0.5, 0.4, 0.5 }, 0xff806680) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesRgb, badColorString, testing::Values( + "", "#", "#1", "#12", + "rgb", "rgb(", "rgb(255,", "rgb(1 2 3", "rgb(1 2 3 4", + "rgba(1 2 3)", + "color(srgb 3" +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesRgb, toString, testing::Values( + _P(out, RGB, { 0.333, 0.2, 0.8 }, "#5533cc"), + _P(out, RGB, { 0.333, 0.8, 0.258 }, "#55cc42"), + _P(out, RGB, { 1.0, 0.5, 0.004 }, "#ff8001"), + _P(out, RGB, { 0, 1, 0.2, 0.8 }, "#00ff33cc") +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesRgb, convertColorSpace, testing::Values( + _P(inb, RGB, { 1.0, 0.0, 0.0 }, RGB, { 1.0, 0.0, 0.0 }, false), + _P(inb, RGB, { 1.0, 0.0, 0.0, 0.5 }, RGB, { 1.0, 0.0, 0.0, 0.5 }, false), + // All other tests are in their respective color space test, for example spoaces-hsl-test.cpp + _P(inb, RGB, { 1.0, 0.0, 0.0 }, HSL, { 0.0, 1.0, 0.5 }), + _P(inb, RGB, { 1.0, 0.0, 0.0, 0.5 }, HSL, { 0.0, 1.0, 0.5, 0.5 }) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesRgb, normalize, testing::Values( + _P(inb, RGB, { 0.5, 0.5, 0.5, 0.5 }, RGB, { 0.5, 0.5, 0.5, 0.5 }), + _P(inb, RGB, { 1.2, 1.2, 1.2, 1.2 }, RGB, { 1.0, 1.0, 1.0, 1.0 }), + _P(inb, RGB, {-0.2, -0.2, -0.2, -0.2 }, RGB, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, RGB, { 0.0, 0.0, 0.0, 0.0 }, RGB, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, RGB, { 1.0, 1.0, 1.0, 1.0 }, RGB, { 1.0, 1.0, 1.0, 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesRgb, randomConversion) +{ + EXPECT_TRUE(RandomPassthrough(RGB, RGB, 1)); // Not really needed +} + +TEST(ColorsSpacesRgb, components) +{ + auto c = Manager::get().find(RGB)->getComponents(); + ASSERT_EQ(c.size(), 3); + ASSERT_EQ(c[0].id, "r"); + ASSERT_EQ(c[1].id, "g"); + ASSERT_EQ(c[2].id, "b"); + ASSERT_EQ(c[0].index, 0); + ASSERT_EQ(c[1].index, 1); + ASSERT_EQ(c[2].index, 2); + + auto c2 = Manager::get().find(RGB)->getComponents(true); + ASSERT_EQ(c2.size(), 4); + ASSERT_EQ(c2[3].id, "a"); + ASSERT_EQ(c2[3].index, 3); +} + +/*TEST(ColorsSpacesRgb, colorVarFallback) +{ + auto &cm = Manager::get(); + ASSERT_EQ(Color("var(--foo, white)").toString(), "white"); + ASSERT_EQ(Color("var(--foo, black)").toString(), "white"); + ASSERT_EQ(Color("var(--foo, #00ff00)").toString(), "#00ff00"); +}*/ + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-testbase.h b/testfiles/src/colors/spaces-testbase.h new file mode 100644 index 0000000000000000000000000000000000000000..b97ef7de9f40eec69979f95d6190ad70653bcca3 --- /dev/null +++ b/testfiles/src/colors/spaces-testbase.h @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Shared test header for testing color spaces + * + * Copyright (C) 2024 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +#include "../test-utils.h" + +#include "colors/color.h" +#include "colors/manager.h" +#include "colors/spaces/base.h" +#include "colors/spaces/components.h" + +using namespace Inkscape::Colors; + +namespace { + +/** + * Test that a color space actually exists, to catch test writing mistakes instead of crashing. + */ +void testSpaceName(Space::Type const &type) +{ + auto &manager = Manager::get(); + ASSERT_TRUE(manager.find(type)) << "Unknown Color Space"; +} + +// Allow numbers to be printed as hex in failures +// see https://github.com/google/googletest/issues/222 +class Hex +{ +public: + explicit Hex(unsigned int n) + : _number(n) + {} + operator unsigned int() { return _number; } + bool operator==(Hex const &other) const { return other._number == _number; } + bool operator!=(Hex const &other) const { return other._number != _number; } + bool operator==(unsigned int const &other) const { return other == _number; } + bool operator!=(unsigned int const &other) const { return other != _number; } + unsigned int _number; +}; +void PrintTo(const Hex &obj, std::ostream *oo) +{ + oo->imbue(std::locale("C")); + *oo << "0x" << std::setfill('0') << std::setw(8) << std::hex << obj._number << "'"; +} + +/* ===== In test ===== */ + +struct in : traced_data +{ + const std::string val; + const std::vector out; + const unsigned int rgba; +}; +void PrintTo(const in &obj, std::ostream *oo) +{ + *oo << "'" << obj.val << "'"; +} + +class fromString : public testing::TestWithParam +{ +}; + +TEST_P(fromString, hasValues) +{ + in test = GetParam(); + auto color = Color::parse(test.val); + ASSERT_TRUE(color); + auto scope = test.enable_scope(); + EXPECT_TRUE(VectorIsNear(color->getValues(), test.out, 0.001)); +} + +TEST_P(fromString, hasRGBA) +{ + in test = GetParam(); + auto scope = test.enable_scope(); + auto color = Color::parse(test.val); + ASSERT_TRUE(color); + EXPECT_EQ(Hex(color->toRGBA(true)), Hex(test.rgba)); +} + +class badColorString : public testing::TestWithParam +{ +}; + +TEST_P(badColorString, returnsNone) +{ + EXPECT_FALSE(Color::parse(GetParam())); +} + +/* ===== Out test ===== */ + +struct out : traced_data +{ + const Space::Type space; + const std::vector val; + const std::string out; + const bool opacity = true; +}; +void PrintTo(const out &obj, std::ostream *oo) +{ + *oo << "'" << obj.out << "'"; +} +class toString : public testing::TestWithParam +{ +}; +TEST_P(toString, hasValue) +{ + out test = GetParam(); + auto scope = test.enable_scope(); + testSpaceName(test.space); + EXPECT_EQ(Color(test.space, test.val).toString(test.opacity), test.out); +} + +/* ====== Convert test ===== */ + +struct norm +{ + double count = 0.0; + std::vector min; + std::vector max; +}; + +struct inb : traced_data +{ + const Space::Type space_in; + const std::vector in; + const Space::Type space_out; + const std::vector out; + bool both_directions = true; + + Color do_conversion(bool inplace, norm *notnorm = nullptr) const + { + auto result = Color(space_in, in); + if (inplace) { + result.convert(space_out); + count_notnorm(result, notnorm); + return result; + } + if (auto color = result.converted(space_out)) { + count_notnorm(*color, notnorm); + return *color; + } + throw ColorError("Bad conversion in test"); + } + + void count_notnorm(Color const &c, norm *out = nullptr) const + { + if (!out) + return; + + for (unsigned int i = 0; i < c.size(); i++) { + double v = c[i]; + + // Count the out of bounds results from conversions + if (v < 0.0) + out->count += std::abs(v); + else if (v > 1.0) + out->count += (v - 1.0); + + // Record the min and max ranges + if (out->min.size() <= i) + out->min.emplace_back(0.0); + if (out->max.size() <= i) + out->max.emplace_back(0.0); + + out->min[i] = std::min(out->min[i], v); + out->max[i] = std::max(out->max[i], v); + } + } + + ::testing::AssertionResult forward_test(bool inplace, norm *notnorm = nullptr) const + { + auto result = do_conversion(inplace, notnorm); + return VectorIsNear(result.getValues(), out, 0.005); + } + + ::testing::AssertionResult backward_test(bool inplace, norm *notnorm = nullptr) const + { + return inb{_file, _line, space_out, out, space_in, in}.forward_test(inplace, notnorm); + } + + // Send the results back to be tested for a pass-through test + ::testing::AssertionResult through_test(bool inplace, norm *notnorm = nullptr) const + { + auto result = do_conversion(inplace, notnorm); + return inb{_file, _line, space_out, result.getValues(), space_in, in}.forward_test(inplace, notnorm); + } +}; +void PrintTo(const inb &obj, std::ostream *oo) +{ + *oo << (int)obj.space_in << print_values(obj.in); + *oo << "<->"; + *oo << (int)obj.space_out << print_values(obj.out); +} + +class convertColorSpace : public testing::TestWithParam +{ +}; +TEST_P(convertColorSpace, copy) +{ + auto test = GetParam(); + auto scope = test.enable_scope(); + testSpaceName(test.space_in); + testSpaceName(test.space_out); + EXPECT_TRUE(test.forward_test(false)) << " " << (int)test.space_in << " copy to " << (int)test.space_out; + if (test.both_directions) { + EXPECT_TRUE(test.backward_test(false)) << " " << (int)test.space_in << " copy from " << (int)test.space_out; + } +} +TEST_P(convertColorSpace, inPlace) +{ + auto test = GetParam(); + auto scope = test.enable_scope(); + testSpaceName(test.space_in); + testSpaceName(test.space_out); + EXPECT_TRUE(test.forward_test(true)) << " in place " << (int)test.space_in << " to " << (int)test.space_out; + if (test.both_directions) { + EXPECT_TRUE(test.backward_test(true)) << " in place " << (int)test.space_in << " from " << (int)test.space_out; + } +} + +/** + * Manually test a conversion function, both ways. + * + * @arg from_func - A conversion function in one direction + * @arg from_values - The values to pass into the from_func and to compare to the output from to_func + * @arg to_func - The reverse function + * @arg to_values - The values to pass to to_func and to compare to the output from from_func + */ +::testing::AssertionResult ManualPassFunc(std::function &)> from_func, + std::vector from_values, + std::function &)> to_func, + std::vector to_values, double epsilon = 0.005) +{ + (void)&ManualPassFunc; // Avoid compile warning + auto copy = from_values; + from_func(copy); + auto ret = VectorIsNear(copy, to_values, epsilon); + + if (ret) { + to_func(to_values); + ret = VectorIsNear(to_values, from_values, epsilon); + } + return ret; +} + +/** + * Create many random tests of the conversion functions, outputs and fed to inputs + * to guarentee stability in both directions. + * + * @arg from_func - A conversion function in one direction + * @arg to_func - The reverse function + * @arg count - The number of tests to create + */ +::testing::AssertionResult RandomPassFunc(std::function &)> from_func, + std::function &)> to_func, unsigned count = 1000) +{ + (void)&RandomPassFunc; // Avoid compile warning + std::srand(13375336); // We always seed for tests' repeatability + + std::vector range = {1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0}; + + for (unsigned i = 0; i < count; i++) { + auto values = random_values(3); + auto expected = values; + + from_func(values); + for (int x = 0; x < 3; x++) { + range[x + 0] = std::min(range[x + 0], values[x]); + range[x + 3] = std::max(range[x + 3], values[x]); + } + + to_func(values); + for (int x = 6; x < 9; x++) { + range[x + 0] = std::min(range[x + 0], values[x - 6]); + range[x + 3] = std::max(range[x + 3], values[x - 6]); + } + + auto ret = VectorIsNear(values, expected, 0.005); + if (!ret) { + return ret; + } + } + /*auto ret = VectorIsNear(range, {0,0,0,1,1,1,0,0,0,1,1,1}, 0.01); + if (!ret) { + return ret << " values ranges in random functions calls."; + }*/ + return ::testing::AssertionSuccess(); +} + +/** + * Create many random tests of the conversion stack, outputs and fed to inputs + * to guarentee stability in both directions. + * + * @arg from - A color space to convert in one direction + * @arg to_func - A color space to convert in the oposite direction + * @arg count - The number of tests to create + */ +::testing::AssertionResult RandomPassthrough( + Space::Type const &from, + Space::Type const &to, + unsigned count = 1000, + bool normal_check = false +) +{ + (void)&RandomPassthrough; // Avoid compile warning + std::srand(13375336); // We always seed for tests' repeatability + norm notnorm; // Count the out of bounds + + testSpaceName(from); + testSpaceName(to); + + auto space = Manager::get().find(from); + if (!space) + return ::testing::AssertionFailure() << "can't find space " << (int)from; + + auto ccount = space->getComponentCount(); + for (unsigned i = 0; i < count; i++) { + auto ret = inb{"", 0, from, random_values(ccount), to, {}}.through_test(true, normal_check ? ¬norm : nullptr); + if (!ret) { + return ret << " | " << (int)from << "->" << (int)to; + } + } + if (normal_check && notnorm.count > 1.0) { + return ::testing::AssertionFailure() << " values went above or below the normal expected range of 0.0 and 1.0 by " << notnorm.count << " in " << count << " conversions\n" + << " - Minimal ranges: " << print_values(notnorm.min) << "\n" + << " + Maximal ranges: " << print_values(notnorm.max) << "\n"; + } + return ::testing::AssertionSuccess(); +} + +/* ===== Normalization tests ===== */ + +class normalize : public testing::TestWithParam +{ +}; + +/** + * Test that the normalization functions as expected for this color space. + */ +TEST_P(normalize, values) +{ + inb test = GetParam(); + testSpaceName(test.space_in); + auto color = Color(test.space_in, test.in); + color.normalize(); + auto scope = test.enable_scope(); + EXPECT_TRUE(VectorIsNear(color.getValues(), test.out, 0.001)); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/spaces-xyz-test.cpp b/testfiles/src/colors/spaces-xyz-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b26193101f3082fbe23d4ca9288ad0b93661c275 --- /dev/null +++ b/testfiles/src/colors/spaces-xyz-test.cpp @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for the Linear RGB color space + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/spaces/xyz.h" +#include "spaces-testbase.h" + +namespace { + +using Space::Type::XYZ; +using Space::Type::RGB; + +// clang-format off +INSTANTIATE_TEST_SUITE_P(ColorsSpacesXYZ, fromString, testing::Values( + _P(in, "color(xyz 0.1 1 0.5)", { 0.1, 1, 0.5 }, 0x2e4a9bff) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesXYZ, badColorString, testing::Values( + "color(xyz", "color(xyz", "color(xyz 360" +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesXYZ, toString, testing::Values( + _P(out, XYZ, { 0.3, 0.2, 0.8 }, "color(xyz 0.3 0.2 0.8)"), + _P(out, XYZ, { 0.3, 0.8, 0.258 }, "color(xyz 0.3 0.8 0.258)"), + _P(out, XYZ, { 1.0, 0.5, 0.004 }, "color(xyz 1 0.5 0.004)"), + _P(out, XYZ, { 0, 1, 0.2, 0.8 }, "color(xyz 0 1 0.2 / 80%)", true), + _P(out, XYZ, { 0, 1, 0.2, 0.8 }, "color(xyz 0 1 0.2)", false) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesXYZ, convertColorSpace, testing::Values( + // Example from w3c css-color-4 documentation + _P(inb, XYZ, {0.217, 0.146, 0.594}, RGB, {0.463, 0.329, 0.804}), + //_P(inb, XYZ, {0.217, 0.146, 0.594}, "Lab", {0.444, 0.644, 0.264}), + // No conversion + _P(inb, XYZ, {1.000, 0.400, 0.200}, XYZ, {1.000, 0.400, 0.200}) +)); + +INSTANTIATE_TEST_SUITE_P(ColorsSpacesXYZ, normalize, testing::Values( + _P(inb, XYZ, { 0.5, 0.5, 0.5, 0.5 }, XYZ, { 0.5, 0.5, 0.5, 0.5 }), + _P(inb, XYZ, { 1.2, 1.2, 1.2, 1.2 }, XYZ, { 1.0, 1.0, 1.0, 1.0 }), + _P(inb, XYZ, {-0.2, -0.2, -0.2, -0.2 }, XYZ, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, XYZ, { 0.0, 0.0, 0.0, 0.0 }, XYZ, { 0.0, 0.0, 0.0, 0.0 }), + _P(inb, XYZ, { 1.0, 1.0, 1.0, 1.0 }, XYZ, { 1.0, 1.0, 1.0, 1.0 }) +)); +// clang-format on + +TEST(ColorsSpacesXYZ, randomConversion) +{ + // Isolate conversion functions + EXPECT_TRUE(RandomPassFunc(Space::XYZ::fromLinearRGB, Space::XYZ::toLinearRGB, 1000)); + + // Full stack conversion + EXPECT_TRUE(RandomPassthrough(XYZ, RGB, 1000)); +} + +TEST(ColorsSpacesXYZ, components) +{ + auto c = Manager::get().find(XYZ)->getComponents(); + ASSERT_EQ(c.size(), 3); + EXPECT_EQ(c[0].id, "x"); + EXPECT_EQ(c[1].id, "y"); + EXPECT_EQ(c[2].id, "z"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/utils-test.cpp b/testfiles/src/colors/utils-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..db7e1107543a18c71e76ff4e791dce59258dd53b --- /dev/null +++ b/testfiles/src/colors/utils-test.cpp @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for color objects. + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/utils.h" + +#include + +#include "colors/color.h" +#include "colors/manager.h" + +using namespace Inkscape; +using namespace Inkscape::Colors; + +namespace { + +TEST(ColorUtils, test_hex_to_rgba) +{ + EXPECT_EQ(hex_to_rgba("#ff00ffff"), 0xff00ffff); +} + +TEST(ColorUtils, test_rgba_to_hex) +{ + EXPECT_EQ(rgba_to_hex(0xff00ff00, false), "#ff00ff"); + EXPECT_EQ(rgba_to_hex(0xff00ffff, true), "#ff00ffff"); +} + +TEST(ColorUtils, test_color_to_id) +{ + EXPECT_EQ(color_to_id({}), "none"); + EXPECT_EQ(color_to_id(Color::parse("not-a-color")), "none"); + EXPECT_EQ(color_to_id(Color::parse("red")), "css-red"); + EXPECT_EQ(color_to_id(Color::parse("#0000ff")), "rgb-0000ff"); + + auto color = Color::parse("hsl(0.5, 0.5, 1.0)"); + EXPECT_EQ(color_to_id(color), "hsl-007fff"); + + color->setName("Huey // Dewy_! Lewy"); + EXPECT_EQ(color_to_id(color), "huey-dewy-lewy"); + + color->convert(Space::Type::RGB); + EXPECT_EQ(color_to_id(color), "rgb-ffffff"); +} + +TEST(ColorUtils, test_desc_to_id) +{ + EXPECT_EQ(desc_to_id("thing"), "thing"); + EXPECT_EQ(desc_to_id("Thing Two"), "thing-two"); + EXPECT_EQ(desc_to_id(" Thing Threé "), "thing-threé"); + EXPECT_EQ(desc_to_id(" Wobble blink CAPLINK!"), "wobble-blink-caplink"); +} + +TEST(ColorUtils, test_make_contrasted_color) +{ + EXPECT_EQ(make_contrasted_color(Color(0x000000ff), 0.2).toRGBA(), 0x040404ff); + EXPECT_EQ(make_contrasted_color(Color(0x000000ff), 0.4).toRGBA(), 0x080808ff); + EXPECT_EQ(make_contrasted_color(Color(0x000000ff), 0.6).toRGBA(), 0x0c0c0cff); + EXPECT_EQ(make_contrasted_color(Color(0xffffffff), 0.2).toRGBA(), 0xfbfbfbff); + EXPECT_EQ(make_contrasted_color(Color(0xffffffff), 0.4).toRGBA(), 0xf7f7f7ff); + EXPECT_EQ(make_contrasted_color(Color(0xffffffff), 0.6).toRGBA(), 0xf3f3f3ff); + EXPECT_EQ(make_contrasted_color(Color(0xa1a1a1ff), 0.2).toRGBA(), 0x9d9d9dff); + EXPECT_EQ(make_contrasted_color(Color(0x1a1a1aff), 0.4).toRGBA(), 0x121212ff); + EXPECT_EQ(make_contrasted_color(Color(0x808080ff), 0.6).toRGBA(), 0x747474ff); +} + +TEST(ColorUtils, test_get_perceptual_lightness) +{ + EXPECT_NEAR(get_perceptual_lightness(*Color::parse("red")), 0.780, 0.001); + EXPECT_NEAR(get_perceptual_lightness(*Color::parse("black")), 0.0, 0.001); + EXPECT_NEAR(get_perceptual_lightness(*Color::parse("white")), 1.0, 0.001); + EXPECT_NEAR(get_perceptual_lightness(*Color::parse("device-cmyk(0.2 0.1 1.0 0.0)")), 0.945, 0.001); +} + +TEST(ColorUtils, test_contrasting_color) +{ + auto a = get_contrasting_color(0.1); + EXPECT_EQ(a.first, 1.0); + EXPECT_NEAR(a.second, 0.688, 0.001); + + auto b = get_contrasting_color(0.9); + EXPECT_EQ(b.first, 0.0); + EXPECT_NEAR(b.second, 0.366, 0.001); +} + +TEST(ColorUtils, make_theme_color) +{ + EXPECT_EQ(make_theme_color(*Color::parse("red"), false).toRGBA(), 0x870000ff); + EXPECT_EQ(make_theme_color(*Color::parse("red"), true).toRGBA(), 0x010101ff); + EXPECT_EQ(make_theme_color(*Color::parse("white"), false).toRGBA(), 0x787878ff); + EXPECT_EQ(make_theme_color(*Color::parse("white"), true).toRGBA(), 0x010101ff); + EXPECT_EQ(make_theme_color(*Color::parse("black"), false).toRGBA(), 0x030303ff); + EXPECT_EQ(make_theme_color(*Color::parse("black"), true).toRGBA(), 0x000000ff); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/colors/xml-color-test.cpp b/testfiles/src/colors/xml-color-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9d11195954b43d33f11462e082250cc62e463058 --- /dev/null +++ b/testfiles/src/colors/xml-color-test.cpp @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Unit tests for color xml conversions. + * + * Copyright (C) 2023 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "colors/xml-color.h" + +#include + +#include "colors/cms/profile.h" +#include "colors/cms/system.h" +#include "colors/color.h" +#include "colors/manager.h" +#include "colors/spaces/cms.h" +#include "preferences.h" + +using namespace Inkscape; +using namespace Inkscape::Colors; + +static std::string cmyk_profile = INKSCAPE_TESTS_DIR "/data/colors/default_cmyk.icc"; + +namespace { + +class ColorXmlColor : public ::testing::Test +{ + void SetUp() override + { + auto prefs = Inkscape::Preferences::get(); + prefs->setBool("/options/svgoutput/inlineattrs", false); + } +}; + +TEST_F(ColorXmlColor, test_paint_to_xml_string) +{ + ASSERT_EQ(paint_to_xml_string({}), R"( + + + +)"); + ASSERT_EQ(paint_to_xml_string(NoColor()), R"( + + + +)"); + ASSERT_EQ(paint_to_xml_string(Color(0xcf321244)), R"( + + + +)"); + ASSERT_EQ(paint_to_xml_string(*Color::parse("hsl(180,1,1)")), R"( + + + +)"); +} + +TEST_F(ColorXmlColor, test_icc_paint_xml) +{ + auto profile = CMS::Profile::create_from_uri(cmyk_profile); + CMS::System::get().addProfile(profile); + auto space = std::make_shared(profile); + space->setIntent(RenderingIntent::AUTO); + std::vector vals = {0.5, 0.2, 0.1, 0.23}; + auto color = Color(space, vals); + auto str = paint_to_xml_string(color); + + ASSERT_EQ(str, R"( + + + +)"); + + auto reverse = xml_string_to_paint(str, nullptr); + ASSERT_EQ(std::get(reverse).toString(), color.toString()); +} + +TEST_F(ColorXmlColor, test_xml_string_to_paint) +{ + ASSERT_TRUE(std::holds_alternative(xml_string_to_paint(R"( + + + +)", + nullptr))); + ASSERT_EQ(std::get(xml_string_to_paint(R"( + + + +)", + nullptr) + ).toString(), + "#cf321244"); + ASSERT_EQ(std::get(xml_string_to_paint(R"( + + + +)", + nullptr) + ).toString(), + "hsl(180, 1, 1)"); +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/object-style-test.cpp b/testfiles/src/object-style-test.cpp index 24f6c9d9440d3c30ef5f6cedb726527640080813..77bb25f174388eabd8492bc2dfab60f1a1ab1ca4 100644 --- a/testfiles/src/object-style-test.cpp +++ b/testfiles/src/object-style-test.cpp @@ -67,9 +67,8 @@ TEST_F(ObjectTest, Styles) { auto one = cast(doc->getObjectById("one")); ASSERT_TRUE(one != nullptr); - // TODO: Fix when Inkscape preserves colour names (i.e. 'red') - EXPECT_EQ(one->style->fill.get_value(), Glib::ustring("#ff0000")); - EXPECT_EQ(one->style->stroke.get_value(), Glib::ustring("#008000")); + EXPECT_EQ(one->style->fill.get_value(), Glib::ustring("red")); + EXPECT_EQ(one->style->stroke.get_value(), Glib::ustring("green")); EXPECT_EQ(one->style->opacity.get_value(), Glib::ustring("0.5")); EXPECT_EQ(one->style->stroke_width.get_value(), Glib::ustring("2px")); @@ -77,7 +76,7 @@ TEST_F(ObjectTest, Styles) { ASSERT_TRUE(two != nullptr); EXPECT_EQ(two->style->fill.get_value(), Glib::ustring("#808080")); - EXPECT_EQ(two->style->stroke.get_value(), Glib::ustring("#008000")); + EXPECT_EQ(two->style->stroke.get_value(), Glib::ustring("green")); EXPECT_EQ(two->style->opacity.get_value(), Glib::ustring("0.5")); EXPECT_EQ(two->style->stroke_width.get_value(), Glib::ustring("4px")); @@ -93,7 +92,7 @@ TEST_F(ObjectTest, Styles) { ASSERT_TRUE(four != nullptr); EXPECT_EQ(four->style->fill.get_value(), Glib::ustring("#d0d0d0")); - EXPECT_EQ(four->style->stroke.get_value(), Glib::ustring("#ff0000")); + EXPECT_EQ(four->style->stroke.get_value(), Glib::ustring("red")); EXPECT_EQ(four->style->opacity.get_value(), Glib::ustring("0.5")); EXPECT_EQ(four->style->stroke_width.get_value(), Glib::ustring("2px")); } diff --git a/testfiles/src/oklab-color-test.cpp b/testfiles/src/oklab-color-test.cpp deleted file mode 100644 index f4cc949b40ec0480f200a7c4855aa26cc027369e..0000000000000000000000000000000000000000 --- a/testfiles/src/oklab-color-test.cpp +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file Tests for the OKLab/OKLch color space backend. - */ -/* - * Authors: - * Rafał Siejakowski - * - * Copyright (C) 2022 Authors - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ - -#include -#include - -#include "color.h" -#include "oklab.h" - -unsigned constexpr L=0, A=1, B=2; -double constexpr EPS = 1e-7; - -inline Oklab::Triplet random_triplet() -{ - return { g_random_double_range(0.0, 1.0), - g_random_double_range(0.0, 1.0), - g_random_double_range(0.0, 1.0) }; -} - -/** Test converting black and white to OKLab. */ -TEST(OklabColorTest, BlackWhite) -{ - using namespace Oklab; - - auto const black = linear_rgb_to_oklab({0, 0, 0}); - EXPECT_NEAR(black[L], 0.0, EPS); - EXPECT_NEAR(black[A], 0.0, EPS); - EXPECT_NEAR(black[B], 0.0, EPS); - - auto const white = linear_rgb_to_oklab({1.0, 1.0, 1.0}); - EXPECT_NEAR(white[L], 1.0, EPS); - EXPECT_NEAR(white[A], 0.0, EPS); - EXPECT_NEAR(white[B], 0.0, EPS); -} - -/** Test linear RGB -> OKLab -> linear RGB roundtrip. */ -TEST(OKlabColorTest, RGBRoundrtip) -{ - using namespace Oklab; - g_random_set_seed(13375336); // We always seed for tests' repeatability - - for (unsigned i = 0; i < 10'000; i++) { - Triplet rgb = random_triplet(); - auto const roundtrip = oklab_to_linear_rgb(linear_rgb_to_oklab(rgb)); - for (size_t i : {0, 1, 2}) { - EXPECT_NEAR(roundtrip[i], rgb[i], EPS); - } - } -} - -/** Test OKLab -> linear RGB -> OKLab roundtrip. */ -TEST(OKlabColorTest, OklabRoundrtip) -{ - using namespace Oklab; - g_random_set_seed(0xCAFECAFE); - - for (unsigned i = 0; i < 10'000; i++) { - Triplet lab = linear_rgb_to_oklab(random_triplet()); - auto const roundtrip = linear_rgb_to_oklab(oklab_to_linear_rgb(lab)); - for (size_t i : {0, 1, 2}) { - EXPECT_NEAR(roundtrip[i], lab[i], EPS); - } - } -} - -/** Test OKLab -> OKLch -> OKLab roundtrip. */ -TEST(OKlabColorTest, PolarRectRoundrtip) -{ - using namespace Oklab; - g_random_set_seed(0xB747A380); - - for (unsigned i = 0; i < 10'000; i++) { - Triplet lab = linear_rgb_to_oklab(random_triplet()); - auto const roundtrip = oklch_to_oklab(oklab_to_oklch(lab)); - for (size_t i : {1, 2}) { // No point testing [0] since L == L - EXPECT_NEAR(roundtrip[i], lab[i], EPS); - } - } -} - -/** Test OKLch -> OKLab -> OKLch roundtrip. */ -TEST(OKlabColorTest, RectPolarRoundrtip) -{ - using namespace Oklab; - g_random_set_seed(0xFA18B52); - - for (unsigned i = 0; i < 10'000; i++) { - Triplet lch = oklab_to_oklch(linear_rgb_to_oklab(random_triplet())); - auto const roundtrip = oklab_to_oklch(oklch_to_oklab(lch)); - for (size_t i : {1, 2}) { // No point testing [0] - EXPECT_NEAR(roundtrip[i], lch[i], EPS); - } - } -} - -/** Test maximum chroma calculations. */ -TEST(OKlabColorTest, Saturate) -{ - using namespace Oklab; - g_random_set_seed(0x987654); - - /** Test whether a number lies near to the endpoint of the unit interval. */ - auto const near_end = [](double x) -> bool { - return x > 0.999 || x < 0.0001; - }; - - for (unsigned i = 0; i < 10'000; i++) { - // Get a random l, h pair and compute the maximum chroma. - auto [l, _, h] = oklab_to_oklch(linear_rgb_to_oklab(random_triplet())); - auto const chromax = max_chroma(l, h); - - // Try maximally saturating the color and verifying that after converting - // the result to RGB we end up hitting the boundary of the sRGB gamut. - auto [r, g, b] = oklab_to_linear_rgb(oklch_to_oklab({l, chromax, h})); - EXPECT_TRUE(near_end(r) || near_end(g) || near_end(b)); - } -} - -/** Test OKHSL -> OKLab -> OKHSL conversion roundtrip. */ -TEST(OKlabColorTest, HSLabRoundtrip) -{ - using namespace Oklab; - g_random_set_seed(908070); - - for (unsigned i = 0; i < 10'000; i++) { - auto const hsl = random_triplet(); - if (hsl[1] < 0.001) { - // Grayscale colors don't have unique hues, - // so we skip them (mapping is not bijective). - continue; - } - auto const roundtrip = oklab_to_okhsl(okhsl_to_oklab(hsl)); - for (size_t i : {0, 1, 2}) { - EXPECT_NEAR(roundtrip[i], hsl[i], EPS); - } - } -} - -/** Test OKLab -> OKHSL -> OKLab conversion roundtrip. */ -TEST(OKlabColorTest, LabHSLRoundtrip) -{ - using namespace Oklab; - g_random_set_seed(5043071); - - for (unsigned i = 0; i < 10'000; i++) { - auto const lab = linear_rgb_to_oklab(random_triplet()); - auto const roundtrip = okhsl_to_oklab(oklab_to_okhsl(lab)); - for (size_t i : {0, 1, 2}) { - EXPECT_NEAR(roundtrip[i], lab[i], EPS); - } - } -} - -/* - 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 : \ No newline at end of file diff --git a/testfiles/src/style-elem-test.cpp b/testfiles/src/style-elem-test.cpp index 83dfde6b7d0f97646c9b77990e43ee46078f63bc..5da4f7c6298421df986f2f6962fd2025ce73e30c 100644 --- a/testfiles/src/style-elem-test.cpp +++ b/testfiles/src/style-elem-test.cpp @@ -58,13 +58,13 @@ TEST_F(ObjectTest, StyleElems) { ASSERT_TRUE(one); for (auto &style : one->get_styles()) { - EXPECT_EQ(style->fill.get_value(), Glib::ustring("#ff0000")); + EXPECT_EQ(style->fill.get_value(), Glib::ustring("red")); } auto two = cast(doc->getObjectById("style02")); ASSERT_TRUE(one); for (auto &style : two->get_styles()) { - EXPECT_EQ(style->fill.get_value(), Glib::ustring("#008000")); + EXPECT_EQ(style->fill.get_value(), Glib::ustring("green")); } } diff --git a/testfiles/src/style-internal-test.cpp b/testfiles/src/style-internal-test.cpp index 6312bb4509599183b16c34548a0c7f09df977029..6d539f193b0da3e00967bb90cb4eea529696a352 100644 --- a/testfiles/src/style-internal-test.cpp +++ b/testfiles/src/style-internal-test.cpp @@ -70,6 +70,16 @@ TEST(StyleInternalTest, testSPIDashArrayValidity) // EXPECT_FALSE(array23.is_valid()); // negative total: invalid and removed by 'read' } +TEST(StyleInternalTest, testSPIPaint) +{ + SPIPaint paint; + EXPECT_EQ(paint.get_value(), ""); + paint.read("red"); + EXPECT_EQ(paint.get_value(), "red"); + paint.clear(); + EXPECT_EQ(paint.get_value(), ""); +} + /* Local Variables: mode:c++ diff --git a/testfiles/src/style-test.cpp b/testfiles/src/style-test.cpp index f0f427f1cd3014c7e33d878a1c8103a378839d24..1e1683651de8e2b9d8cd72ec0db5da995cfc9bc7 100644 --- a/testfiles/src/style-test.cpp +++ b/testfiles/src/style-test.cpp @@ -61,8 +61,8 @@ std::vector getStyleData() // StyleRead("fill:url(#painter) rgb(255, 0, 255)", // "fill:url(#painter) #ff00ff", "#painter"), - - StyleRead("fill:#ff00ff icc-color(colorChange, 0.1, 0.5, 0.1)"), + // Requires a documentCMS to be available to the SPStyle + // StyleRead("fill:#ff00ff icc-color(colorChange, 0.1, 0.5, 0.1)"), // StyleRead("fill:url(#painter)", "", "#painter"), // StyleRead("fill:url(#painter) none", "", "#painter"), @@ -85,7 +85,7 @@ std::vector getStyleData() StyleRead("overflow:visible"), // SPIEnum StyleRead("overflow:auto"), // SPIEnum - StyleRead("color:#ff0000"), StyleRead("color:blue", "color:#0000ff"), + StyleRead("color:#ff0000"), StyleRead("color:blue", "color:blue"), // StyleRead("color:currentColor"), SVG 1.1 does not allow color value 'currentColor' // Font shorthand @@ -343,7 +343,7 @@ std::vector getStyleMatchData() // SPIColor StyleMatch("color:blue", "color:blue", true ), StyleMatch("color:blue", "color:red", false), - StyleMatch("color:red", "color:#ff0000", true ), + StyleMatch("color:red", "color:#ff0000", false), // SPIPaint StyleMatch("fill:blue", "fill:blue", true ), diff --git a/testfiles/src/svg-color-test.cpp b/testfiles/src/svg-color-test.cpp deleted file mode 100644 index c4e937928d652785b51d4853851bc0598e429812..0000000000000000000000000000000000000000 --- a/testfiles/src/svg-color-test.cpp +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/** @file - * Test for SVG colors - *//* - * Authors: see git history - * - * Copyright (C) 2010 Authors - * Released under GNU GPL v2+, read the file 'COPYING' for more information. - */ -#include "svg/svg-color.h" - -#include -#include - -#include "preferences.h" -#include "svg/svg-icc-color.h" - -static void check_rgb24(unsigned const rgb24) -{ - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - char css[8]; - - prefs->setBool("/options/svgoutput/usenamedcolors", false); - sp_svg_write_color(css, sizeof(css), rgb24 << 8); - ASSERT_EQ(sp_svg_read_color(css, 0xff), rgb24 << 8); - - prefs->setBool("/options/svgoutput/usenamedcolors", true); - sp_svg_write_color(css, sizeof(css), rgb24 << 8); - ASSERT_EQ(sp_svg_read_color(css, 0xff), rgb24 << 8); -} - -TEST(SvgColorTest, testWrite) -{ - unsigned const components[] = {0, 0x80, 0xff, 0xc0, 0x77}; - unsigned const nc = G_N_ELEMENTS(components); - for (unsigned i = nc * nc * nc; i--;) { - unsigned tmp = i; - unsigned rgb24 = 0; - for (unsigned c = 0; c < 3; ++c) { - unsigned const component = components[tmp % nc]; - rgb24 = (rgb24 << 8) | component; - tmp /= nc; - } - ASSERT_TRUE(tmp == 0); - check_rgb24(rgb24); - } - - /* And a few completely random ones. */ - for (unsigned i = 500; i--;) { /* Arbitrary number of iterations. */ - unsigned const rgb24 = (std::rand() >> 4) & 0xffffff; - check_rgb24(rgb24); - } -} - -TEST(SvgColorTest, testReadColor) -{ - gchar const *val[] = {"#f0f", "#ff00ff", "rgb(255,0,255)", "fuchsia"}; - size_t const n = sizeof(val) / sizeof(*val); - for (size_t i = 0; i < n; i++) { - gchar const *end = 0; - guint32 result = sp_svg_read_color(val[i], &end, 0x3); - ASSERT_EQ(result, 0xff00ff00); - ASSERT_LT(val[i], end); - } -} - -TEST(SvgColorTest, testIccColor) -{ - struct - { - unsigned numEntries; - bool shouldPass; - char const *name; - char const *str; - } cases[] = { - {1, true, "named", "icc-color(named, 3)"}, - {0, false, "", "foodle"}, - {1, true, "a", "icc-color(a, 3)"}, - {4, true, "named", "icc-color(named, 3, 0, 0.1, 2.5)"}, - {0, false, "", "icc-color(named, 3"}, - {0, false, "", "icc-color(space named, 3)"}, - {0, false, "", "icc-color(tab\tnamed, 3)"}, - {0, false, "", "icc-color(0name, 3)"}, - {0, false, "", "icc-color(-name, 3)"}, - {1, true, "positive", "icc-color(positive, +3)"}, - {1, true, "negative", "icc-color(negative, -3)"}, - {1, true, "positive", "icc-color(positive, +0.1)"}, - {1, true, "negative", "icc-color(negative, -0.1)"}, - {0, false, "", "icc-color(named, value)"}, - {1, true, "hyphen-name", "icc-color(hyphen-name, 1)"}, - {1, true, "under_name", "icc-color(under_name, 1)"}, - }; - - for (size_t i = 0; i < G_N_ELEMENTS(cases); i++) { - SVGICCColor tmp; - char const *str = cases[i].str; - char const *result = nullptr; - - bool parseRet = sp_svg_read_icc_color(str, &result, &tmp); - ASSERT_EQ(parseRet, cases[i].shouldPass) << str; - ASSERT_EQ(tmp.colors.size(), cases[i].numEntries) << str; - if (cases[i].shouldPass) { - ASSERT_STRNE(str, result); - ASSERT_EQ(tmp.colorProfile, cases[i].name) << str; - } else { - ASSERT_STREQ(str, result); - ASSERT_TRUE(tmp.colorProfile.empty()); - } - } -} - -// vim: filetype=cpp:expandtab:shiftwidth=4:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/testfiles/src/test-utils.h b/testfiles/src/test-utils.h new file mode 100644 index 0000000000000000000000000000000000000000..f5920db816cc99c79f064c32ef3466d777a177aa --- /dev/null +++ b/testfiles/src/test-utils.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Shared test header for testing colors + * + * Copyright (C) 2024 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include + +namespace { + +/** + * Allow the correct tracing of the file and line where data came from when using P_TESTs. + */ +struct traced_data +{ + const char *_file; + const int _line; + + ::testing::ScopedTrace enable_scope() const { return ::testing::ScopedTrace(_file, _line, ""); } +}; +// Macro for the above tracing in P Tests +#define _P(type, ...) \ + type \ + { \ + __FILE__, __LINE__, __VA_ARGS__ \ + } + + +/** + * Print a vector of doubles for debugging + */ +std::string print_values(const std::vector &v) +{ + std::ostringstream oo; + oo << "{"; + bool first = true; + for (double const &item : v) { + if (!first) { + oo << ", "; + } + first = false; + oo << std::setprecision(3) << item; + } + oo << "}"; + return oo.str(); +} + +/** + * Test each value in a values list is within a certain distance from each other. + */ +::testing::AssertionResult VectorIsNear(std::vector const &A, std::vector const &B, double epsilon) +{ + bool is_same = A.size() == B.size(); + for (size_t i = 0; is_same && i < A.size(); i++) { + is_same = is_same and (std::fabs((A[i]) - (B[i])) < epsilon); + } + if (!is_same) { + return ::testing::AssertionFailure() << "\n" << print_values(A) << "\n != \n" << print_values(B); + } + return ::testing::AssertionSuccess(); +} + + +/** + * Generate a count of random doubles between 0 and 1. + * + * Randomly appends an extra value for optional opacity. + */ +inline static std::vector random_values(unsigned ccount) +{ + std::vector values; + for (unsigned j = 0; j < ccount; j++) { + values.emplace_back(static_cast(std::rand()) / RAND_MAX); + } + // randomly add opacity + if (std::rand() > (RAND_MAX / 2)) { + values.emplace_back(static_cast(std::rand()) / RAND_MAX); + } + return values; +} + +} // namespace + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/testfiles/src/ui-util-test.cpp b/testfiles/src/ui-util-test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1cd95a2baf306ba096bcd11cf2a78d6ff2a9bafe --- /dev/null +++ b/testfiles/src/ui-util-test.cpp @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Test utilities from src/ui/ + */ +/* + * Authors: + * Martin Owens + * + * Copyright (C) 2024 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include "test-utils.h" + +#include "ui/widget/canvas/util.h" + +using namespace Inkscape::UI::Widget; + +::testing::AssertionResult Array3IsNear(std::array const &A, std::vector const &B, double epsilon) +{ + std::vector av; + for (auto v : A) { + av.emplace_back((double)v); + } + return VectorIsNear(av, B, epsilon); +} + +TEST(UtilTest, CheckerboardDarken) +{ + EXPECT_TRUE(Array3IsNear(checkerboard_darken(0x00000000), {0.08, 0.08, 0.08}, 0.01)); + EXPECT_TRUE(Array3IsNear(checkerboard_darken(0x00000080), {0.0398, 0.0398, 0.0398}, 0.01)); + EXPECT_TRUE(Array3IsNear(checkerboard_darken(0x000000ff), {0, 0, 0}, 0.01)); + EXPECT_TRUE(Array3IsNear(checkerboard_darken(0x00000080), {0.0398, 0.0398, 0.0398}, 0.01)); + EXPECT_TRUE(Array3IsNear(checkerboard_darken(0xffffff00), {0.92, 0.92, 0.92}, 0.01)); + EXPECT_TRUE(Array3IsNear(checkerboard_darken(0xffffffff), {1, 1, 1}, 0.01)); + EXPECT_TRUE(Array3IsNear(checkerboard_darken(0x80808000), {0.422, 0.422, 0.422}, 0.01)); + EXPECT_TRUE(Array3IsNear(checkerboard_darken(0x80808080), {0.462, 0.462, 0.462}, 0.01)); + EXPECT_TRUE(Array3IsNear(checkerboard_darken(0x808080ff), {0.502, 0.502, 0.502}, 0.01)); +} + +// vim: filetype=cpp:expandtab:shiftwidth=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :