/* This was part of the KDE project - see KGuiAddons * Copyright (C) 2007 Matthew Woehlke * Copyright (C) 2007 Olaf Schmidt * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Zack Rusin * Copyright (C) 2015 Petr Mrazek * * 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/rainbow.h" #include #include #include // qIsNaN #include //BEGIN internal helper functions 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)); } // normalize: like qBound(a, 0.0, 1.0) but without needing the args and with // "safer" behavior on NaN (isnan(a) -> return 0.0) static inline qreal normalize(qreal a) { return (a < 1.0 ? (a > 0.0 ? a : 0.0) : 1.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 class KHCY { public: explicit 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)); } } explicit KHCY(qreal h_, qreal c_, qreal y_, qreal a_ = 1.0) { h = h_; c = c_; y = y_; a = a_; } QColor 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 h, c, y, a; static qreal luma(const QColor &color) { return lumag(gamma(color.redF()), gamma(color.greenF()), gamma(color.blueF())); } private: static qreal gamma(qreal n) { return pow(normalize(n), 2.2); } static qreal igamma(qreal n) { return pow(normalize(n), 1.0 / 2.2); } static qreal lumag(qreal r, qreal g, qreal b) { return r * yc[0] + g * yc[1] + b * yc[2]; } }; static inline qreal mixQreal(qreal a, qreal b, qreal bias) { return a + (b - a) * bias; } //END internal helper functions qreal Rainbow::luma(const QColor &color) { return KHCY::luma(color); } void Rainbow::getHcy(const QColor &color, qreal *h, qreal *c, qreal *y, qreal *a) { if (!c || !h || !y) { return; } KHCY khcy(color); *c = khcy.c; *h = khcy.h; *y = khcy.y; if (a) { *a = khcy.a; } } 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 Rainbow::contrastRatio(const QColor &c1, const QColor &c2) { return contrastRatioForLuma(luma(c1), luma(c2)); } QColor Rainbow::lighten(const QColor &color, qreal ky, qreal kc) { 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 Rainbow::darken(const QColor &color, qreal ky, qreal kc) { KHCY c(color); c.y = normalize(c.y * (1.0 - ky)); c.c = normalize(c.c * kc); return c.qColor(); } QColor Rainbow::shade(const QColor &color, qreal ky, qreal kc) { 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) { KHCY result(Rainbow::mix(base, color, pow(amount, 0.3))); result.y = mixQreal(baseLuma, result.y, amount); return result.qColor(); } QColor Rainbow::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 Rainbow::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 Rainbow::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); }