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 postProcess(m_imageData);
194 m_futureImageData->deleteLater();
195 m_futureImageData = nullptr;
196
197 Q_EMIT paletteChanged();
198 });
199 m_futureImageData->setFuture(future);
200 };
201
202 if (!m_sourceItem) {
203 if (!m_sourceImage.isNull()) {
204 runUpdate();
205 } else {
206 m_imageData = {};
207 Q_EMIT paletteChanged();
208 }
209 return;
210 }
211
212 if (m_grabResult) {
213 disconnect(m_grabResult.data(), nullptr, this, nullptr);
214 m_grabResult.clear();
215 }
216
217 m_grabResult = m_sourceItem->grabToImage(QSize(128, 128));
218
219 if (m_grabResult) {
220 connect(m_grabResult.data(), &QQuickItemGrabResult::ready, this, [this, runUpdate]() {
221 m_sourceImage = m_grabResult->image();
222 m_grabResult.clear();
223 runUpdate();
224 });
225 }
226}
227
228inline int squareDistance(QRgb color1, QRgb color2)
229{
230 // https://en.wikipedia.org/wiki/Color_difference
231 // Using RGB distance for performance, as CIEDE2000 is too complicated
232 if (qRed(color1) - qRed(color2) < 128) {
233 return 2 * pow(qRed(color1) - qRed(color2), 2) //
234 + 4 * pow(qGreen(color1) - qGreen(color2), 2) //
235 + 3 * pow(qBlue(color1) - qBlue(color2), 2);
236 } else {
237 return 3 * pow(qRed(color1) - qRed(color2), 2) //
238 + 4 * pow(qGreen(color1) - qGreen(color2), 2) //
239 + 2 * pow(qBlue(color1) - qBlue(color2), 2);
240 }
241}
242
243void ImageColors::positionColor(QRgb rgb, QList<ImageData::colorStat> &clusters)
244{
245 for (auto &stat : clusters) {
246 if (squareDistance(rgb, stat.centroid) < s_minimumSquareDistance) {
247 stat.colors.append(rgb);
248 return;
249 }
250 }
251
252 ImageData::colorStat stat;
253 stat.colors.append(rgb);
254 stat.centroid = rgb;
255 clusters << stat;
256}
257
258void ImageColors::positionColorMP(const decltype(ImageData::m_samples) &samples, decltype(ImageData::m_clusters) &clusters, int numCore)
259{
260#if HAVE_OpenMP
261 if (samples.size() < 65536 /* 256^2 */ || numCore < 2) {
262#else
263 if (true) {
264#endif
265 // Fall back to single thread
266 for (auto color : samples) {
267 positionColor(color, clusters);
268 }
269 return;
270 }
271#if HAVE_OpenMP
272 // Split the whole samples into multiple parts
273 const int numSamplesPerThread = samples.size() / numCore;
274 std::vector<decltype(ImageData::m_clusters)> tempClusters(numCore, decltype(ImageData::m_clusters){});
275#pragma omp parallel for
276 for (int i = 0; i < numCore; ++i) {
277 decltype(ImageData::m_samples) samplePart;
278 const auto beginIt = std::next(samples.begin(), numSamplesPerThread * i);
279 const auto endIt = i < numCore - 1 ? std::next(samples.begin(), numSamplesPerThread * (i + 1)) : samples.end();
280
281 for (auto it = beginIt; it != endIt; it = std::next(it)) {
282 positionColor(*it, tempClusters[omp_get_thread_num()]);
283 }
284 } // END omp parallel for
285
286 // Restore clusters
287 // Don't use std::as_const as memory will grow significantly
288 for (const auto &clusterPart : tempClusters) {
290 }
291 for (int i = 0; i < clusters.size() - 1; ++i) {
292 auto &clusterA = clusters[i];
293 if (clusterA.colors.empty()) {
294 continue; // Already merged
295 }
296 for (int j = i + 1; j < clusters.size(); ++j) {
297 auto &clusterB = clusters[j];
298 if (clusterB.colors.empty()) {
299 continue; // Already merged
300 }
301 if (squareDistance(clusterA.centroid, clusterB.centroid) < s_minimumSquareDistance) {
302 // Merge colors in clusterB into clusterA
303 clusterA.colors.append(clusterB.colors);
304 clusterB.colors.clear();
305 }
306 }
307 }
308
309 auto removeIt = std::remove_if(clusters.begin(), clusters.end(), [](const ImageData::colorStat &stat) {
310 return stat.colors.empty();
311 });
312 clusters.erase(removeIt, clusters.end());
313#endif
314}
315
316ImageData ImageColors::generatePalette(const QImage &sourceImage) const
317{
318 ImageData imageData;
319
320 if (sourceImage.isNull() || sourceImage.width() == 0) {
321 return imageData;
322 }
323
324 imageData.m_clusters.clear();
325 imageData.m_samples.clear();
326
327#if HAVE_OpenMP
328 static const int numCore = std::min(8, omp_get_num_procs());
330#else
331 constexpr int numCore = 1;
332#endif
333 int r = 0;
334 int g = 0;
335 int b = 0;
336 int c = 0;
337
338#pragma omp parallel for collapse(2) reduction(+ : r) reduction(+ : g) reduction(+ : b) reduction(+ : c)
339 for (int x = 0; x < sourceImage.width(); ++x) {
340 for (int y = 0; y < sourceImage.height(); ++y) {
341 const QColor sampleColor = sourceImage.pixelColor(x, y);
342 if (sampleColor.alpha() == 0) {
343 continue;
344 }
346 continue;
347 }
348 QRgb rgb = sampleColor.rgb();
349 ++c;
350 r += qRed(rgb);
351 g += qGreen(rgb);
352 b += qBlue(rgb);
353#pragma omp critical
354 imageData.m_samples << rgb;
355 }
356 } // END omp parallel for
357
358 if (imageData.m_samples.isEmpty()) {
359 return imageData;
360 }
361
362 positionColorMP(imageData.m_samples, imageData.m_clusters, numCore);
363
364 imageData.m_average = QColor(r / c, g / c, b / c, 255);
365
366 for (int iteration = 0; iteration < 5; ++iteration) {
367#pragma omp parallel for private(r, g, b, c)
368 for (int i = 0; i < imageData.m_clusters.size(); ++i) {
369 auto &stat = imageData.m_clusters[i];
370 r = 0;
371 g = 0;
372 b = 0;
373 c = 0;
374
375 for (auto color : std::as_const(stat.colors)) {
376 c++;
377 r += qRed(color);
378 g += qGreen(color);
379 b += qBlue(color);
380 }
381 r = r / c;
382 g = g / c;
383 b = b / c;
384 stat.centroid = qRgb(r, g, b);
385 stat.ratio = qreal(stat.colors.count()) / qreal(imageData.m_samples.count());
386 stat.colors = QList<QRgb>({stat.centroid});
387 } // END omp parallel for
388
389 positionColorMP(imageData.m_samples, imageData.m_clusters, numCore);
390 }
391
392 std::sort(imageData.m_clusters.begin(), imageData.m_clusters.end(), [this](const ImageData::colorStat &a, const ImageData::colorStat &b) {
393 return getClusterScore(a) > getClusterScore(b);
394 });
395
396 // compress blocks that became too similar
397 auto sourceIt = imageData.m_clusters.end();
398 // Use index instead of iterator, because QList::erase may invalidate iterator.
399 std::vector<int> itemsToDelete;
400 while (sourceIt != imageData.m_clusters.begin()) {
401 sourceIt--;
402 for (auto destIt = imageData.m_clusters.begin(); destIt != imageData.m_clusters.end() && destIt != sourceIt; destIt++) {
403 if (squareDistance((*sourceIt).centroid, (*destIt).centroid) < s_minimumSquareDistance) {
404 const qreal ratio = (*sourceIt).ratio / (*destIt).ratio;
405 const int r = ratio * qreal(qRed((*sourceIt).centroid)) + (1 - ratio) * qreal(qRed((*destIt).centroid));
406 const int g = ratio * qreal(qGreen((*sourceIt).centroid)) + (1 - ratio) * qreal(qGreen((*destIt).centroid));
407 const int b = ratio * qreal(qBlue((*sourceIt).centroid)) + (1 - ratio) * qreal(qBlue((*destIt).centroid));
408 (*destIt).ratio += (*sourceIt).ratio;
409 (*destIt).centroid = qRgb(r, g, b);
410 itemsToDelete.push_back(std::distance(imageData.m_clusters.begin(), sourceIt));
411 break;
412 }
413 }
414 }
415 for (auto i : std::as_const(itemsToDelete)) {
416 imageData.m_clusters.removeAt(i);
417 }
418
419 imageData.m_highlight = QColor();
420 imageData.m_dominant = QColor(imageData.m_clusters.first().centroid);
421 imageData.m_closestToBlack = Qt::white;
422 imageData.m_closestToWhite = Qt::black;
423
424 imageData.m_palette.clear();
425
426 bool first = true;
427
428#pragma omp parallel for ordered
429 for (int i = 0; i < imageData.m_clusters.size(); ++i) {
430 const auto &stat = imageData.m_clusters[i];
431 QVariantMap entry;
432 const QColor color(stat.centroid);
433 entry[QStringLiteral("color")] = color;
434 entry[QStringLiteral("ratio")] = stat.ratio;
435
436 QColor contrast = QColor(255 - color.red(), 255 - color.green(), 255 - color.blue());
437 contrast.setHsl(contrast.hslHue(), //
438 contrast.hslSaturation(), //
439 128 + (128 - contrast.lightness()));
441 int minimumDistance = 4681800; // max distance: 4*3*2*3*255*255
442 for (const auto &stat : std::as_const(imageData.m_clusters)) {
443 const int distance = squareDistance(contrast.rgb(), stat.centroid);
444
445 if (distance < minimumDistance) {
446 tempContrast = QColor(stat.centroid);
448 }
449 }
450
451 if (imageData.m_clusters.size() <= 3) {
452 if (qGray(imageData.m_dominant.rgb()) < 120) {
453 contrast = QColor(230, 230, 230);
454 } else {
455 contrast = QColor(20, 20, 20);
456 }
457 // TODO: replace m_clusters.size() > 3 with entropy calculation
458 } else if (squareDistance(contrast.rgb(), tempContrast.rgb()) < s_minimumSquareDistance * 1.5) {
459 contrast = tempContrast;
460 } else {
461 contrast = tempContrast;
462 contrast.setHsl(contrast.hslHue(),
463 contrast.hslSaturation(),
464 contrast.lightness() > 128 ? qMin(contrast.lightness() + 20, 255) : qMax(0, contrast.lightness() - 20));
465 }
466
467 entry[QStringLiteral("contrastColor")] = contrast;
468#pragma omp ordered
469 { // BEGIN omp ordered
470 if (first) {
471 imageData.m_dominantContrast = contrast;
472 imageData.m_dominant = color;
473 }
474 first = false;
475
476 if (!imageData.m_highlight.isValid() || ColorUtils::chroma(color) > ColorUtils::chroma(imageData.m_highlight)) {
477 imageData.m_highlight = color;
478 }
479
480 if (qGray(color.rgb()) > qGray(imageData.m_closestToWhite.rgb())) {
481 imageData.m_closestToWhite = color;
482 }
483 if (qGray(color.rgb()) < qGray(imageData.m_closestToBlack.rgb())) {
484 imageData.m_closestToBlack = color;
485 }
486 imageData.m_palette << entry;
487 } // END omp ordered
488 }
489
490 return imageData;
491}
492
493double ImageColors::getClusterScore(const ImageData::colorStat &stat) const
494{
495 return stat.ratio * ColorUtils::chroma(QColor(stat.centroid));
496}
497
498void ImageColors::postProcess(ImageData &imageData) const
499{
500 constexpr short unsigned WCAG_NON_TEXT_CONTRAST_RATIO = 3;
501 constexpr qreal WCAG_TEXT_CONTRAST_RATIO = 4.5;
502
504 if (!platformTheme) {
505 return;
506 }
507
508 const QColor backgroundColor = static_cast<Kirigami::Platform::PlatformTheme *>(platformTheme)->backgroundColor();
509 const qreal backgroundLum = ColorUtils::luminance(backgroundColor);
510 qreal lowerLum, upperLum;
511 // 192 is from kcm_colors
512 if (qGray(backgroundColor.rgb()) < 192) {
513 // (lowerLum + 0.05) / (backgroundLum + 0.05) >= 3
515 upperLum = 0.95;
516 } else {
517 // For light themes, still prefer lighter colors
518 // (lowerLum + 0.05) / (textLum + 0.05) >= 4.5
519 const QColor textColor =
521 const qreal textLum = ColorUtils::luminance(textColor);
522 lowerLum = WCAG_TEXT_CONTRAST_RATIO * (textLum + 0.05) - 0.05;
524 }
525
526 auto adjustSaturation = [](QColor &color) {
527 // Adjust saturation to make the color more vibrant
528 if (color.hsvSaturationF() < 0.5) {
529 const qreal h = color.hsvHueF();
530 const qreal v = color.valueF();
531 color.setHsvF(h, 0.5, v);
532 }
533 };
534 adjustSaturation(imageData.m_dominant);
535 adjustSaturation(imageData.m_highlight);
536 adjustSaturation(imageData.m_average);
537
538 auto adjustLightness = [lowerLum, upperLum](QColor &color) {
539 short unsigned colorOperationCount = 0;
540 const qreal h = color.hslHueF();
541 const qreal s = color.hslSaturationF();
542 const qreal l = color.lightnessF();
543 while (ColorUtils::luminance(color.rgb()) < lowerLum && colorOperationCount++ < 10) {
544 color.setHslF(h, s, std::min(1.0, l + colorOperationCount * 0.03));
545 }
546 while (ColorUtils::luminance(color.rgb()) > upperLum && colorOperationCount++ < 10) {
547 color.setHslF(h, s, std::max(0.0, l - colorOperationCount * 0.03));
548 }
549 };
550 adjustLightness(imageData.m_dominant);
551 adjustLightness(imageData.m_highlight);
552 adjustLightness(imageData.m_average);
553}
554
555QVariantList ImageColors::palette() const
556{
557 if (m_futureImageData) {
558 qCWarning(KirigamiLog) << m_futureImageData->future().isFinished();
559 }
560 return_fallback(m_fallbackPalette) return m_imageData.m_palette;
561}
562
564{
565 /* clang-format off */
566 return_fallback(m_fallbackPaletteBrightness)
567
568 return qGray(m_imageData.m_dominant.rgb()) < 128 ? ColorUtils::Dark : ColorUtils::Light;
569 /* clang-format on */
570}
571
573{
574 /* clang-format off */
575 return_fallback_finally(m_fallbackAverage, linkBackgroundColor)
576
577 return m_imageData.m_average;
578 /* clang-format on */
579}
580
582{
583 /* clang-format off */
584 return_fallback_finally(m_fallbackDominant, linkBackgroundColor)
585
586 return m_imageData.m_dominant;
587 /* clang-format on */
588}
589
591{
592 /* clang-format off */
593 return_fallback_finally(m_fallbackDominantContrasting, linkBackgroundColor)
594
595 return m_imageData.m_dominantContrast;
596 /* clang-format on */
597}
598
600{
601 /* clang-format off */
602 return_fallback_finally(m_fallbackForeground, textColor)
603
605 {
606 if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
607 return QColor(230, 230, 230);
608 }
609 return m_imageData.m_closestToWhite;
610 } else {
611 if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
612 return QColor(20, 20, 20);
613 }
614 return m_imageData.m_closestToBlack;
615 }
616 /* clang-format on */
617}
618
620{
621 /* clang-format off */
622 return_fallback_finally(m_fallbackBackground, backgroundColor)
623
625 if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
626 return QColor(20, 20, 20);
627 }
628 return m_imageData.m_closestToBlack;
629 } else {
630 if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
631 return QColor(230, 230, 230);
632 }
633 return m_imageData.m_closestToWhite;
634 }
635 /* clang-format on */
636}
637
639{
640 /* clang-format off */
641 return_fallback_finally(m_fallbackHighlight, linkColor)
642
643 return m_imageData.m_highlight;
644 /* clang-format on */
645}
646
648{
649 /* clang-format off */
650 return_fallback(Qt::white)
651 if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
652 return QColor(230, 230, 230);
653 }
654 /* clang-format on */
655
656 return m_imageData.m_closestToWhite;
657}
658
660{
661 /* clang-format off */
662 return_fallback(Qt::black)
663 if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
664 return QColor(20, 20, 20);
665 }
666 /* clang-format on */
667 return m_imageData.m_closestToBlack;
668}
669
670#include "moc_imagecolors.cpp"
static Q_INVOKABLE qreal chroma(const QColor &color)
Returns the CIELAB chroma of the given color.
Brightness
Describes the contrast of an item.
Definition colorutils.h:29
@ Light
The item is light and requires a dark foreground color to achieve readable contrast.
Definition colorutils.h:31
@ Dark
The item is dark and requires a light foreground color to achieve readable contrast.
Definition colorutils.h:30
QML_ELEMENTQVariant source
The source from which colors should be extracted from.
Definition imagecolors.h:80
QColor foreground
A color suitable for rendering text and other foreground over the source image.
QColor closestToWhite
The lightest color of the source image.
QColor dominant
The dominant color of the source image.
QVariantList palette
A list of colors and related information about then.
Definition imagecolors.h:96
QColor closestToBlack
The darkest color of the source image.
QColor dominantContrast
Suggested "contrasting" color to the dominant one.
ColorUtils::Brightness paletteBrightness
Information whether the palette is towards a light or dark color scheme, possible values are:
QColor average
The average color of the source image.
QColor background
A color suitable for rendering a background behind the source image.
QColor highlight
An accent color extracted from the source image.
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 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)
T qobject_cast(QObject *object)
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 May 17 2024 11:49:07 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.