diff --git a/src/colorutils.cpp b/src/colorutils.cpp index 2d85f2e9..3da2e290 100644 --- a/src/colorutils.cpp +++ b/src/colorutils.cpp @@ -1,211 +1,246 @@ /* * SPDX-FileCopyrightText: 2020 Carson Black * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "colorutils.h" #include #include #include #include ColorUtils::ColorUtils(QObject *parent) : QObject(parent) {} -ColorUtils::Brightness ColorUtils::brightnessForColor(QColor color) { - auto luma = [](QColor color) { - return (0.299*color.red() + 0.587*color.green() + 0.114*color.blue())/255; +ColorUtils::Brightness ColorUtils::brightnessForColor(const QColor &color) { + auto luma = [](const QColor &color) { + return (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255; }; return luma(color) > 0.5 ? ColorUtils::Brightness::Light : ColorUtils::Brightness::Dark; } -QColor ColorUtils::alphaBlend(QColor foreground, QColor background) { +QColor ColorUtils::alphaBlend(const QColor &foreground, const QColor &background) { const auto foregroundAlpha = foreground.alpha(); const auto inverseForegroundAlpha = 0xff - foregroundAlpha; const auto backgroundAlpha = background.alpha(); - if (foregroundAlpha == 0x00) return background; + if (foregroundAlpha == 0x00) { + return background; + } if (backgroundAlpha == 0xff) { return QColor::fromRgb( (foregroundAlpha*foreground.red()) + (inverseForegroundAlpha*background.red()), (foregroundAlpha*foreground.green()) + (inverseForegroundAlpha*background.green()), (foregroundAlpha*foreground.blue()) + (inverseForegroundAlpha*background.blue()), 0xff ); } else { const auto inverseBackgroundAlpha = (backgroundAlpha * inverseForegroundAlpha) / 255; const auto finalAlpha = foregroundAlpha + inverseBackgroundAlpha; Q_ASSERT(finalAlpha != 0x00); return QColor::fromRgb( (foregroundAlpha*foreground.red()) + (inverseBackgroundAlpha*background.red()), (foregroundAlpha*foreground.green()) + (inverseBackgroundAlpha*background.green()), (foregroundAlpha*foreground.blue()) + (inverseBackgroundAlpha*background.blue()), finalAlpha ); } } -QColor ColorUtils::linearInterpolation(QColor one, QColor two, double balance) { +QColor ColorUtils::linearInterpolation(const QColor &one, const QColor &two, double balance) { - auto scaleAlpha = [](QColor color, double factor) { - return QColor::fromRgb(color.red(), color.green(), color.blue(), color.alpha()*factor); + auto scaleAlpha = [](const QColor &color, double factor) { + return QColor::fromRgb(color.red(), color.green(), color.blue(), color.alpha() * factor); }; auto linearlyInterpolateDouble = [](double one, double two, double factor) { return one + (two - one) * factor; }; - if (one == Qt::transparent) return scaleAlpha(two, balance); - if (two == Qt::transparent) return scaleAlpha(one, 1 - balance); + if (one == Qt::transparent) { + return scaleAlpha(two, balance); + } + if (two == Qt::transparent) { + return scaleAlpha(one, 1 - balance); + } return QColor::fromHsv( std::fmod(linearlyInterpolateDouble(one.hue(), two.hue(), balance), 360.0), qBound(0.0, linearlyInterpolateDouble(one.saturation(), two.saturation(), balance), 255.0), qBound(0.0, linearlyInterpolateDouble(one.value(), two.value(), balance), 255.0), qBound(0.0, linearlyInterpolateDouble(one.alpha(), two.alpha(), balance), 255.0) ); } // Some private things for the adjust, change, and scale properties struct ParsedAdjustments { double red = 0.0; double green = 0.0; double blue = 0.0; double hue = 0.0; double saturation = 0.0; double value = 0.0; double alpha = 0.0; }; -ParsedAdjustments parseAdjustments(QJSValue value) +ParsedAdjustments parseAdjustments(const QJSValue &value) { ParsedAdjustments parsed; - auto checkProperty = [](QJSValue value, QString property) { + auto checkProperty = [](const QJSValue &value, const QString &property) { if (value.hasProperty(property)) { auto val = value.property(property); if (val.isNumber()) { return QVariant::fromValue(val.toNumber()); } } return QVariant(); }; - std::map map = { + std::vector> items { { QStringLiteral("red"), parsed.red }, { QStringLiteral("green"), parsed.green }, { QStringLiteral("blue"), parsed.blue }, // { QStringLiteral("hue"), parsed.hue }, { QStringLiteral("saturation"), parsed.saturation }, { QStringLiteral("value"), parsed.value }, { QStringLiteral("lightness"), parsed.value }, // { QStringLiteral("alpha"), parsed.alpha } }; - for (std::pair item : map) { + for (const auto &item : items) { auto val = checkProperty(value, item.first); - if (val != QVariant()) item.second = val.toDouble(); + if (val.isValid()) { + item.second = val.toDouble(); + } } if ((parsed.red || parsed.green || parsed.blue) && (parsed.hue || parsed.saturation || parsed.value)) { qCritical() << "It is an error to have both RGB and HSL values in an adjustment."; } return parsed; } -QColor ColorUtils::adjustColor(QColor color, QJSValue adjustments) +QColor ColorUtils::adjustColor(const QColor &color, const QJSValue &adjustments) { auto adjusts = parseAdjustments(adjustments); - if (qBound(-360.0, adjusts.hue, 360.0) != adjusts.hue) qCritical() << "Hue is out of bounds"; - - if (qBound(-255.0, adjusts.red, 255.0) != adjusts.red) qCritical() << "Red is out of bounds"; - if (qBound(-255.0, adjusts.green, 255.0) != adjusts.green) qCritical() << "Green is out of bounds"; - if (qBound(-255.0, adjusts.blue, 255.0) != adjusts.blue) qCritical() << "Green is out of bounds"; - if (qBound(-255.0, adjusts.saturation, 255.0) != adjusts.saturation) qCritical() << "Saturation is out of bounds"; - if (qBound(-255.0, adjusts.value, 255.0) != adjusts.value) qCritical() << "Value is out of bounds"; - if (qBound(-255.0, adjusts.alpha, 255.0) != adjusts.alpha) qCritical() << "Alpha is out of bounds"; + if (qBound(-360.0, adjusts.hue, 360.0) != adjusts.hue) { + qCritical() << "Hue is out of bounds"; + } + if (qBound(-255.0, adjusts.red, 255.0) != adjusts.red) { + qCritical() << "Red is out of bounds"; + } + if (qBound(-255.0, adjusts.green, 255.0) != adjusts.green) { + qCritical() << "Green is out of bounds"; + } + if (qBound(-255.0, adjusts.blue, 255.0) != adjusts.blue) { + qCritical() << "Green is out of bounds"; + } + if (qBound(-255.0, adjusts.saturation, 255.0) != adjusts.saturation) { + qCritical() << "Saturation is out of bounds"; + } + if (qBound(-255.0, adjusts.value, 255.0) != adjusts.value) { + qCritical() << "Value is out of bounds"; + } + if (qBound(-255.0, adjusts.alpha, 255.0) != adjusts.alpha) { + qCritical() << "Alpha is out of bounds"; + } auto copy = color; if (adjusts.alpha) { copy.setAlpha(adjusts.alpha); } if (adjusts.red || adjusts.green || adjusts.blue) { copy.setRed(copy.red() + adjusts.red); copy.setGreen(copy.green() + adjusts.green); copy.setBlue(copy.blue() + adjusts.blue); } else if (adjusts.hue || adjusts.saturation || adjusts.value) { copy.setHsl( - std::fmod(copy.hue()+adjusts.hue, 360.0), - copy.saturation()+adjusts.saturation, - copy.value()+adjusts.value, + std::fmod(copy.hue() + adjusts.hue, 360.0), + copy.saturation() + adjusts.saturation, + copy.value() + adjusts.value, copy.alpha() ); } return copy; } -QColor ColorUtils::scaleColor(QColor color, QJSValue adjustments) +QColor ColorUtils::scaleColor(const QColor& color, const QJSValue &adjustments) { auto adjusts = parseAdjustments(adjustments); auto copy = color; - if (qBound(-100.0, adjusts.red, 100.00) != adjusts.red) qCritical() << "Red is out of bounds"; - if (qBound(-100.0, adjusts.green, 100.00) != adjusts.green) qCritical() << "Green is out of bounds"; - if (qBound(-100.0, adjusts.blue, 100.00) != adjusts.blue) qCritical() << "Blue is out of bounds"; - if (qBound(-100.0, adjusts.saturation, 100.00) != adjusts.saturation) qCritical() << "Saturation is out of bounds"; - if (qBound(-100.0, adjusts.value, 100.00) != adjusts.value) qCritical() << "Value is out of bounds"; - if (qBound(-100.0, adjusts.alpha, 100.00) != adjusts.alpha) qCritical() << "Alpha is out of bounds"; + if (qBound(-100.0, adjusts.red, 100.00) != adjusts.red) { + qCritical() << "Red is out of bounds"; + } + if (qBound(-100.0, adjusts.green, 100.00) != adjusts.green) { + qCritical() << "Green is out of bounds"; + } + if (qBound(-100.0, adjusts.blue, 100.00) != adjusts.blue) { + qCritical() << "Blue is out of bounds"; + } + if (qBound(-100.0, adjusts.saturation, 100.00) != adjusts.saturation) { + qCritical() << "Saturation is out of bounds"; + } + if (qBound(-100.0, adjusts.value, 100.00) != adjusts.value) { + qCritical() << "Value is out of bounds"; + } + if (qBound(-100.0, adjusts.alpha, 100.00) != adjusts.alpha) { + qCritical() << "Alpha is out of bounds"; + } - if (adjusts.hue != 0) qCritical() << "Hue cannot be scaled"; + if (adjusts.hue != 0) { + qCritical() << "Hue cannot be scaled"; + } auto shiftToAverage = [](double current, double factor) { - auto scale = qBound(-100.0, factor, 100.0)/100; + auto scale = qBound(-100.0, factor, 100.0) / 100; return current + (scale > 0 ? 255 - current : current) * scale; }; if (adjusts.red || adjusts.green || adjusts.blue) { copy.setRed(qBound(0.0, shiftToAverage(copy.red(), adjusts.red), 255.0)); copy.setGreen(qBound(0.0, shiftToAverage(copy.green(), adjusts.green), 255.0)); copy.setBlue(qBound(0.0, shiftToAverage(copy.blue(), adjusts.blue), 255.0)); } else { copy.setHsl( copy.hue(), qBound(0.0, shiftToAverage(copy.saturation(), adjusts.saturation), 255.0), qBound(0.0, shiftToAverage(copy.value(), adjusts.value), 255.0), qBound(0.0, shiftToAverage(copy.alpha(), adjusts.alpha), 255.0) ); } return copy; } QColor ColorUtils::tintWithAlpha(const QColor &targetColor, const QColor &tintColor, double alpha) { qreal tintAlpha = tintColor.alphaF() * alpha; qreal inverseAlpha = 1.0 - tintAlpha; if (qFuzzyCompare(tintAlpha, 1.0)) { return tintColor; } else if (qFuzzyIsNull(tintAlpha)) { return targetColor; } return QColor::fromRgbF( tintColor.redF() * tintAlpha + targetColor.redF() * inverseAlpha, tintColor.greenF() * tintAlpha + targetColor.greenF() * inverseAlpha, tintColor.blueF() * tintAlpha + targetColor.blueF() * inverseAlpha, tintAlpha + inverseAlpha * targetColor.alphaF() ); } diff --git a/src/colorutils.h b/src/colorutils.h index e1d8446a..8d8914cd 100644 --- a/src/colorutils.h +++ b/src/colorutils.h @@ -1,180 +1,180 @@ /* * SPDX-FileCopyrightText: 2020 Carson Black * * SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include #include #include #include /** * Utilities for processing items to obtain colors and information useful for * UIs that need to adjust to variable elements. */ class ColorUtils : public QObject { Q_OBJECT public: /** * Describes the contrast of an item. */ enum Brightness { Dark, /**< The item is dark and requires a light foreground color to achieve readable contrast. */ Light, /**< The item is light and requires a dark foreground color to achieve readable contrast. */ }; Q_ENUM(Brightness) explicit ColorUtils(QObject* parent = nullptr); /** * Returns whether a color is bright or dark. * * @code{.qml} * import QtQuick 2.0 * import org.kde.kirigami 2.12 as Kirigami * * Kirigami.Heading { * text: { * if (Kirigami.ColorUtils.brightnessForColor("pink") == Kirigami.ColorUtils.Light) { * return "The color is light" * } else { * return "The color is dark" * } * } * } * @endcode * * @since 5.69 * @since org.kde.kirigami 2.12 */ - Q_INVOKABLE ColorUtils::Brightness brightnessForColor(QColor color); + Q_INVOKABLE ColorUtils::Brightness brightnessForColor(const QColor &color); /** * Returns the result of overlaying the foreground color on the background * color. * * @param foreground The color to overlay on the background. * * @param background The color to overlay the foreground on. * * @code{.qml} * import QtQuick 2.0 * import org.kde.kirigami 2.12 as Kirigami * * Rectangle { * color: Kirigami.ColorUtils.alphaBlend(Qt.rgba(0, 0, 0, 0.5), Qt.rgba(1, 1, 1, 1)) * } * @endcode * * @since 5.69 * @since org.kde.kirigami 2.12 */ - Q_INVOKABLE QColor alphaBlend(QColor foreground, QColor background); + Q_INVOKABLE QColor alphaBlend(const QColor &foreground, const QColor &background); /** * Returns a linearly interpolated color between color one and color two. * * @param one The color to linearly interpolate from. * * @param two The color to linearly interpolate to. * * @param balance The balance between the two colors. 0.0 will return the * first color, 1.0 will return the second color. Values beyond these bounds * are valid, and will result in extrapolation. * * @code{.qml} * import QtQuick 2.0 * import org.kde.kirigami 2.12 as Kirigami * * Rectangle { * color: Kirigami.ColorUtils.linearInterpolation("black", "white", 0.5) * } * @endcode * * @since 5.69 * @since org.kde.kirigami 2.12 */ - Q_INVOKABLE QColor linearInterpolation(QColor one, QColor two, double balance); + Q_INVOKABLE QColor linearInterpolation(const QColor &one, const QColor &two, double balance); /** * Increases or decreases the properties of `color` by fixed amounts. * * @param color The color to adjust. * * @param adjustments The adjustments to apply to the color. * * @note `value` and `lightness` are aliases for the same value. * * @code{.js} * { * red: null, // Range: -255 to 255 * green: null, // Range: -255 to 255 * blue: null, // Range: -255 to 255 * hue: null, // Range: -360 to 360 * saturation: null, // Range: -255 to 255 * value: null // Range: -255 to 255 * lightness: null, // Range: -255 to 255 * alpha: null, // Range: -255 to 255 * } * @endcode * * @warning It is an error to adjust both RGB and HSL properties. * * @since 5.69 * @since org.kde.kirigami 2.12 */ - Q_INVOKABLE QColor adjustColor(QColor color, QJSValue adjustments); + Q_INVOKABLE QColor adjustColor(const QColor &color, const QJSValue &adjustments); /** * Smoothly scales colors. * * @param color The color to adjust. * * @param adjustments The adjustments to apply to the color. Each value must * be between `-100.0` and `100.0`. This indicates how far the property should * be scaled from its original to the maximum if positive or to the minumum if * negative. * * @note `value` and `lightness` are aliases for the same value. * * @code{.js} * { * red: null * green: null * blue: null * saturation: null * lightness: null * value: null * alpha: null * } * @endcode * * @warning It is an error to scale both RGB and HSL properties. * * @since 5.69 * @since org.kde.kirigami 2.12 */ - Q_INVOKABLE QColor scaleColor(QColor color, QJSValue adjustments); + Q_INVOKABLE QColor scaleColor(const QColor &color, const QJSValue &adjustments); /** * Tint a color using a separate alpha value. * * This does the same as Qt.tint() except that rather than using the tint * color's alpha value, it uses a separate value that gets multiplied with * the tint color's alpha. This avoids needing to create a new color just to * adjust an alpha value. * * \param targetColor The color to tint. * \param tintColor The color to tint with. * \param alpha The amount of tinting to apply. * * \return The tinted color. * * \sa Qt.tint() */ Q_INVOKABLE QColor tintWithAlpha(const QColor &targetColor, const QColor &tintColor, double alpha); };