diff --git a/src/colors/kcolorspaces.cpp b/src/colors/kcolorspaces.cpp index 7c9ed89..9495959 100644 --- a/src/colors/kcolorspaces.cpp +++ b/src/colors/kcolorspaces.cpp @@ -1,165 +1,175 @@ /* This file is part of the KDE project * Copyright (C) 2007 Matthew Woehlke * Copyright (C) 2007 Olaf Schmidt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kcolorspaces_p.h" #include "kguiaddons_colorhelpers_p.h" #include #include using namespace KColorSpaces; static inline qreal wrap(qreal a, qreal d = 1.0) { qreal r = fmod(a, d); return (r < 0.0 ? d + r : (r > 0.0 ? r : 0.0)); } /////////////////////////////////////////////////////////////////////////////// // HCY color space #define HCY_REC 709 // use 709 for now #if HCY_REC == 601 static const qreal yc[3] = { 0.299, 0.587, 0.114 }; #elif HCY_REC == 709 static const qreal yc[3] = {0.2126, 0.7152, 0.0722}; #else // use Qt values static const qreal yc[3] = { 0.34375, 0.5, 0.15625 }; #endif qreal KHCY::gamma(qreal n) { return pow(normalize(n), 2.2); } qreal KHCY::igamma(qreal n) { return pow(normalize(n), 1.0 / 2.2); } qreal KHCY::lumag(qreal r, qreal g, qreal b) { return r * yc[0] + g * yc[1] + b * yc[2]; } KHCY::KHCY(qreal h_, qreal c_, qreal y_, qreal a_) { h = h_; c = c_; y = y_; a = a_; } KHCY::KHCY(const QColor &color) { qreal r = gamma(color.redF()); qreal g = gamma(color.greenF()); qreal b = gamma(color.blueF()); a = color.alphaF(); // luma component y = lumag(r, g, b); // hue component qreal p = qMax(qMax(r, g), b); qreal n = qMin(qMin(r, g), b); qreal d = 6.0 * (p - n); if (n == p) { h = 0.0; } else if (r == p) { h = ((g - b) / d); } else if (g == p) { h = ((b - r) / d) + (1.0 / 3.0); } else { h = ((r - g) / d) + (2.0 / 3.0); } // chroma component if (r == g && g == b) { c = 0.0; } else { c = qMax((y - n) / y, (p - y) / (1 - y)); } } QColor KHCY::qColor() const { // start with sane component values qreal _h = wrap(h); qreal _c = normalize(c); qreal _y = normalize(y); // calculate some needed variables qreal _hs = _h * 6.0, th, tm; if (_hs < 1.0) { th = _hs; tm = yc[0] + yc[1] * th; } else if (_hs < 2.0) { th = 2.0 - _hs; tm = yc[1] + yc[0] * th; } else if (_hs < 3.0) { th = _hs - 2.0; tm = yc[1] + yc[2] * th; } else if (_hs < 4.0) { th = 4.0 - _hs; tm = yc[2] + yc[1] * th; } else if (_hs < 5.0) { th = _hs - 4.0; tm = yc[2] + yc[0] * th; } else { th = 6.0 - _hs; tm = yc[0] + yc[2] * th; } // calculate RGB channels in sorted order qreal tn, to, tp; if (tm >= _y) { tp = _y + _y * _c * (1.0 - tm) / tm; to = _y + _y * _c * (th - tm) / tm; tn = _y - (_y * _c); } else { tp = _y + (1.0 - _y) * _c; to = _y + (1.0 - _y) * _c * (th - tm) / (1.0 - tm); tn = _y - (1.0 - _y) * _c * tm / (1.0 - tm); } // return RGB channels in appropriate order if (_hs < 1.0) { return QColor::fromRgbF(igamma(tp), igamma(to), igamma(tn), a); } else if (_hs < 2.0) { return QColor::fromRgbF(igamma(to), igamma(tp), igamma(tn), a); } else if (_hs < 3.0) { return QColor::fromRgbF(igamma(tn), igamma(tp), igamma(to), a); } else if (_hs < 4.0) { return QColor::fromRgbF(igamma(tn), igamma(to), igamma(tp), a); } else if (_hs < 5.0) { return QColor::fromRgbF(igamma(to), igamma(tn), igamma(tp), a); } else { return QColor::fromRgbF(igamma(tp), igamma(tn), igamma(to), a); } } +qreal KHCY::hue(const QColor &color) +{ + return wrap(KHCY(color).h); +} + +qreal KHCY::chroma(const QColor &color) +{ + return KHCY(color).c; +} + qreal KHCY::luma(const QColor &color) { return lumag(gamma(color.redF()), gamma(color.greenF()), gamma(color.blueF())); } diff --git a/src/colors/kcolorspaces_p.h b/src/colors/kcolorspaces_p.h index 8da5bf9..db5a36a 100644 --- a/src/colors/kcolorspaces_p.h +++ b/src/colors/kcolorspaces_p.h @@ -1,50 +1,52 @@ /* This file is part of the KDE project * Copyright (C) 2007 Matthew Woehlke * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* * If you use KColorSpaces in your own KDE code, please drop me a line at * mw_triad@users.sourceforge.net, as I would like to track if people find it * useful. Thanks! */ #ifndef KCOLORSPACES_H #define KCOLORSPACES_H #include namespace KColorSpaces { class KHCY { public: explicit KHCY(const QColor &); explicit KHCY(qreal h_, qreal c_, qreal y_, qreal a_ = 1.0); QColor qColor() const; qreal h, c, y, a; + static qreal hue(const QColor &); + static qreal chroma(const QColor &); static qreal luma(const QColor &); private: static qreal gamma(qreal); static qreal igamma(qreal); static qreal lumag(qreal, qreal, qreal); }; } #endif diff --git a/src/colors/kcolorutils.cpp b/src/colors/kcolorutils.cpp index 3f4f1c8..397ac67 100644 --- a/src/colors/kcolorutils.cpp +++ b/src/colors/kcolorutils.cpp @@ -1,167 +1,182 @@ /* This file is part of the KDE project * Copyright (C) 2007 Matthew Woehlke * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Zack Rusin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include "kcolorspaces_p.h" #include "kguiaddons_colorhelpers_p.h" #include #include #include // qIsNaN #include // BEGIN internal helper functions static inline qreal mixQreal(qreal a, qreal b, qreal bias) { return a + (b - a) * bias; } // END internal helper functions +qreal KColorUtils::hue(const QColor &color) +{ + return KColorSpaces::KHCY::hue(color); +} + +qreal KColorUtils::chroma(const QColor &color) +{ + return KColorSpaces::KHCY::chroma(color); +} + qreal KColorUtils::luma(const QColor &color) { return KColorSpaces::KHCY::luma(color); } void KColorUtils::getHcy(const QColor &color, qreal *h, qreal *c, qreal *y, qreal *a) { if (!c || !h || !y) { return; } KColorSpaces::KHCY khcy(color); *c = khcy.c; *h = khcy.h; *y = khcy.y; if (a) { *a = khcy.a; } } +QColor KColorUtils::hcyColor(qreal h, qreal c, qreal y, qreal a) +{ + return KColorSpaces::KHCY(h, c, y, a).qColor(); +} + static qreal contrastRatioForLuma(qreal y1, qreal y2) { if (y1 > y2) { return (y1 + 0.05) / (y2 + 0.05); } else { return (y2 + 0.05) / (y1 + 0.05); } } qreal KColorUtils::contrastRatio(const QColor &c1, const QColor &c2) { return contrastRatioForLuma(luma(c1), luma(c2)); } QColor KColorUtils::lighten(const QColor &color, qreal ky, qreal kc) { KColorSpaces::KHCY c(color); c.y = 1.0 - normalize((1.0 - c.y) * (1.0 - ky)); c.c = 1.0 - normalize((1.0 - c.c) * kc); return c.qColor(); } QColor KColorUtils::darken(const QColor &color, qreal ky, qreal kc) { KColorSpaces::KHCY c(color); c.y = normalize(c.y * (1.0 - ky)); c.c = normalize(c.c * kc); return c.qColor(); } QColor KColorUtils::shade(const QColor &color, qreal ky, qreal kc) { KColorSpaces::KHCY c(color); c.y = normalize(c.y + ky); c.c = normalize(c.c + kc); return c.qColor(); } static QColor tintHelper(const QColor &base, qreal baseLuma, const QColor &color, qreal amount) { KColorSpaces::KHCY result(KColorUtils::mix(base, color, pow(amount, 0.3))); result.y = mixQreal(baseLuma, result.y, amount); return result.qColor(); } QColor KColorUtils::tint(const QColor &base, const QColor &color, qreal amount) { if (amount <= 0.0) { return base; } if (amount >= 1.0) { return color; } if (qIsNaN(amount)) { return base; } qreal baseLuma = luma(base); //cache value because luma call is expensive double ri = contrastRatioForLuma(baseLuma, luma(color)); double rg = 1.0 + ((ri + 1.0) * amount * amount * amount); double u = 1.0, l = 0.0; QColor result; for (int i = 12; i; --i) { double a = 0.5 * (l + u); result = tintHelper(base, baseLuma, color, a); double ra = contrastRatioForLuma(baseLuma, luma(result)); if (ra > rg) { u = a; } else { l = a; } } return result; } QColor KColorUtils::mix(const QColor &c1, const QColor &c2, qreal bias) { if (bias <= 0.0) { return c1; } if (bias >= 1.0) { return c2; } if (qIsNaN(bias)) { return c1; } qreal r = mixQreal(c1.redF(), c2.redF(), bias); qreal g = mixQreal(c1.greenF(), c2.greenF(), bias); qreal b = mixQreal(c1.blueF(), c2.blueF(), bias); qreal a = mixQreal(c1.alphaF(), c2.alphaF(), bias); return QColor::fromRgbF(r, g, b, a); } QColor KColorUtils::overlayColors(const QColor &base, const QColor &paint, QPainter::CompositionMode comp) { // This isn't the fastest way, but should be "fast enough". // It's also the only safe way to use QPainter::CompositionMode QImage img(1, 1, QImage::Format_ARGB32_Premultiplied); QPainter p(&img); QColor start = base; start.setAlpha(255); // opaque p.fillRect(0, 0, 1, 1, start); p.setCompositionMode(comp); p.fillRect(0, 0, 1, 1, paint); p.end(); return img.pixel(0, 0); } diff --git a/src/colors/kcolorutils.h b/src/colors/kcolorutils.h index 0785518..adcba80 100644 --- a/src/colors/kcolorutils.h +++ b/src/colors/kcolorutils.h @@ -1,160 +1,202 @@ /* This file is part of the KDE project * Copyright (C) 2007 Matthew Woehlke * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Zack Rusin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KCOLORUTILS_H #define KCOLORUTILS_H #include #include class QColor; /** * A set of methods used to work with colors. */ namespace KColorUtils { +/** + * Calculate the hue of a color. The range is from 0.0 (red) to almost 1.0 (slightly blue-ish red). + * + * The result is computed in linear (not sRGB) color space and may differ slightly from QColor::hue(). + * + * @see https://en.wikipedia.org/wiki/Hue + * @since 5.68 + */ +KGUIADDONS_EXPORT qreal hue(const QColor &); + +/** + * Calculate the chroma of a color. The range is from 0.0 (none) to 1.0 (full). + * + * The result is computed in linear (not sRGB) color space. + * + * @see https://en.wikipedia.org/wiki/Colorfulness + * @since 5.68 + */ +KGUIADDONS_EXPORT qreal chroma(const QColor &); + /** * Calculate the luma of a color. Luma is weighted sum of gamma-adjusted * R'G'B' components of a color. The result is similar to qGray. The range * is from 0.0 (black) to 1.0 (white). + * + * The result is computed in linear (not sRGB) color space. * * KColorUtils::darken(), KColorUtils::lighten() and KColorUtils::shade() * operate on the luma of a color. * * @see http://en.wikipedia.org/wiki/Luma_(video) */ KGUIADDONS_EXPORT qreal luma(const QColor &); /** * Calculate hue, chroma and luma of a color in one call. + * + * The range of hue is from 0.0 (red) to almost 1.0 (slightly blue-ish red). + * The range of chroma is from 0.0 (none) to 1.0 (full). + * The range of luma is from 0.0 (black) to 1.0 (white). + * + * The hue, chroma and luma values are computed in linear (not sRGB) color space. + * * @since 5.0 */ KGUIADDONS_EXPORT void getHcy(const QColor &, qreal *hue, qreal *chroma, qreal *luma, qreal *alpha = nullptr); +/** + * Return a QColor based on the given hue, chroma, luma and alpha values. + * + * The range of hue is cyclical. For example, 0.0 and 1.0 are both red while -0.166667 and 0.833333 are both magenta. + * The range of chroma is from 0.0 (none) to 1.0 (full). Out of range values will be clamped. + * The range of luma is from 0.0 (black) to 1.0 (white). Out of range values will be clamped. + * + * The hue, chroma and luma values are computed in linear (not sRGB) color space. + * + * @since 5.68 + */ +KGUIADDONS_EXPORT QColor hcyColor(qreal hue, qreal chroma, qreal luma, qreal alpha = 1.0); + /** * Calculate the contrast ratio between two colors, according to the * W3C/WCAG2.0 algorithm, (Lmax + 0.05)/(Lmin + 0.05), where Lmax and Lmin * are the luma values of the lighter color and the darker color, * respectively. * * A contrast ration of 5:1 (result == 5.0) is the minimum for "normal" * text to be considered readable (large text can go as low as 3:1). The * ratio ranges from 1:1 (result == 1.0) to 21:1 (result == 21.0). * * @see KColorUtils::luma */ KGUIADDONS_EXPORT qreal contrastRatio(const QColor &, const QColor &); /** * Adjust the luma of a color by changing its distance from white. * * @li amount == 1.0 gives white * @li amount == 0.5 results in a color whose luma is halfway between 1.0 * and that of the original color * @li amount == 0.0 gives the original color * @li amount == -1.0 gives a color that is 'twice as far from white' as * the original color, that is luma(result) == 1.0 - 2*(1.0 - luma(color)) * * @param amount factor by which to adjust the luma component of the color * @param chromaInverseGain (optional) factor by which to adjust the chroma * component of the color; 1.0 means no change, 0.0 maximizes chroma * @see KColorUtils::shade */ KGUIADDONS_EXPORT QColor lighten(const QColor &, qreal amount = 0.5, qreal chromaInverseGain = 1.0); /** * Adjust the luma of a color by changing its distance from black. * * @li amount == 1.0 gives black * @li amount == 0.5 results in a color whose luma is halfway between 0.0 * and that of the original color * @li amount == 0.0 gives the original color * @li amount == -1.0 gives a color that is 'twice as far from black' as * the original color, that is luma(result) == 2*luma(color) * * @param amount factor by which to adjust the luma component of the color * @param chromaGain (optional) factor by which to adjust the chroma * component of the color; 1.0 means no change, 0.0 minimizes chroma * @see KColorUtils::shade */ KGUIADDONS_EXPORT QColor darken(const QColor &, qreal amount = 0.5, qreal chromaGain = 1.0); /** * Adjust the luma and chroma components of a color. The amount is added * to the corresponding component. * * @param lumaAmount amount by which to adjust the luma component of the * color; 0.0 results in no change, -1.0 turns anything black, 1.0 turns * anything white * @param chromaAmount (optional) amount by which to adjust the chroma * component of the color; 0.0 results in no change, -1.0 minimizes chroma, * 1.0 maximizes chroma * @see KColorUtils::luma */ KGUIADDONS_EXPORT QColor shade(const QColor &, qreal lumaAmount, qreal chromaAmount = 0.0); /** * Create a new color by tinting one color with another. This function is * meant for creating additional colors withings the same class (background, * foreground) from colors in a different class. Therefore when @p amount * is low, the luma of @p base is mostly preserved, while the hue and * chroma of @p color is mostly inherited. * * @param base color to be tinted * @param color color with which to tint * @param amount how strongly to tint the base; 0.0 gives @p base, * 1.0 gives @p color */ KGUIADDONS_EXPORT QColor tint(const QColor &base, const QColor &color, qreal amount = 0.3); /** * Blend two colors into a new color by linear combination. * @code QColor lighter = KColorUtils::mix(myColor, Qt::white) * @endcode * @param c1 first color. * @param c2 second color. * @param bias weight to be used for the mix. @p bias <= 0 gives @p c1, * @p bias >= 1 gives @p c2. @p bias == 0.5 gives a 50% blend of @p c1 * and @p c2. */ KGUIADDONS_EXPORT QColor mix(const QColor &c1, const QColor &c2, qreal bias = 0.5); /** * Blend two colors into a new color by painting the second color over the * first using the specified composition mode. * @code QColor white(Qt::white); white.setAlphaF(0.5); QColor lighter = KColorUtils::overlayColors(myColor, white); @endcode * @param base the base color (alpha channel is ignored). * @param paint the color to be overlayed onto the base color. * @param comp the CompositionMode used to do the blending. */ KGUIADDONS_EXPORT QColor overlayColors(const QColor &base, const QColor &paint, QPainter::CompositionMode comp = QPainter::CompositionMode_SourceOver); } #endif // KCOLORUTILS_H