Kirigami2

imagecolors.cpp
1/*
2 * SPDX-FileCopyrightText: 2020 Marco Martin <mart@kde.org>
3 * SPDX-FileCopyrightText: 2024 ivan tkachenko <me@ratijas.tk>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "imagecolors.h"
9
10#include <QDebug>
11#include <QFutureWatcher>
12#include <QGuiApplication>
13#include <QtConcurrentRun>
14
15#include "loggingcategory.h"
16#include <cmath>
17#include <vector>
18
19#include "config-OpenMP.h"
20#if HAVE_OpenMP
21#include <omp.h>
22#endif
23
24#include "platform/platformtheme.h"
25
26#define return_fallback(value) \
27 if (m_imageData.m_samples.size() == 0) { \
28 return value; \
29 }
30
31#define return_fallback_finally(value, finally) \
32 if (m_imageData.m_samples.size() == 0) { \
33 return value.isValid() \
34 ? value \
35 : static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true))->finally(); \
36 }
37
38PaletteSwatch::PaletteSwatch()
39{
40}
41
42PaletteSwatch::PaletteSwatch(qreal ratio, const QColor &color, const QColor &contrastColor)
43 : m_ratio(ratio)
44 , m_color(color)
45 , m_contrastColor(contrastColor)
46{
47}
48
49qreal PaletteSwatch::ratio() const
50{
51 return m_ratio;
52}
53
54const QColor &PaletteSwatch::color() const
55{
56 return m_color;
57}
58
59const QColor &PaletteSwatch::contrastColor() const
60{
61 return m_contrastColor;
62}
63
64bool PaletteSwatch::operator==(const PaletteSwatch &other) const
65{
66 return m_ratio == other.m_ratio //
67 && m_color == other.m_color //
68 && m_contrastColor == other.m_contrastColor;
69}
70
71ImageColors::ImageColors(QObject *parent)
72 : QObject(parent)
73{
74}
75
76ImageColors::~ImageColors()
77{
78}
79
80void ImageColors::setSource(const QVariant &source)
81{
82 if (m_futureSourceImageData) {
83 m_futureSourceImageData->cancel();
84 m_futureSourceImageData->deleteLater();
85 m_futureSourceImageData = nullptr;
86 }
87
88 if (source.canConvert<QQuickItem *>()) {
89 setSourceItem(source.value<QQuickItem *>());
90 } else if (source.canConvert<QImage>()) {
91 setSourceImage(source.value<QImage>());
92 } else if (source.canConvert<QIcon>()) {
93 setSourceImage(source.value<QIcon>().pixmap(128, 128).toImage());
94 } else if (source.canConvert<QString>()) {
95 const QString sourceString = source.toString();
96
97 if (QIcon::hasThemeIcon(sourceString)) {
98 setSourceImage(QIcon::fromTheme(sourceString).pixmap(128, 128).toImage());
99 } else {
100 QFuture<QImage> future = QtConcurrent::run([sourceString]() {
101 if (auto url = QUrl(sourceString); url.isLocalFile()) {
102 return QImage(url.toLocalFile());
103 }
104 return QImage(sourceString);
105 });
106 m_futureSourceImageData = new QFutureWatcher<QImage>(this);
107 connect(m_futureSourceImageData, &QFutureWatcher<QImage>::finished, this, [this, source]() {
108 const QImage image = m_futureSourceImageData->future().result();
109 m_futureSourceImageData->deleteLater();
110 m_futureSourceImageData = nullptr;
111 setSourceImage(image);
112 m_source = source;
113 Q_EMIT sourceChanged();
114 });
115 m_futureSourceImageData->setFuture(future);
116 return;
117 }
118 } else {
119 return;
120 }
121
122 m_source = source;
123 Q_EMIT sourceChanged();
124}
125
127{
128 return m_source;
129}
130
131void ImageColors::setSourceImage(const QImage &image)
132{
133 if (m_window) {
134 disconnect(m_window.data(), nullptr, this, nullptr);
135 }
136 if (m_sourceItem) {
137 disconnect(m_sourceItem.data(), nullptr, this, nullptr);
138 }
139 if (m_grabResult) {
140 disconnect(m_grabResult.data(), nullptr, this, nullptr);
141 m_grabResult.clear();
142 }
143
144 m_sourceItem.clear();
145
146 m_sourceImage = image;
147 update();
148}
149
150QImage ImageColors::sourceImage() const
151{
152 return m_sourceImage;
153}
154
155void ImageColors::setSourceItem(QQuickItem *source)
156{
157 if (m_sourceItem == source) {
158 return;
159 }
160
161 if (m_window) {
162 disconnect(m_window.data(), nullptr, this, nullptr);
163 }
164 if (m_sourceItem) {
165 disconnect(m_sourceItem, nullptr, this, nullptr);
166 }
167 m_sourceItem = source;
168 update();
169
170 if (m_sourceItem) {
171 auto syncWindow = [this]() {
172 if (m_window) {
173 disconnect(m_window.data(), nullptr, this, nullptr);
174 }
175 m_window = m_sourceItem->window();
176 if (m_window) {
177 connect(m_window, &QWindow::visibleChanged, this, &ImageColors::update);
178 }
179 update();
180 };
181
182 connect(m_sourceItem, &QQuickItem::windowChanged, this, syncWindow);
183 syncWindow();
184 }
185}
186
187QQuickItem *ImageColors::sourceItem() const
188{
189 return m_sourceItem;
190}
191
192void ImageColors::update()
193{
194 if (m_futureImageData) {
195 m_futureImageData->disconnect(this, nullptr);
196 m_futureImageData->cancel();
197 m_futureImageData->deleteLater();
198 m_futureImageData = nullptr;
199 }
200
201 auto runUpdate = [this]() {
202 auto sourceImage{m_sourceImage};
203 QFuture<ImageData> future = QtConcurrent::run([sourceImage = std::move(sourceImage)]() {
204 return generatePalette(sourceImage);
205 });
206 m_futureImageData = new QFutureWatcher<ImageData>(this);
207 connect(m_futureImageData, &QFutureWatcher<ImageData>::finished, this, [this]() {
208 if (!m_futureImageData) {
209 return;
210 }
211 m_imageData = m_futureImageData->future().result();
212 postProcess(m_imageData);
213 m_futureImageData->deleteLater();
214 m_futureImageData = nullptr;
215
216 Q_EMIT paletteChanged();
217 });
218 m_futureImageData->setFuture(future);
219 };
220
221 if (!m_sourceItem || !m_sourceItem->window() || !m_sourceItem->window()->isVisible()) {
222 if (!m_sourceImage.isNull()) {
223 runUpdate();
224 } else {
225 m_imageData = {};
226 Q_EMIT paletteChanged();
227 }
228 return;
229 }
230
231 if (m_grabResult) {
232 disconnect(m_grabResult.data(), nullptr, this, nullptr);
233 m_grabResult.clear();
234 }
235
236 m_grabResult = m_sourceItem->grabToImage(QSize(128, 128));
237
238 if (m_grabResult) {
239 connect(m_grabResult.data(), &QQuickItemGrabResult::ready, this, [this, runUpdate]() {
240 m_sourceImage = m_grabResult->image();
241 m_grabResult.clear();
242 runUpdate();
243 });
244 }
245}
246
247static inline int squareDistance(QRgb color1, QRgb color2)
248{
249 // https://en.wikipedia.org/wiki/Color_difference
250 // Using RGB distance for performance, as CIEDE2000 is too complicated
251 if (qRed(color1) - qRed(color2) < 128) {
252 return 2 * pow(qRed(color1) - qRed(color2), 2) //
253 + 4 * pow(qGreen(color1) - qGreen(color2), 2) //
254 + 3 * pow(qBlue(color1) - qBlue(color2), 2);
255 } else {
256 return 3 * pow(qRed(color1) - qRed(color2), 2) //
257 + 4 * pow(qGreen(color1) - qGreen(color2), 2) //
258 + 2 * pow(qBlue(color1) - qBlue(color2), 2);
259 }
260}
261
262void ImageColors::positionColor(QRgb rgb, QList<ImageData::colorStat> &clusters)
263{
264 for (auto &stat : clusters) {
265 if (squareDistance(rgb, stat.centroid) < s_minimumSquareDistance) {
266 stat.colors.append(rgb);
267 return;
268 }
269 }
270
271 ImageData::colorStat stat;
272 stat.colors.append(rgb);
273 stat.centroid = rgb;
274 clusters << stat;
275}
276
277void ImageColors::positionColorMP(const decltype(ImageData::m_samples) &samples, decltype(ImageData::m_clusters) &clusters, int numCore)
278{
279#if HAVE_OpenMP
280 if (samples.size() < 65536 /* 256^2 */ || numCore < 2) {
281#else
282 if (true) {
283#endif
284 // Fall back to single thread
285 for (auto color : samples) {
286 positionColor(color, clusters);
287 }
288 return;
289 }
290#if HAVE_OpenMP
291 // Split the whole samples into multiple parts
292 const int numSamplesPerThread = samples.size() / numCore;
293 std::vector<decltype(ImageData::m_clusters)> tempClusters(numCore, decltype(ImageData::m_clusters){});
294#pragma omp parallel for
295 for (int i = 0; i < numCore; ++i) {
296 const auto beginIt = std::next(samples.begin(), numSamplesPerThread * i);
297 const auto endIt = i < numCore - 1 ? std::next(samples.begin(), numSamplesPerThread * (i + 1)) : samples.end();
298
299 for (auto it = beginIt; it != endIt; it = std::next(it)) {
300 positionColor(*it, tempClusters[omp_get_thread_num()]);
301 }
302 } // END omp parallel for
303
304 // Restore clusters
305 // Don't use std::as_const as memory will grow significantly
306 for (const auto &clusterPart : tempClusters) {
307 clusters << clusterPart;
308 }
309 for (int i = 0; i < clusters.size() - 1; ++i) {
310 auto &clusterA = clusters[i];
311 if (clusterA.colors.empty()) {
312 continue; // Already merged
313 }
314 for (int j = i + 1; j < clusters.size(); ++j) {
315 auto &clusterB = clusters[j];
316 if (clusterB.colors.empty()) {
317 continue; // Already merged
318 }
319 if (squareDistance(clusterA.centroid, clusterB.centroid) < s_minimumSquareDistance) {
320 // Merge colors in clusterB into clusterA
321 clusterA.colors.append(clusterB.colors);
322 clusterB.colors.clear();
323 }
324 }
325 }
326
327 auto removeIt = std::remove_if(clusters.begin(), clusters.end(), [](const ImageData::colorStat &stat) {
328 return stat.colors.empty();
329 });
330 clusters.erase(removeIt, clusters.end());
331#endif
332}
333
334ImageData ImageColors::generatePalette(const QImage &sourceImage)
335{
336 ImageData imageData;
337
338 if (sourceImage.isNull() || sourceImage.width() == 0) {
339 return imageData;
340 }
341
342 imageData.m_clusters.clear();
343 imageData.m_samples.clear();
344
345#if HAVE_OpenMP
346 static const int numCore = std::min(8, omp_get_num_procs());
347 omp_set_num_threads(numCore);
348#else
349 constexpr int numCore = 1;
350#endif
351 int r = 0;
352 int g = 0;
353 int b = 0;
354 int c = 0;
355
356#pragma omp parallel for collapse(2) reduction(+ : r) reduction(+ : g) reduction(+ : b) reduction(+ : c)
357 for (int x = 0; x < sourceImage.width(); ++x) {
358 for (int y = 0; y < sourceImage.height(); ++y) {
359 const QColor sampleColor = sourceImage.pixelColor(x, y);
360 if (sampleColor.alpha() == 0) {
361 continue;
362 }
363 if (ColorUtils::chroma(sampleColor) < 20) {
364 continue;
365 }
366 QRgb rgb = sampleColor.rgb();
367 ++c;
368 r += qRed(rgb);
369 g += qGreen(rgb);
370 b += qBlue(rgb);
371#pragma omp critical
372 imageData.m_samples << rgb;
373 }
374 } // END omp parallel for
375
376 if (imageData.m_samples.isEmpty()) {
377 return imageData;
378 }
379
380 positionColorMP(imageData.m_samples, imageData.m_clusters, numCore);
381
382 imageData.m_average = QColor(r / c, g / c, b / c, 255);
383
384 for (int iteration = 0; iteration < 5; ++iteration) {
385#pragma omp parallel for private(r, g, b, c)
386 for (int i = 0; i < imageData.m_clusters.size(); ++i) {
387 auto &stat = imageData.m_clusters[i];
388 r = 0;
389 g = 0;
390 b = 0;
391 c = 0;
392
393 for (auto color : std::as_const(stat.colors)) {
394 c++;
395 r += qRed(color);
396 g += qGreen(color);
397 b += qBlue(color);
398 }
399 r = r / c;
400 g = g / c;
401 b = b / c;
402 stat.centroid = qRgb(r, g, b);
403 stat.ratio = std::clamp(qreal(stat.colors.count()) / qreal(imageData.m_samples.count()), 0.0, 1.0);
404 stat.colors = QList<QRgb>({stat.centroid});
405 } // END omp parallel for
406
407 positionColorMP(imageData.m_samples, imageData.m_clusters, numCore);
408 }
409
410 std::sort(imageData.m_clusters.begin(), imageData.m_clusters.end(), [](const ImageData::colorStat &a, const ImageData::colorStat &b) {
411 return getClusterScore(a) > getClusterScore(b);
412 });
413
414 // compress blocks that became too similar
415 auto sourceIt = imageData.m_clusters.end();
416 // Use index instead of iterator, because QList::erase may invalidate iterator.
417 std::vector<int> itemsToDelete;
418 while (sourceIt != imageData.m_clusters.begin()) {
419 sourceIt--;
420 for (auto destIt = imageData.m_clusters.begin(); destIt != imageData.m_clusters.end() && destIt != sourceIt; destIt++) {
421 if (squareDistance((*sourceIt).centroid, (*destIt).centroid) < s_minimumSquareDistance) {
422 const qreal ratio = (*sourceIt).ratio / (*destIt).ratio;
423 const int r = ratio * qreal(qRed((*sourceIt).centroid)) + (1 - ratio) * qreal(qRed((*destIt).centroid));
424 const int g = ratio * qreal(qGreen((*sourceIt).centroid)) + (1 - ratio) * qreal(qGreen((*destIt).centroid));
425 const int b = ratio * qreal(qBlue((*sourceIt).centroid)) + (1 - ratio) * qreal(qBlue((*destIt).centroid));
426 (*destIt).ratio += (*sourceIt).ratio;
427 (*destIt).centroid = qRgb(r, g, b);
428 itemsToDelete.push_back(std::distance(imageData.m_clusters.begin(), sourceIt));
429 break;
430 }
431 }
432 }
433 for (auto i : std::as_const(itemsToDelete)) {
434 imageData.m_clusters.removeAt(i);
435 }
436
437 imageData.m_highlight = QColor();
438 imageData.m_dominant = QColor(imageData.m_clusters.first().centroid);
439 imageData.m_closestToBlack = Qt::white;
440 imageData.m_closestToWhite = Qt::black;
441
442 imageData.m_palette.clear();
443
444 bool first = true;
445
446#pragma omp parallel for ordered
447 for (int i = 0; i < imageData.m_clusters.size(); ++i) {
448 const auto &stat = imageData.m_clusters[i];
449 const QColor color(stat.centroid);
450
451 QColor contrast = QColor(255 - color.red(), 255 - color.green(), 255 - color.blue());
452 contrast.setHsl(contrast.hslHue(), //
453 contrast.hslSaturation(), //
454 128 + (128 - contrast.lightness()));
455 QColor tempContrast;
456 int minimumDistance = 4681800; // max distance: 4*3*2*3*255*255
457 for (const auto &stat : std::as_const(imageData.m_clusters)) {
458 const int distance = squareDistance(contrast.rgb(), stat.centroid);
459
460 if (distance < minimumDistance) {
461 tempContrast = QColor(stat.centroid);
462 minimumDistance = distance;
463 }
464 }
465
466 if (imageData.m_clusters.size() <= 3) {
467 if (qGray(imageData.m_dominant.rgb()) < 120) {
468 contrast = QColor(230, 230, 230);
469 } else {
470 contrast = QColor(20, 20, 20);
471 }
472 // TODO: replace m_clusters.size() > 3 with entropy calculation
473 } else if (squareDistance(contrast.rgb(), tempContrast.rgb()) < s_minimumSquareDistance * 1.5) {
474 contrast = tempContrast;
475 } else {
476 contrast = tempContrast;
477 contrast.setHsl(contrast.hslHue(),
478 contrast.hslSaturation(),
479 contrast.lightness() > 128 ? qMin(contrast.lightness() + 20, 255) : qMax(0, contrast.lightness() - 20));
480 }
481
482#pragma omp ordered
483 { // BEGIN omp ordered
484 if (first) {
485 imageData.m_dominantContrast = contrast;
486 imageData.m_dominant = color;
487 }
488 first = false;
489
490 if (!imageData.m_highlight.isValid() || ColorUtils::chroma(color) > ColorUtils::chroma(imageData.m_highlight)) {
491 imageData.m_highlight = color;
492 }
493
494 if (qGray(color.rgb()) > qGray(imageData.m_closestToWhite.rgb())) {
495 imageData.m_closestToWhite = color;
496 }
497 if (qGray(color.rgb()) < qGray(imageData.m_closestToBlack.rgb())) {
498 imageData.m_closestToBlack = color;
499 }
500 imageData.m_palette << PaletteSwatch(stat.ratio, color, contrast);
501 } // END omp ordered
502 }
503
504 return imageData;
505}
506
507double ImageColors::getClusterScore(const ImageData::colorStat &stat)
508{
509 return stat.ratio * ColorUtils::chroma(QColor(stat.centroid));
510}
511
512void ImageColors::postProcess(ImageData &imageData) const
513{
514 constexpr short unsigned WCAG_NON_TEXT_CONTRAST_RATIO = 3;
515 constexpr qreal WCAG_TEXT_CONTRAST_RATIO = 4.5;
516
517 auto platformTheme = qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, false);
518 if (!platformTheme) {
519 return;
520 }
521
522 const QColor backgroundColor = static_cast<Kirigami::Platform::PlatformTheme *>(platformTheme)->backgroundColor();
523 const qreal backgroundLum = ColorUtils::luminance(backgroundColor);
524 qreal lowerLum, upperLum;
525 // 192 is from kcm_colors
526 if (qGray(backgroundColor.rgb()) < 192) {
527 // (lowerLum + 0.05) / (backgroundLum + 0.05) >= 3
528 lowerLum = WCAG_NON_TEXT_CONTRAST_RATIO * (backgroundLum + 0.05) - 0.05;
529 upperLum = 0.95;
530 } else {
531 // For light themes, still prefer lighter colors
532 // (lowerLum + 0.05) / (textLum + 0.05) >= 4.5
533 const QColor textColor =
534 static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true))->textColor();
535 const qreal textLum = ColorUtils::luminance(textColor);
536 lowerLum = WCAG_TEXT_CONTRAST_RATIO * (textLum + 0.05) - 0.05;
537 upperLum = backgroundLum;
538 }
539
540 auto adjustSaturation = [](QColor &color) {
541 // Adjust saturation to make the color more vibrant
542 if (color.hsvSaturationF() < 0.5) {
543 const qreal h = color.hsvHueF();
544 const qreal v = color.valueF();
545 color.setHsvF(h, 0.5, v);
546 }
547 };
548 adjustSaturation(imageData.m_dominant);
549 adjustSaturation(imageData.m_highlight);
550 adjustSaturation(imageData.m_average);
551
552 auto adjustLightness = [lowerLum, upperLum](QColor &color) {
553 short unsigned colorOperationCount = 0;
554 const qreal h = color.hslHueF();
555 const qreal s = color.hslSaturationF();
556 const qreal l = color.lightnessF();
557 while (ColorUtils::luminance(color.rgb()) < lowerLum && colorOperationCount++ < 10) {
558 color.setHslF(h, s, std::min(1.0, l + colorOperationCount * 0.03));
559 }
560 while (ColorUtils::luminance(color.rgb()) > upperLum && colorOperationCount++ < 10) {
561 color.setHslF(h, s, std::max(0.0, l - colorOperationCount * 0.03));
562 }
563 };
564 adjustLightness(imageData.m_dominant);
565 adjustLightness(imageData.m_highlight);
566 adjustLightness(imageData.m_average);
567}
568
570{
571 if (m_futureImageData) {
572 qCWarning(KirigamiLog) << m_futureImageData->future().isFinished();
573 }
574 return_fallback(m_fallbackPalette) return m_imageData.m_palette;
575}
576
578{
579 /* clang-format off */
580 return_fallback(m_fallbackPaletteBrightness)
581
582 return qGray(m_imageData.m_dominant.rgb()) < 128 ? ColorUtils::Dark : ColorUtils::Light;
583 /* clang-format on */
584}
585
587{
588 /* clang-format off */
589 return_fallback_finally(m_fallbackAverage, linkBackgroundColor)
590
591 return m_imageData.m_average;
592 /* clang-format on */
593}
594
596{
597 /* clang-format off */
598 return_fallback_finally(m_fallbackDominant, linkBackgroundColor)
599
600 return m_imageData.m_dominant;
601 /* clang-format on */
602}
603
605{
606 /* clang-format off */
607 return_fallback_finally(m_fallbackDominantContrasting, linkBackgroundColor)
608
609 return m_imageData.m_dominantContrast;
610 /* clang-format on */
611}
612
614{
615 /* clang-format off */
616 return_fallback_finally(m_fallbackForeground, textColor)
617
619 {
620 if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
621 return QColor(230, 230, 230);
622 }
623 return m_imageData.m_closestToWhite;
624 } else {
625 if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
626 return QColor(20, 20, 20);
627 }
628 return m_imageData.m_closestToBlack;
629 }
630 /* clang-format on */
631}
632
634{
635 /* clang-format off */
636 return_fallback_finally(m_fallbackBackground, backgroundColor)
637
639 if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
640 return QColor(20, 20, 20);
641 }
642 return m_imageData.m_closestToBlack;
643 } else {
644 if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
645 return QColor(230, 230, 230);
646 }
647 return m_imageData.m_closestToWhite;
648 }
649 /* clang-format on */
650}
651
653{
654 /* clang-format off */
655 return_fallback_finally(m_fallbackHighlight, linkColor)
656
657 return m_imageData.m_highlight;
658 /* clang-format on */
659}
660
662{
663 /* clang-format off */
664 return_fallback(Qt::white)
665 if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
666 return QColor(230, 230, 230);
667 }
668 /* clang-format on */
669
670 return m_imageData.m_closestToWhite;
671}
672
674{
675 /* clang-format off */
676 return_fallback(Qt::black)
677 if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
678 return QColor(20, 20, 20);
679 }
680 /* clang-format on */
681 return m_imageData.m_closestToBlack;
682}
683
684#include "moc_imagecolors.cpp"
static Q_INVOKABLE qreal chroma(const QColor &color)
QML_ELEMENTQVariant source
Definition imagecolors.h:92
QColor foreground
QColor closestToWhite
QColor dominant
QVariantList palette
QColor closestToBlack
QColor dominantContrast
ColorUtils::Brightness paletteBrightness
QColor average
QColor background
QColor highlight
This class is the base for color management in Kirigami, different platforms can reimplement this cla...
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
int alpha() const const
int hslHue() const const
int hslSaturation() const const
bool isValid() const const
int lightness() const const
QRgb rgb() const const
void setHsl(int h, int s, int l, int a)
QPixmap pixmap(QWindow *window, const QSize &size, Mode mode, State state) const const
QIcon fromTheme(const QString &name)
bool hasThemeIcon(const QString &name)
int height() const const
bool isNull() const const
QColor pixelColor(const QPoint &position) const const
int width() const const
iterator begin()
void clear()
qsizetype count() const const
iterator end()
T & first()
bool isEmpty() const const
void removeAt(qsizetype i)
qsizetype size() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QImage toImage() const const
void clear()
T * data() const const
void windowChanged(QQuickWindow *window)
T * data() const const
QFuture< T > run(Function function,...)
bool isLocalFile() const const
void visibleChanged(bool arg)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:54:50 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.