Kirigami2

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

KDE's Doxygen guidelines are available online.