diff --git a/examples/imagecolorstest.qml b/examples/imagecolorstest.qml new file mode 100644 --- /dev/null +++ b/examples/imagecolorstest.qml @@ -0,0 +1,152 @@ + +import QtQuick 2.12 +import QtQuick.Layouts 1.4 +import QtQuick.Controls 2.12 as Controls +import org.kde.kirigami 2.13 as Kirigami + +RowLayout { + id: root + width: 500 + height: 500 + + property var icons: ["desktop", "firefox", "vlc", "blender", "applications-games", "blinken", "adjustlevels", "adjustrgb", "cuttlefish", "folder-games", "applications-network", "multimedia-player", "applications-utilities", "accessories-dictionary", "calligraflow", "calligrakrita", "view-left-close","calligraauthor"] + property int i + + Kirigami.ImageColors { + id: palette + source: icon + } + Kirigami.ImageColors { + id: imgPalette + source: image + } + + ColumnLayout { + Rectangle { + Layout.preferredWidth: 200 + Layout.preferredHeight: 200 + z: -1 + color: palette.dominantContrast + Kirigami.Icon { + id: icon + anchors.centerIn: parent + width: 128 + height: 128 + source: "desktop" + } + } + Rectangle { + Layout.preferredWidth: 30 + Layout.preferredHeight: 30 + color: palette.average + } + Controls.Button { + text: "Next" + onClicked: { + i = (i+1)%icons.length + icon.source = icons[i] + palette.update() + } + } + + Repeater { + model: palette.palette + delegate: RowLayout { + Layout.fillWidth: true + Rectangle { + implicitWidth: 10 + 300 * modelData.ratio + implicitHeight: 30 + color: modelData.color + } + Item { + Layout.fillWidth: true + } + Rectangle { + color: modelData.contrastColor + implicitWidth: 30 + implicitHeight: 30 + } + } + } + } + Item { + Layout.preferredWidth: 500 + Layout.preferredHeight: 500/(image.sourceSize.width/image.sourceSize.height) + Image { + id: image + source: "https://source.unsplash.com/random" + anchors.fill: parent + onStatusChanged: imgPalette.update() + } + ColumnLayout { + Controls.Button { + text: "Update" + onClicked: { + image.source = "https://source.unsplash.com/random#" + (new Date()).getMilliseconds() + } + } + Repeater { + model: imgPalette.palette + delegate: RowLayout { + Layout.fillWidth: true + Rectangle { + implicitWidth: 10 + 300 * modelData.ratio + implicitHeight: 30 + color: modelData.color + } + Item { + Layout.fillWidth: true + } + Rectangle { + color: modelData.contrastColor + implicitWidth: 30 + implicitHeight: 30 + } + } + } + } + Item { + width: 300 + height: 150 + Kirigami.Theme.backgroundColor: imgPalette.background + Kirigami.Theme.textColor: imgPalette.foreground + Kirigami.Theme.highlightColor: imgPalette.highlight + + anchors { + bottom: parent.bottom + right: parent.right + } + + Rectangle { + anchors.fill: parent + opacity: 0.8 + color: Kirigami.Theme.backgroundColor + } + ColumnLayout { + anchors.centerIn: parent + RowLayout { + Rectangle { + Layout.alignment: Qt.AlignCenter + implicitWidth: 10 + implicitHeight: 10 + color: Kirigami.Theme.highlightColor + } + Controls.Label { + text: "Lorem Ipsum dolor sit amet" + color: Kirigami.Theme.textColor + } + } + RowLayout { + Controls.TextField { + Kirigami.Theme.inherit: true + text: "text" + } + Controls.Button { + Kirigami.Theme.inherit: true + text: "Ok" + } + } + } + } + } +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,7 @@ settings.cpp formlayoutattached.cpp pagepool.cpp + imagecolors.cpp scenepositionattached.cpp mnemonicattached.cpp wheelhandler.cpp diff --git a/src/colorutils.h b/src/colorutils.h --- a/src/colorutils.h +++ b/src/colorutils.h @@ -177,4 +177,23 @@ * \sa Qt.tint() */ Q_INVOKABLE QColor tintWithAlpha(const QColor &targetColor, const QColor &tintColor, double alpha); + + /** + * Returns the CIELAB chroma of the given color. + * + * CIELAB chroma may give a better quantification of how vibrant a color is compared to HSV saturation. + * + * \sa https://en.wikipedia.org/wiki/Colorfulness + * \sa https://en.wikipedia.org/wiki/CIELAB_color_space + */ + Q_INVOKABLE static qreal chroma(const QColor &color); + + struct LabColor { + qreal l = 0; + qreal a = 0; + qreal b = 0; + }; + + // Not for QML, returns the comvertion from srgb of a QColor and Lab colorspace + static ColorUtils::LabColor colorToLab(const QColor &color); }; diff --git a/src/colorutils.cpp b/src/colorutils.cpp --- a/src/colorutils.cpp +++ b/src/colorutils.cpp @@ -210,3 +210,74 @@ tintAlpha + inverseAlpha * targetColor.alphaF() ); } + +ColorUtils::LabColor ColorUtils::colorToLab(const QColor &color) +{ + // http://wiki.nuaj.net/index.php/Color_Transforms#RGB_.E2.86.92_XYZ + // First: convert to XYZ + qreal r = color.redF(); + qreal g = color.greenF(); + qreal b = color.blueF(); + + // Apply gamma correction (i.e. conversion to linear-space) + if (r > 0.04045) { + r = pow((r + 0.055 ) / 1.055, 2.4); + } else { + r = r / 12.92; + } + + if (g > 0.04045 ) { + g = pow((g + 0.055 ) / 1.055, 2.4); + } else { + g = g / 12.92; + } + + if (b > 0.04045) { + b = pow((b + 0.055 ) / 1.055, 2.4); + } else { + b = b / 12.92; + } + + // Observer. = 2°, Illuminant = D65 + qreal x = r * 0.4124 + g * 0.3576 + b * 0.1805; + qreal y = r * 0.2126 + g * 0.7152 + b * 0.0722; + qreal z = r * 0.0193 + g * 0.1192 + b * 0.9505; + + // Second: convert from XYZ to L*a*b + x = x / 0.95047; // Observer= 2°, Illuminant= D65 + y = y / 1.0; + z = z / 1.08883; + + if (x > 0.008856) { + x = pow(x, 1.0/3.0); + } else { + x = (7.787 * x) + (16.0 / 116.0); + } + + if (y > 0.008856) { + y = pow(y, 1.0/3.0); + } else { + y = (7.787 * y) + (16.0 / 116.0); + } + + if (z > 0.008856) { + z = pow(z, 1.0/3.0); + } else { + z = (7.787 * z) + (16.0 / 116.0); + } + + LabColor labColor; + labColor.l = (116 * y) - 16; + labColor.a = 500 * (x - y); + labColor.b = 200 * (y - z); + + return labColor; +} + +qreal ColorUtils::chroma(const QColor &color) +{ + LabColor labColor = colorToLab(color); + + // Chroma is hypotenuse of a and b + return sqrt(pow(labColor.a, 2) + pow(labColor.b, 2)); +} diff --git a/src/imagecolors.h b/src/imagecolors.h new file mode 100644 --- /dev/null +++ b/src/imagecolors.h @@ -0,0 +1,212 @@ +/* + * Copyright 2020 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. + */ + +#pragma once + +#include "colorutils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +class QTimer; + +struct ImageData { + struct colorStat { + QList colors; + QRgb centroid = 0; + qreal ratio = 0; + }; + + struct colorSet { + QColor average; + QColor text; + QColor background; + QColor highlight; + }; + + QList m_samples; + QList m_clusters; + QVariantList m_palette; + + bool m_darkPalette = true; + QColor m_dominant; + QColor m_dominantContrast; + QColor m_average; + QColor m_highlight; + + QColor m_closestToBlack; + QColor m_closestToWhite; +}; + +class ImageColors : public QObject +{ + Q_OBJECT + /** + * The source from which colors should be extracted from. + * + * `source` can be one of the following: + * * Item + * * QImage + * * QIcon + * * Icon name + * + * Note that an Item's color palette will only be extracted once unless you * call `update()`, regardless of how the item hanges. + */ + Q_PROPERTY(QVariant source READ source WRITE setSource NOTIFY sourceChanged) + + /** + * A list of colors and related information about then. + * + * Each list item has the following properties: + * * `color`: The color of the list item. + * * `ratio`: How dominant the color is in the source image. + * * `contrastingColor`: The color from the source image that's closest to the inverse of `color`. + * + * The list is sorted by `ratio`; the first element is the most + * dominant color in the source image and the last element is the + * least dominant color of the image. + * + * \note K-means clustering is used to extract these colors; see https://en.wikipedia.org/wiki/K-means_clustering. + */ + Q_PROPERTY(QVariantList palette READ palette NOTIFY paletteChanged) + + /** + * Information whether the palette is towards a light or dark color + * scheme, possible values are: + * * ColorUtils.Light + * * ColorUtils.Dark + */ + Q_PROPERTY(ColorUtils::Brightness paletteBrightness READ paletteBrightness NOTIFY paletteChanged) + + /** + * The average color of the source image. + */ + Q_PROPERTY(QColor average READ average NOTIFY paletteChanged) + + /** + * The dominant color of the source image. + * + * The dominant color of the image is the color of the largest + * cluster in the image. + * + * \sa https://en.wikipedia.org/wiki/K-means_clustering + */ + Q_PROPERTY(QColor dominant READ dominant NOTIFY paletteChanged) + + /** + * Suggested "contrasting" color to the dominant one. It's the color in the palette nearest to the negative of the dominant + */ + Q_PROPERTY(QColor dominantContrast READ dominantContrast NOTIFY paletteChanged) + + /** + * An accent color extracted from the source image. + * + * The accent color is the color cluster with the highest CIELAB + * chroma in the source image. + * + * \sa https://en.wikipedia.org/wiki/Colorfulness#Chroma + */ + Q_PROPERTY(QColor highlight READ highlight NOTIFY paletteChanged) + + /** + * A color suitable for rendering text and other foreground + * over the source image. + * + * On dark items, this will be the color closest to white in + * the image if it's light enough, or a bright gray otherwise. + * On light items, this will be the color closest to black in + * the image if it's dark enough, or a dark gray otherwise. + */ + Q_PROPERTY(QColor foreground READ foreground NOTIFY paletteChanged) + + /** + * A color suitable for rendering a background behind the + * source image. + * + * On dark items, this will be the color closest to black in the + * image if it's dark enough, or a dark gray otherwise. + * On light items, this will be the color closest to white + * in the image if it's light enough, or a bright gray otherwise. + */ + Q_PROPERTY(QColor background READ background NOTIFY paletteChanged) + + /** + * The lightest color of the source image. + */ + Q_PROPERTY(QColor closestToWhite READ closestToWhite NOTIFY paletteChanged) + + /** + * The darkest color of the source image. + */ + Q_PROPERTY(QColor closestToBlack READ closestToBlack NOTIFY paletteChanged) + +public: + explicit ImageColors(QObject* parent = nullptr); + ~ImageColors(); + + void setSource(const QVariant &source); + QVariant source() const; + + void setSourceImage(const QImage &image); + QImage sourceImage() const; + + void setSourceItem(QQuickItem *source); + QQuickItem *sourceItem() const; + + Q_INVOKABLE void update(); + + QVariantList palette() const; + ColorUtils::Brightness paletteBrightness() const; + QColor average() const; + QColor dominant() const; + QColor dominantContrast() const; + QColor highlight() const; + QColor foreground() const; + QColor background() const; + QColor closestToWhite() const; + QColor closestToBlack() const; + +Q_SIGNALS: + void sourceChanged(); + void paletteChanged(); + +private: + static inline void positionColor(QRgb rgb, QList &clusters); + static ImageData generatePalette(const QImage &sourceImage); + + // Arbitrary number that seems to work well + static const int s_minimumSquareDistance = 32000; + QPointer m_window; + QVariant m_source; + QPointer m_sourceItem; + QSharedPointer m_grabResult; + QImage m_sourceImage; + + + QTimer *m_imageSyncTimer; + + QFutureWatcher *m_futureImageData = nullptr; + ImageData m_imageData; +}; + diff --git a/src/imagecolors.cpp b/src/imagecolors.cpp new file mode 100644 --- /dev/null +++ b/src/imagecolors.cpp @@ -0,0 +1,445 @@ +/* + * Copyright 2020 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. + */ + +#include "imagecolors.h" + +#include +#include +#include +#include + +#include + + +ImageColors::ImageColors(QObject *parent) + : QObject(parent) +{ + m_imageSyncTimer = new QTimer(this); + m_imageSyncTimer->setSingleShot(true); + m_imageSyncTimer->setInterval(100); + /* connect(m_imageSyncTimer, &QTimer::timeout, this, [this]() { + generatePalette(); + });*/ +} + +ImageColors::~ImageColors() +{} + +void ImageColors::setSource(const QVariant &source) +{ + if (source.canConvert()) { + setSourceItem(source.value()); + } else if (source.canConvert()) { + setSourceImage(source.value()); + } else if (source.canConvert()) { + setSourceImage(source.value().pixmap(128, 128).toImage()); + } else if (source.canConvert()) { + setSourceImage(QIcon::fromTheme(source.toString()).pixmap(128, 128).toImage()); + } else { + return; + } + + m_source = source; + emit sourceChanged(); +} + +QVariant ImageColors::source() const +{ + return m_source; +} + +void ImageColors::setSourceImage(const QImage &image) +{ + if (m_window) { + disconnect(m_window.data(), nullptr, this, nullptr); + } + if (m_sourceItem) { + disconnect(m_sourceItem.data(), nullptr, this, nullptr); + } + if (m_grabResult) { + disconnect(m_grabResult.data(), nullptr, this, nullptr); + m_grabResult.clear(); + } + + m_sourceItem.clear(); + + if (m_sourceImage.isNull()) { + m_sourceImage = image; + update(); + } else { + m_sourceImage = image; + update(); + } +} + +QImage ImageColors::sourceImage() const +{ + return m_sourceImage; +} + +void ImageColors::setSourceItem(QQuickItem *source) +{ + if (m_sourceItem == source) { + return; + } + + if (m_window) { + disconnect(m_window.data(), nullptr, this, nullptr); + } + if (m_sourceItem) { + disconnect(m_sourceItem, nullptr, this, nullptr); + } + m_sourceItem = source; + update(); + + if (m_sourceItem) { + auto syncWindow = [this] () { + if (m_window) { + disconnect(m_window.data(), nullptr, this, nullptr); + } + m_window = m_sourceItem->window(); + if (m_window) { + connect(m_window, &QWindow::visibleChanged, + this, &ImageColors::update); + } + }; + + connect(m_sourceItem, &QQuickItem::windowChanged, + this, syncWindow); + syncWindow(); + } +} + +QQuickItem *ImageColors::sourceItem() const +{ + return m_sourceItem; +} + +void ImageColors::update() +{ + if (m_futureImageData) { + m_futureImageData->cancel(); + m_futureImageData->deleteLater(); + } + auto runUpdate = [this]() { + QFuture future = QtConcurrent::run([this](){return generatePalette(m_sourceImage);}); + m_futureImageData = new QFutureWatcher(this); + connect(m_futureImageData, &QFutureWatcher::finished, + this, [this] () { + m_imageData = m_futureImageData->future().result(); + m_futureImageData->deleteLater(); + m_futureImageData = nullptr; + + emit paletteChanged(); + }); + m_futureImageData->setFuture(future); + }; + + if (!m_sourceItem || !m_window) { + if (!m_sourceImage.isNull()) { + runUpdate(); + } + return; + } + + if (m_grabResult) { + disconnect(m_grabResult.data(), nullptr, this, nullptr); + m_grabResult.clear(); + } + + m_grabResult = m_sourceItem->grabToImage(QSize(128, 128)); + + if (m_grabResult) { + connect(m_grabResult.data(), &QQuickItemGrabResult::ready, this, [this, runUpdate]() { + m_sourceImage = m_grabResult->image(); + m_grabResult.clear(); + runUpdate(); + }); + } +} + +inline int squareDistance(QRgb color1, QRgb color2) +{ + // https://en.wikipedia.org/wiki/Color_difference + // Using RGB distance for performance, as CIEDE2000 istoo complicated + if (qRed(color1) - qRed(color2) < 128) { + return 2 * pow(qRed(color1) - qRed(color2), 2) + + 4 * pow(qGreen(color1) - qGreen(color2), 2) + + 3 * pow(qBlue(color1) - qBlue(color2), 2); + } else { + return 3 * pow(qRed(color1) - qRed(color2), 2) + + 4 * pow(qGreen(color1) - qGreen(color2), 2) + + 2 * pow(qBlue(color1) - qBlue(color2), 2); + } +} + +void ImageColors::positionColor(QRgb rgb, QList &clusters) +{ + for (auto &stat : clusters) { + if (squareDistance(rgb, stat.centroid) < s_minimumSquareDistance) { + stat.colors.append(rgb); + return; + } + } + + ImageData::colorStat stat; + stat.colors.append(rgb); + stat.centroid = rgb; + clusters << stat; +} + +ImageData ImageColors::generatePalette(const QImage &sourceImage) +{ + ImageData imageData; + + if (sourceImage.isNull() || sourceImage.width() == 0) { + return imageData; + } + + imageData.m_clusters.clear(); + imageData.m_samples.clear(); + + QColor sampleColor; + int r = 0; + int g = 0; + int b = 0; + int c = 0; + for (int x = 0; x < sourceImage.width(); ++x) { + for (int y = 0; y < sourceImage.height(); ++y) { + sampleColor = sourceImage.pixelColor(x, y); + if (sampleColor.alpha() == 0) { + continue; + } + QRgb rgb = sampleColor.rgb(); + c++; + r += qRed(rgb); + g += qGreen(rgb); + b += qBlue(rgb); + imageData.m_samples << rgb; + positionColor(rgb, imageData.m_clusters); + } + } + + if (imageData.m_samples.isEmpty()) { + return imageData; + } + + imageData.m_average = QColor(r/c, g/c, b/c, 255); + + for (int iteration = 0; iteration < 5; ++iteration) { + for (auto &stat : imageData.m_clusters) { + r = 0; + g = 0; + b = 0; + c = 0; + + for (auto color : stat.colors) { + c++; + r += qRed(color); + g += qGreen(color); + b += qBlue(color); + } + r = r / c; + g = g / c; + b = b / c; + stat.centroid = qRgb(r, g, b); + stat.ratio = qreal(stat.colors.count()) / qreal(imageData.m_samples.count()); + stat.colors = QList({stat.centroid}); + } + + for (auto color : imageData.m_samples) { + positionColor(color, imageData.m_clusters); + } + } + + std::sort(imageData.m_clusters.begin(), imageData.m_clusters.end(), [](const ImageData::colorStat &a, const ImageData::colorStat &b) { + return a.colors.size() > b.colors.size(); + }); + + // compress blocks that became too similar + auto sourceIt = imageData.m_clusters.end(); + QList::iterator> itemsToDelete; + while (sourceIt != imageData.m_clusters.begin()) { + sourceIt--; + for (auto destIt = imageData.m_clusters.begin(); destIt != imageData.m_clusters.end() && destIt != sourceIt; destIt++) { + if (squareDistance((*sourceIt).centroid, (*destIt).centroid) < s_minimumSquareDistance) { + const qreal ratio = (*sourceIt).ratio / (*destIt).ratio; + const int r = ratio * qreal(qRed((*sourceIt).centroid)) + + (1 - ratio) * qreal(qRed((*destIt).centroid)); + const int g = ratio * qreal(qGreen((*sourceIt).centroid)) + + (1 - ratio) * qreal(qGreen((*destIt).centroid)); + const int b = ratio * qreal(qBlue((*sourceIt).centroid)) + + (1 - ratio) * qreal(qBlue((*destIt).centroid)); + (*destIt).ratio += (*sourceIt).ratio; + (*destIt).centroid = qRgb(r, g, b); + itemsToDelete << sourceIt; + break; + } + } + } + for (const auto &i : itemsToDelete) { + imageData.m_clusters.erase(i); + } + + imageData.m_highlight = QColor(); + imageData.m_dominant = QColor(imageData.m_clusters.first().centroid); + imageData.m_closestToBlack = Qt::white; + imageData.m_closestToWhite = Qt::black; + + imageData.m_palette.clear(); + + bool first = true; + + for (const auto &stat : imageData.m_clusters) { + QVariantMap entry; + const QColor color(stat.centroid); + entry[QStringLiteral("color")] = color; + entry[QStringLiteral("ratio")] = stat.ratio; + + QColor contrast = QColor(255 - color.red(), 255 - color.green(), 255 - color.blue()); + contrast.setHsl(contrast.hslHue(), + contrast.hslSaturation(), + 128 + (128 - contrast.lightness())); + QColor tempContrast; + int minimumDistance = 4681800; //max distance: 4*3*2*3*255*255 + for (const auto &stat : imageData.m_clusters) { + const int distance = squareDistance(contrast.rgb(), stat.centroid); + + if (distance < minimumDistance) { + tempContrast = QColor(stat.centroid); + minimumDistance = distance; + } + } + + + if (imageData.m_clusters.size() <= 3) { + if (qGray(imageData.m_dominant.rgb()) < 120) { + contrast = QColor(230, 230, 230); + } else { + contrast = QColor(20, 20, 20); + } + // TODO: replace m_clusters.size() > 3 with entropy calculation + } else if (squareDistance(contrast.rgb(), tempContrast.rgb()) < s_minimumSquareDistance * 1.5) { + contrast = tempContrast; + } else { + contrast = tempContrast; + contrast.setHsl(contrast.hslHue(), + contrast.hslSaturation(), + contrast.lightness() > 128 + ? qMin(contrast.lightness() + 20, 255) + : qMax(0, contrast.lightness() - 20)); + } + + entry[QStringLiteral("contrastColor")] = contrast; + + if (first) { + imageData.m_dominantContrast = contrast; + imageData.m_dominant = color; + } + first = false; + + + if (!imageData.m_highlight.isValid() || ColorUtils::chroma(color) > ColorUtils::chroma(imageData.m_highlight)) { + imageData.m_highlight = color; + } + + if (qGray(color.rgb()) > qGray(imageData.m_closestToWhite.rgb())) { + imageData.m_closestToWhite = color; + } + if (qGray(color.rgb()) < qGray(imageData.m_closestToBlack.rgb())) { + imageData.m_closestToBlack = color; + } + imageData.m_palette << entry; + } + + return imageData; +} + +QVariantList ImageColors::palette() const +{ + return m_imageData.m_palette; +} + +ColorUtils::Brightness ImageColors::paletteBrightness() const +{ + return qGray(m_imageData.m_dominant.rgb()) < 128 + ? ColorUtils::Dark + : ColorUtils::Light; +} + +QColor ImageColors::average() const +{ + return m_imageData.m_average; +} + +QColor ImageColors::dominant() const +{ + return m_imageData.m_dominant; +} + +QColor ImageColors::dominantContrast() const +{ + return m_imageData.m_dominantContrast; +} + +QColor ImageColors::foreground() const +{ + if (paletteBrightness() == ColorUtils::Dark) { + if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) { + return QColor(230, 230, 230); + } + return m_imageData.m_closestToWhite; + } else { + if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) { + return QColor(20, 20, 20); + } + return m_imageData.m_closestToBlack; + } +} + +QColor ImageColors::background() const +{ + if (paletteBrightness() == ColorUtils::Dark) { + if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) { + return QColor(20, 20, 20); + } + return m_imageData.m_closestToBlack; + } else { + if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) { + return QColor(230, 230, 230); + } + return m_imageData.m_closestToWhite; + } +} + +QColor ImageColors::highlight() const +{ + return m_imageData.m_highlight; +} + +QColor ImageColors::closestToWhite() const +{ + return m_imageData.m_closestToWhite; +} + +QColor ImageColors::closestToBlack() const +{ + return m_imageData.m_closestToBlack; +} + +#include "moc_imagecolors.cpp" diff --git a/src/kirigamiplugin.cpp b/src/kirigamiplugin.cpp --- a/src/kirigamiplugin.cpp +++ b/src/kirigamiplugin.cpp @@ -21,6 +21,7 @@ #include "shadowedtexture.h" #include "colorutils.h" #include "pagerouter.h" +#include "imagecolors.h" #include #include @@ -254,6 +255,9 @@ qmlRegisterType(uri, 2, 12, "PageRoute"); qmlRegisterUncreatableType(uri, 2, 12, "PageRouterAttached", QStringLiteral("PageRouterAttached cannot be created")); + // 2.13 + qmlRegisterType(uri, 2, 13, "ImageColors"); + qmlProtectModule(uri, 2); }