diff --git a/examples/imagecolorstest.qml b/examples/imagecolorstest.qml new file mode 100644 index 00000000..147963c6 --- /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 index 7668637d..d56af1f1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,121 +1,122 @@ project(kirigami) if (BUILD_SHARED_LIBS) ecm_create_qm_loader(kirigami_QM_LOADER libkirigami2plugin_qt) else() set(KIRIGAMI_STATIC_FILES libkirigami/basictheme.cpp libkirigami/platformtheme.cpp libkirigami/tabletmodewatcher.cpp libkirigami/kirigamipluginfactory.cpp) endif() include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libkirigami ${CMAKE_CURRENT_BINARY_DIR}/libkirigami) set(kirigami_SRCS kirigamiplugin.cpp columnview.cpp enums.cpp delegaterecycler.cpp icon.cpp settings.cpp formlayoutattached.cpp pagepool.cpp + imagecolors.cpp scenepositionattached.cpp mnemonicattached.cpp wheelhandler.cpp shadowedrectangle.cpp shadowedtexture.cpp colorutils.cpp pagerouter.cpp scenegraph/shadowedrectanglenode.cpp scenegraph/shadowedrectanglematerial.cpp scenegraph/shadowedborderrectanglematerial.cpp scenegraph/paintedrectangleitem.cpp scenegraph/shadowedtexturenode.cpp scenegraph/shadowedtexturematerial.cpp scenegraph/shadowedbordertexturematerial.cpp ${kirigami_QM_LOADER} ${KIRIGAMI_STATIC_FILES} ) qt5_add_resources(SHADERS scenegraph/shaders/shaders.qrc) add_subdirectory(libkirigami) if(NOT BUILD_SHARED_LIBS) # `rcc` is a bit dumb and isn't designed to use auto generated files, to # avoid poluting the source directory, use absolute paths set(kirigami_QML_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../) # First, pre-process the QRC to add the files associated with the right Qt # version. configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/../kirigami.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/../kirigami.qrc @ONLY ) # When using the static library, all QML files need to be shipped within the # .a file. qt5_add_resources( RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/../kirigami.qrc ) if (UNIX AND NOT ANDROID AND NOT(APPLE) AND NOT(DISABLE_DBUS)) qt5_add_dbus_interface(kirigami_SRCS libkirigami/org.kde.KWin.TabletModeManager.xml tabletmodemanager_interface) endif() endif() add_library(kirigamiplugin ${kirigami_SRCS} ${RESOURCES} ${SHADERS}) if(NOT BUILD_SHARED_LIBS) SET_TARGET_PROPERTIES(kirigamiplugin PROPERTIES AUTOMOC_MOC_OPTIONS -Muri=org.kde.kirigami) if (UNIX AND NOT ANDROID AND NOT(APPLE) AND NOT(DISABLE_DBUS)) set(Kirigami_EXTRA_LIBS Qt5::DBus) else() set(Kirigami_EXTRA_LIBS "") endif() else() set(Kirigami_EXTRA_LIBS KF5::Kirigami2) endif() target_link_libraries(kirigamiplugin PUBLIC Qt5::Core PRIVATE ${Kirigami_EXTRA_LIBS} Qt5::Qml Qt5::Quick Qt5::QuickControls2 ) if (BUILD_SHARED_LIBS) add_custom_target(copy_to_bin ALL COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/bin/org/kde/kirigami.2/ COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/controls ${CMAKE_BINARY_DIR}/bin/org/kde/kirigami.2/ COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/styles ${CMAKE_BINARY_DIR}/bin/org/kde/kirigami.2/styles COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_BINARY_DIR}/bin/org/kde/kirigami.2/ ) install(DIRECTORY controls/ DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2) if (DESKTOP_ENABLED) install(DIRECTORY styles/org.kde.desktop DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2/styles) endif() install(DIRECTORY styles/Material DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2/styles) install(FILES ${platformspecific} DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2) include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME Kirigami2 LIB_NAME KF5Kirigami2 DEPS "core qml quick svg" FILENAME_VAR PRI_FILENAME ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) endif() install(TARGETS kirigamiplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirigami.2) diff --git a/src/colorutils.cpp b/src/colorutils.cpp index 3da2e290..21378f87 100644 --- a/src/colorutils.cpp +++ b/src/colorutils.cpp @@ -1,246 +1,317 @@ /* * 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(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(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 (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(const QColor &one, const QColor &two, double balance) { 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); } 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(const QJSValue &value) { ParsedAdjustments parsed; 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::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 (const auto &item : items) { auto val = checkProperty(value, item.first); 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(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"; } 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, copy.alpha() ); } return copy; } 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 (adjusts.hue != 0) { qCritical() << "Hue cannot be scaled"; } auto shiftToAverage = [](double current, double factor) { 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() ); } + +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/colorutils.h b/src/colorutils.h index 8d8914cd..52995a98 100644 --- a/src/colorutils.h +++ b/src/colorutils.h @@ -1,180 +1,199 @@ /* * 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(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(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(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(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(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); + + /** + * 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/imagecolors.cpp b/src/imagecolors.cpp new file mode 100644 index 00000000..74461c84 --- /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/imagecolors.h b/src/imagecolors.h new file mode 100644 index 00000000..137956cb --- /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/kirigamiplugin.cpp b/src/kirigamiplugin.cpp index 88e7358b..8b120d25 100644 --- a/src/kirigamiplugin.cpp +++ b/src/kirigamiplugin.cpp @@ -1,267 +1,271 @@ /* * SPDX-FileCopyrightText: 2009 Alan Alpert * SPDX-FileCopyrightText: 2010 Ménard Alexis * SPDX-FileCopyrightText: 2010 Marco Martin * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "kirigamiplugin.h" #include "columnview.h" #include "enums.h" #include "icon.h" #include "settings.h" #include "formlayoutattached.h" #include "mnemonicattached.h" #include "delegaterecycler.h" #include "pagepool.h" #include "scenepositionattached.h" #include "wheelhandler.h" #include "shadowedrectangle.h" #include "shadowedtexture.h" #include "colorutils.h" #include "pagerouter.h" +#include "imagecolors.h" #include #include #include #include #include #include #include "libkirigami/platformtheme.h" static QString s_selectedStyle; //Q_INIT_RESOURCE(kirigami); #ifdef KIRIGAMI_BUILD_TYPE_STATIC #include #endif class CopyHelperPrivate : public QObject { Q_OBJECT public: Q_INVOKABLE static void copyTextToClipboard(const QString& text) { qGuiApp->clipboard()->setText(text); } }; // we can't do this in the plugin object directly, as that can live in a different thread // and event filters are only allowed in the same thread as the filtered object class LanguageChangeEventFilter : public QObject { Q_OBJECT public: bool eventFilter(QObject *receiver, QEvent *event) override { if (event->type() == QEvent::LanguageChange && receiver == QCoreApplication::instance()) { emit languageChangeEvent(); } return QObject::eventFilter(receiver, event); } Q_SIGNALS: void languageChangeEvent(); }; KirigamiPlugin::KirigamiPlugin(QObject *parent) : QQmlExtensionPlugin(parent) { auto filter = new LanguageChangeEventFilter; filter->moveToThread(QCoreApplication::instance()->thread()); QCoreApplication::instance()->installEventFilter(filter); connect(filter, &LanguageChangeEventFilter::languageChangeEvent, this, &KirigamiPlugin::languageChangeEvent); } QUrl KirigamiPlugin::componentUrl(const QString &fileName) const { for (const QString &style : qAsConst(m_stylesFallbackChain)) { const QString candidate = QStringLiteral("styles/") + style + QLatin1Char('/') + fileName; if (QFile::exists(resolveFilePath(candidate))) { #ifdef KIRIGAMI_BUILD_TYPE_STATIC return QUrl(QStringLiteral("qrc:/org/kde/kirigami/styles/") + style + QLatin1Char('/') + fileName); #else return QUrl(resolveFileUrl(candidate)); #endif } } #ifdef KIRIGAMI_BUILD_TYPE_STATIC return QUrl(QStringLiteral("qrc:/org/kde/kirigami/") + fileName); #else return QUrl(resolveFileUrl(fileName)); #endif } void KirigamiPlugin::registerTypes(const char *uri) { #if defined(Q_OS_ANDROID) && QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QResource::registerResource(QStringLiteral("assets:/android_rcc_bundle.rcc")); #endif Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.kirigami")); const QString style = QQuickStyle::name(); if (QIcon::themeName().isEmpty() && !qEnvironmentVariableIsSet("XDG_CURRENT_DESKTOP")) { QIcon::setThemeSearchPaths({resolveFilePath(QStringLiteral(".")), QStringLiteral(":/icons")}); QIcon::setThemeName(QStringLiteral("breeze-internal")); } #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) //org.kde.desktop.plasma is a couple of files that fall back to desktop by purpose if ((style.isEmpty() || style == QStringLiteral("org.kde.desktop.plasma")) && QFile::exists(resolveFilePath(QStringLiteral("/styles/org.kde.desktop")))) { m_stylesFallbackChain.prepend(QStringLiteral("org.kde.desktop")); } #elif defined(Q_OS_ANDROID) if (!m_stylesFallbackChain.contains(QLatin1String("Material"))) { m_stylesFallbackChain.prepend(QStringLiteral("Material")); } #else // do we have an iOS specific style? if (!m_stylesFallbackChain.contains(QLatin1String("Material"))) { m_stylesFallbackChain.prepend(QStringLiteral("Material")); } #endif if (!style.isEmpty() && QFile::exists(resolveFilePath(QStringLiteral("/styles/") + style)) && !m_stylesFallbackChain.contains(style)) { m_stylesFallbackChain.prepend(style); //if we have plasma deps installed, use them for extra integration if (style == QStringLiteral("org.kde.desktop") && QFile::exists(resolveFilePath(QStringLiteral("/styles/org.kde.desktop.plasma")))) { m_stylesFallbackChain.prepend(QStringLiteral("org.kde.desktop.plasma")); } } else { #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) m_stylesFallbackChain.prepend(QStringLiteral("org.kde.desktop")); #endif } //At this point the fallback chain will be selected->org.kde.desktop->Fallback s_selectedStyle = m_stylesFallbackChain.first(); qmlRegisterSingletonType(uri, 2, 0, "Settings", [](QQmlEngine *e, QJSEngine*) -> QObject* { Settings *settings = Settings::self(); //singleton managed internally, qml should never delete it e->setObjectOwnership(settings, QQmlEngine::CppOwnership); settings->setStyle(s_selectedStyle); return settings; } ); qmlRegisterUncreatableType(uri, 2, 0, "ApplicationHeaderStyle", QStringLiteral("Cannot create objects of type ApplicationHeaderStyle")); //old legacy retrocompatible Theme qmlRegisterSingletonType(componentUrl(QStringLiteral("Theme.qml")), uri, 2, 0, "Theme"); qmlRegisterSingletonType(componentUrl(QStringLiteral("Units.qml")), uri, 2, 0, "Units"); qmlRegisterType(componentUrl(QStringLiteral("Action.qml")), uri, 2, 0, "Action"); qmlRegisterType(componentUrl(QStringLiteral("AbstractApplicationHeader.qml")), uri, 2, 0, "AbstractApplicationHeader"); qmlRegisterType(componentUrl(QStringLiteral("AbstractApplicationWindow.qml")), uri, 2, 0, "AbstractApplicationWindow"); qmlRegisterType(componentUrl(QStringLiteral("AbstractListItem.qml")), uri, 2, 0, "AbstractListItem"); qmlRegisterType(componentUrl(QStringLiteral("ApplicationHeader.qml")), uri, 2, 0, "ApplicationHeader"); qmlRegisterType(componentUrl(QStringLiteral("ToolBarApplicationHeader.qml")), uri, 2, 0, "ToolBarApplicationHeader"); qmlRegisterType(componentUrl(QStringLiteral("ApplicationWindow.qml")), uri, 2, 0, "ApplicationWindow"); qmlRegisterType(componentUrl(QStringLiteral("BasicListItem.qml")), uri, 2, 0, "BasicListItem"); qmlRegisterType(componentUrl(QStringLiteral("OverlayDrawer.qml")), uri, 2, 0, "OverlayDrawer"); qmlRegisterType(componentUrl(QStringLiteral("ContextDrawer.qml")), uri, 2, 0, "ContextDrawer"); qmlRegisterType(componentUrl(QStringLiteral("GlobalDrawer.qml")), uri, 2, 0, "GlobalDrawer"); qmlRegisterType(componentUrl(QStringLiteral("Heading.qml")), uri, 2, 0, "Heading"); qmlRegisterType(componentUrl(QStringLiteral("Separator.qml")), uri, 2, 0, "Separator"); qmlRegisterType(componentUrl(QStringLiteral("PageRow.qml")), uri, 2, 0, "PageRow"); qmlRegisterType(uri, 2, 0, "Icon"); qmlRegisterType(componentUrl(QStringLiteral("Label.qml")), uri, 2, 0, "Label"); //TODO: uncomment for 2.3 release //qmlRegisterTypeNotAvailable(uri, 2, 3, "Label", "Label type not supported anymore, use QtQuick.Controls.Label 2.0 instead"); qmlRegisterType(componentUrl(QStringLiteral("OverlaySheet.qml")), uri, 2, 0, "OverlaySheet"); qmlRegisterType(componentUrl(QStringLiteral("Page.qml")), uri, 2, 0, "Page"); qmlRegisterType(componentUrl(QStringLiteral("ScrollablePage.qml")), uri, 2, 0, "ScrollablePage"); qmlRegisterType(componentUrl(QStringLiteral("SplitDrawer.qml")), uri, 2, 0, "SplitDrawer"); qmlRegisterType(componentUrl(QStringLiteral("SwipeListItem.qml")), uri, 2, 0, "SwipeListItem"); //2.1 qmlRegisterType(componentUrl(QStringLiteral("AbstractItemViewHeader.qml")), uri, 2, 1, "AbstractItemViewHeader"); qmlRegisterType(componentUrl(QStringLiteral("ItemViewHeader.qml")), uri, 2, 1, "ItemViewHeader"); qmlRegisterType(componentUrl(QStringLiteral("AbstractApplicationItem.qml")), uri, 2, 1, "AbstractApplicationItem"); qmlRegisterType(componentUrl(QStringLiteral("ApplicationItem.qml")), uri, 2, 1, "ApplicationItem"); //2.2 //Theme changed from a singleton to an attached property qmlRegisterUncreatableType(uri, 2, 2, "Theme", QStringLiteral("Cannot create objects of type Theme, use it as an attached property")); //2.3 qmlRegisterType(componentUrl(QStringLiteral("FormLayout.qml")), uri, 2, 3, "FormLayout"); qmlRegisterUncreatableType(uri, 2, 3, "FormData", QStringLiteral("Cannot create objects of type FormData, use it as an attached property")); qmlRegisterUncreatableType(uri, 2, 3, "MnemonicData", QStringLiteral("Cannot create objects of type MnemonicData, use it as an attached property")); //2.4 qmlRegisterType(componentUrl(QStringLiteral("AbstractCard.qml")), uri, 2, 4, "AbstractCard"); qmlRegisterType(componentUrl(QStringLiteral("Card.qml")), uri, 2, 4, "Card"); qmlRegisterType(componentUrl(QStringLiteral("CardsListView.qml")), uri, 2, 4, "CardsListView"); qmlRegisterType(componentUrl(QStringLiteral("CardsGridView.qml")), uri, 2, 4, "CardsGridView"); qmlRegisterType(componentUrl(QStringLiteral("CardsLayout.qml")), uri, 2, 4, "CardsLayout"); qmlRegisterType(componentUrl(QStringLiteral("InlineMessage.qml")), uri, 2, 4, "InlineMessage"); qmlRegisterUncreatableType(uri, 2, 4, "MessageType", QStringLiteral("Cannot create objects of type MessageType")); qmlRegisterType(uri, 2, 4, "DelegateRecycler"); //2.5 qmlRegisterType(componentUrl(QStringLiteral("ListItemDragHandle.qml")), uri, 2, 5, "ListItemDragHandle"); qmlRegisterType(componentUrl(QStringLiteral("ActionToolBar.qml")), uri, 2, 5, "ActionToolBar"); qmlRegisterUncreatableType(uri, 2, 5, "ScenePosition", QStringLiteral("Cannot create objects of type ScenePosition, use it as an attached property")); //2.6 qmlRegisterType(componentUrl(QStringLiteral("AboutPage.qml")), uri, 2, 6, "AboutPage"); qmlRegisterType(componentUrl(QStringLiteral("LinkButton.qml")), uri, 2, 6, "LinkButton"); qmlRegisterType(componentUrl(QStringLiteral("UrlButton.qml")), uri, 2, 6, "UrlButton"); qmlRegisterSingletonType("org.kde.kirigami.private", 2, 6, "CopyHelperPrivate", [] (QQmlEngine*, QJSEngine*) -> QObject* { return new CopyHelperPrivate; }); //2.7 qmlRegisterType(uri, 2, 7, "ColumnView"); qmlRegisterType(componentUrl(QStringLiteral("ActionTextField.qml")), uri, 2, 7, "ActionTextField"); //2.8 qmlRegisterType(componentUrl(QStringLiteral("SearchField.qml")), uri, 2, 8, "SearchField"); qmlRegisterType(componentUrl(QStringLiteral("PasswordField.qml")), uri, 2, 8, "PasswordField"); //2.9 qmlRegisterType(uri, 2, 9, "WheelHandler"); qmlRegisterUncreatableType(uri, 2, 9, "WheelEvent", QStringLiteral("Cannot create objects of type WheelEvent.")); //2.10 qmlRegisterType(componentUrl(QStringLiteral("ListSectionHeader.qml")), uri, 2, 10, "ListSectionHeader"); // 2.11 qmlRegisterType(uri, 2, 11, "PagePool"); qmlRegisterType(componentUrl(QStringLiteral("PagePoolAction.qml")), uri, 2, 11, "PagePoolAction"); //TODO: remove qmlRegisterType(componentUrl(QStringLiteral("SwipeListItem2.qml")), uri, 2, 11, "SwipeListItem2"); // 2.12 qmlRegisterType(uri, 2, 12, "ShadowedRectangle"); qmlRegisterType(uri, 2, 12, "ShadowedTexture"); qmlRegisterType(componentUrl(QStringLiteral("ShadowedImage.qml")), uri, 2, 12, "ShadowedImage"); qmlRegisterType(componentUrl(QStringLiteral("PlaceholderMessage.qml")), uri, 2, 12, "PlaceholderMessage"); qmlRegisterUncreatableType(uri, 2, 12, "BorderGroup", QStringLiteral("Used as grouped property")); qmlRegisterUncreatableType(uri, 2, 12, "ShadowGroup", QStringLiteral("Used as grouped property")); qmlRegisterSingletonType(uri, 2, 12, "ColorUtils", [] (QQmlEngine*, QJSEngine*) -> QObject* { return new ColorUtils; }); qmlRegisterUncreatableType(uri, 2, 12, "CornersGroup", QStringLiteral("Used as grouped property")); qmlRegisterType(uri, 2, 12, "PageRouter"); qmlRegisterType(uri, 2, 12, "PageRoute"); qmlRegisterUncreatableType(uri, 2, 12, "PageRouterAttached", QStringLiteral("PageRouterAttached cannot be created")); qmlRegisterType(componentUrl(QStringLiteral("RouterWindow.qml")), uri, 2, 12, "RouterWindow"); + // 2.13 + qmlRegisterType(uri, 2, 13, "ImageColors"); + qmlProtectModule(uri, 2); } void KirigamiPlugin::initializeEngine(QQmlEngine *engine, const char *uri) { Q_UNUSED(uri); connect(this, &KirigamiPlugin::languageChangeEvent, engine, &QQmlEngine::retranslate); } #include "kirigamiplugin.moc"