Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickimagebase.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7
8#include <QtGui/qguiapplication.h>
9#include <QtGui/qscreen.h>
10#include <QtGui/qicon.h>
11
12#include <QtQml/qqmlinfo.h>
13#include <QtQml/qqmlfile.h>
14#include <QtQml/qqmlabstracturlinterceptor.h>
15
16
18
19using namespace Qt::Literals::StringLiterals;
20
21// This function gives derived classes the chance set the devicePixelRatio
22// if they're not happy with our implementation of it.
23bool QQuickImageBasePrivate::updateDevicePixelRatio(qreal targetDevicePixelRatio, bool scalable)
24{
25 // QQuickImageProvider and SVG and PDF can generate a high resolution image when
26 // sourceSize is set. If sourceSize is not set then the provider default size will
27 // be used, as usual.
28 if (scalable)
29 devicePixelRatio = targetDevicePixelRatio;
30 return scalable;
31}
32
33// Returns the device pixel ratio to load the image for, and remembers it.
34qreal QQuickImageBasePrivate::devicePixelRatioForLoading()
35{
36 loadedForDevicePixelRatio = effectiveDevicePixelRatio();
37 return loadedForDevicePixelRatio;
38}
39
40// Whether the image needs to be reloaded for the device pixel ratio of its window.
41bool QQuickImageBasePrivate::needsReloadForDevicePixelRatio() const
42{
43 Q_Q(const QQuickImageBase);
44 return window && loadedForDevicePixelRatio > 0 && qmlEngine(q) && q->isComponentComplete()
45 && window->effectiveDevicePixelRatio() != loadedForDevicePixelRatio;
46}
47
48void QQuickImageBasePrivate::setStatus(QQuickImageBase::Status value)
49{
50 Q_Q(QQuickImageBase);
51
52 if (status == value)
53 return;
54
55 status = value;
56 emit q->statusChanged(status);
57}
58
59void QQuickImageBasePrivate::setProgress(qreal value)
60{
61 Q_Q(QQuickImageBase);
62
63 if (qFuzzyCompare(progress, value))
64 return;
65
66 progress = value;
67 emit q->progressChanged(progress);
68}
69
70QQuickImageBase::QQuickImageBase(QQuickItem *parent)
71: QQuickImplicitSizeItem(*(new QQuickImageBasePrivate), parent)
72{
73 setFlag(ItemHasContents);
74}
75
76QQuickImageBase::QQuickImageBase(QQuickImageBasePrivate &dd, QQuickItem *parent)
77: QQuickImplicitSizeItem(dd, parent)
78{
79 setFlag(ItemHasContents);
80}
81
82QQuickImageBase::~QQuickImageBase()
83{
84}
85
86QQuickImageBase::Status QQuickImageBase::status() const
87{
88 Q_D(const QQuickImageBase);
89 return d->status;
90}
91
92qreal QQuickImageBase::progress() const
93{
94 Q_D(const QQuickImageBase);
95 return d->progress;
96}
97
98bool QQuickImageBase::asynchronous() const
99{
100 Q_D(const QQuickImageBase);
101 return d->async;
102}
103
104void QQuickImageBase::setAsynchronous(bool async)
105{
106 Q_D(QQuickImageBase);
107 if (d->async != async) {
108 d->async = async;
109 emit asynchronousChanged();
110 }
111}
112
113QUrl QQuickImageBase::source() const
114{
115 Q_D(const QQuickImageBase);
116 return d->url;
117}
118
119void QQuickImageBase::setSource(const QUrl &url)
120{
121 Q_D(QQuickImageBase);
122
123 if (url == d->url)
124 return;
125
126 d->url = url;
127 emit sourceChanged(d->url);
128
129 if (isComponentComplete())
130 load();
131}
132
133void QQuickImageBase::setSourceSize(const QSize& size)
134{
135 Q_D(QQuickImageBase);
136 if (d->sourcesize == size)
137 return;
138
139 d->sourcesize = size;
140 emit sourceSizeChanged();
141 if (isComponentComplete())
142 load();
143}
144
145QSize QQuickImageBase::sourceSize() const
146{
147 Q_D(const QQuickImageBase);
148
149 int width = d->sourcesize.width();
150 int height = d->sourcesize.height();
151 return QSize(width != -1 ? width : d->currentPix->width(), height != -1 ? height : d->currentPix->height());
152}
153
154void QQuickImageBase::resetSourceSize()
155{
156 setSourceSize(QSize());
157}
158
159QRectF QQuickImageBase::sourceClipRect() const
160{
161 Q_D(const QQuickImageBase);
162 return d->sourceClipRect;
163}
164
165void QQuickImageBase::setSourceClipRect(const QRectF &r)
166{
167 Q_D(QQuickImageBase);
168 if (d->sourceClipRect == r)
169 return;
170
171 d->sourceClipRect = r;
172 d->providerOptions.setSourceClipRect(r);
173 emit sourceClipRectChanged();
174 if (isComponentComplete())
175 load();
176}
177
178void QQuickImageBase::resetSourceClipRect()
179{
180 setSourceClipRect(QRect());
181}
182
183bool QQuickImageBase::cache() const
184{
185 Q_D(const QQuickImageBase);
186 return d->cache;
187}
188
189void QQuickImageBase::setCache(bool cache)
190{
191 Q_D(QQuickImageBase);
192 if (d->cache == cache)
193 return;
194
195 d->cache = cache;
196 emit cacheChanged();
197 if (isComponentComplete())
198 load();
199}
200
201QImage QQuickImageBase::image() const
202{
203 Q_D(const QQuickImageBase);
204 return d->currentPix->image();
205}
206
207void QQuickImageBase::setMirror(bool mirror)
208{
209 Q_D(QQuickImageBase);
210 if (mirror == d->mirrorHorizontally)
211 return;
212
213 d->mirrorHorizontally = mirror;
214
215 if (isComponentComplete())
216 update();
217
218 emit mirrorChanged();
219}
220
221bool QQuickImageBase::mirror() const
222{
223 Q_D(const QQuickImageBase);
224 return d->mirrorHorizontally;
225}
226
227void QQuickImageBase::setMirrorVertically(bool mirror)
228{
229 Q_D(QQuickImageBase);
230 if (mirror == d->mirrorVertically)
231 return;
232
233 d->mirrorVertically = mirror;
234
235 if (isComponentComplete())
236 update();
237
238 emit mirrorVerticallyChanged();
239}
240
241bool QQuickImageBase::mirrorVertically() const
242{
243 Q_D(const QQuickImageBase);
244 return d->mirrorVertically;
245}
246
247void QQuickImageBase::setCurrentFrame(int frame)
248{
249 Q_D(QQuickImageBase);
250 if (frame == d->currentFrame || frame < 0 || (isComponentComplete() && frame >= d->currentPix->frameCount()))
251 return;
252
253 d->currentFrame = frame;
254
255 if (isComponentComplete()) {
256 if (frame > 0)
257 d->cache = false;
258 load();
259 update();
260 }
261
262 emit currentFrameChanged();
263}
264
265int QQuickImageBase::currentFrame() const
266{
267 Q_D(const QQuickImageBase);
268 return d->currentFrame;
269}
270
271int QQuickImageBase::frameCount() const
272{
273 Q_D(const QQuickImageBase);
274 return d->frameCount;
275}
276
277void QQuickImageBase::loadEmptyUrl()
278{
279 Q_D(QQuickImageBase);
280 d->loadedForDevicePixelRatio = 0;
281 d->currentPix->clear(this);
282 d->pendingPix->clear(this);
283 d->setProgress(0);
284 d->status = Null; // do not emit statusChanged until after setImplicitSize
285 setImplicitSize(0, 0); // also called in QQuickImageBase::pixmapChange, but not QQuickImage/QQuickBorderImage overrides
286 pixmapChange(); // This calls update() in QQuickBorderImage and QQuickImage, not in QQuickImageBase...
287
288 emit statusChanged(d->status);
289 if (sourceSize() != d->oldSourceSize) {
290 d->oldSourceSize = sourceSize();
291 emit sourceSizeChanged();
292 }
293 if (autoTransform() != d->oldAutoTransform) {
294 d->oldAutoTransform = autoTransform();
295 emitAutoTransformBaseChanged();
296 }
297 update(); // .. but double updating should be harmless
298}
299
300void QQuickImageBase::loadPixmap(const QUrl &url, LoadPixmapOptions loadOptions)
301{
302 Q_D(QQuickImageBase);
303 QQuickPixmap::Options options;
304 if (d->async)
305 options |= QQuickPixmap::Asynchronous;
306 if (d->cache)
307 options |= QQuickPixmap::Cache;
308 d->pendingPix->clear(this);
309 QUrl loadUrl = url;
310 const QQmlContext *context = qmlContext(this);
311 if (context)
312 loadUrl = context->resolvedUrl(url);
313
314 if (loadOptions & HandleDPR) {
315 const qreal targetDevicePixelRatio = d->devicePixelRatioForLoading();
316 d->devicePixelRatio = 1.0;
317 bool updatedDevicePixelRatio = false;
318 const QUrl resolvedUrl = context ? context->resolvedUrl(d->url) : d->url;
319 const bool scalable = QQuickPixmap::isScalableImageFormat(resolvedUrl);
320 if (d->sourcesize.isValid() || (scalable && d->url.scheme() != "image"_L1))
321 updatedDevicePixelRatio = d->updateDevicePixelRatio(targetDevicePixelRatio, scalable);
322
323 if (!updatedDevicePixelRatio) {
324 // (possible) local file: loadUrl and d->devicePixelRatio will be modified if
325 // an "@2x" file is found.
326 resolve2xLocalFile(resolvedUrl, targetDevicePixelRatio, &loadUrl, &d->devicePixelRatio);
327 }
328 }
329
330 d->status = Null; // reset status, no emit
331
332 auto engine = qmlEngine(this);
333 if (engine) {
334 d->pendingPix->load(engine,
335 loadUrl,
336 d->sourceClipRect.toRect(),
337 (loadOptions & HandleDPR) ? d->sourcesize * d->devicePixelRatio : QSize(),
338 options,
339 (loadOptions & UseProviderOptions) ? d->providerOptions : QQuickImageProviderOptions(),
340 d->currentFrame, d->frameCount,
341 d->devicePixelRatio);
342 }
343 if (d->pendingPix->isLoading()) {
344 d->setProgress(0);
345 d->setStatus(Loading);
346
347 static int thisRequestProgress = -1;
348 static int thisRequestFinished = -1;
349 if (thisRequestProgress == -1) {
350 thisRequestProgress =
351 QQuickImageBase::staticMetaObject.indexOfSlot("requestProgress(qint64,qint64)");
352 thisRequestFinished =
353 QQuickImageBase::staticMetaObject.indexOfSlot("requestFinished()");
354 }
355
356 d->pendingPix->connectFinished(this, thisRequestFinished);
357 d->pendingPix->connectDownloadProgress(this, thisRequestProgress);
358 if (!d->retainWhileLoading)
359 update(); //pixmap may have invalidated texture, updatePaintNode needs to be called before the next repaint
360 } else {
361 requestFinished();
362 }
363}
364
365void QQuickImageBase::load()
366{
367 Q_D(QQuickImageBase);
368
369 if (d->url.isEmpty()) {
370 loadEmptyUrl();
371 update();
372 } else {
373 loadPixmap(d->url, LoadPixmapOptions(HandleDPR | UseProviderOptions));
374 }
375}
376
377// Optional, but needs to be called from updatePaintNode() for full
378// QSG_TRANSIENT_IMAGES support, when no texture could be created for the
379// current pixmap. With QSG_TRANSIENT_IMAGES set, the image data is released
380// after uploading it into a texture once. When the texture is needed again, for
381// example because the item is shown in another window, or because the scene
382// graph got invalidated, the image must be loaded again.
383void QQuickImageBase::reloadIfTransientImageDropped()
384{
385 Q_D(QQuickImageBase);
386 if (d->status == Loading
387 || !d->currentPix->hasDroppedTransientImage()
388 || d->url.scheme() == QQuickPixmap::itemGrabberScheme)
389 {
390 return;
391 }
392
393 QMetaObject::invokeMethod(this, [this] {
394 Q_D(QQuickImageBase);
395 if (d->status == Loading || !d->currentPix->hasDroppedTransientImage())
396 return;
397 d->currentPix->removeDroppedTransientImageFromCache();
398 load();
399 }, Qt::QueuedConnection);
400}
401
402void QQuickImageBase::requestFinished()
403{
404 Q_D(QQuickImageBase);
405 if (d->pendingPix != d->currentPix
406 && d->pendingPix->status() != QQuickPixmap::Null
407 && d->pendingPix->status() != QQuickPixmap::Loading) {
408 std::swap(d->pendingPix, d->currentPix);
409 d->pendingPix->clear(this); // Clear the old image
410 }
411
412 if (d->currentPix->isError()) {
413 qmlWarning(this) << d->currentPix->error();
414 d->status = Error;
415 d->setProgress(0);
416 } else {
417 // itemgrabber: images carry their own devicePixelRatio (set by whatever grabbed
418 // them), which isn't known ahead of load the way "@2x" and scalable-image-format
419 // ratios are, so pick it up here once the pixmap has actually loaded.
420 if (d->url.scheme() == QQuickPixmap::itemGrabberScheme)
421 d->devicePixelRatio = d->currentPix->devicePixelRatio();
422 d->status = Ready; // do not emit statusChanged until after setImplicitSize
423 d->setProgress(1);
424 }
425
426 pixmapChange();
427 emit statusChanged(d->status);
428
429 if (sourceSize() != d->oldSourceSize) {
430 d->oldSourceSize = sourceSize();
431 emit sourceSizeChanged();
432 }
433 if (autoTransform() != d->oldAutoTransform) {
434 d->oldAutoTransform = autoTransform();
435 emitAutoTransformBaseChanged();
436 }
437 if (d->frameCount != d->currentPix->frameCount()) {
438 d->frameCount = d->currentPix->frameCount();
439 emit frameCountChanged();
440 }
441 if (d->colorSpace != d->currentPix->colorSpace()) {
442 d->colorSpace = d->currentPix->colorSpace();
443 emit colorSpaceChanged();
444 }
445
446 update();
447}
448
449void QQuickImageBase::requestProgress(qint64 received, qint64 total)
450{
451 Q_D(QQuickImageBase);
452 if (d->status == Loading && total > 0)
453 d->setProgress(qreal(received) / total);
454}
455
456void QQuickImageBase::itemChange(ItemChange change, const ItemChangeData &value)
457{
458 Q_D(QQuickImageBase);
459 // If the screen DPI changed, reload image.
460 if (change == ItemDevicePixelRatioHasChanged && value.realValue != d->devicePixelRatio) {
461 const auto oldDpr = d->devicePixelRatio;
462 // ### how can we get here with !qmlEngine(this)? that implies
463 // itemChange() on an item pending deletion, which seems strange.
464 // Note: We call load here without checking for a valid URL or
465 // any other properties, as we can't know whether the subclass'
466 // implementation of load() is based on URLs or not.
467 if (qmlEngine(this) && isComponentComplete()) {
468 load();
469 // not changed when loading (sourceSize might not be set)
470 if (d->devicePixelRatio == oldDpr) {
471 const QQmlContext *context = qmlContext(this);
472 const QUrl resolvedUrl = context ? context->resolvedUrl(d->url) : d->url;
473 d->updateDevicePixelRatio(value.realValue,
474 QQuickPixmap::isScalableImageFormat(resolvedUrl));
475 }
476 }
477 } else if (change == ItemSceneChange && d->needsReloadForDevicePixelRatio()) {
478 // If the image was loaded before the item had a window, it was loaded
479 // for qApp->devicePixelRatio(). ItemDevicePixelRatioHasChanged is not
480 // sent when the item enters a window, so reload the image if it was
481 // loaded for a different DPR. Do it when polishing, to not emit
482 // signals in the middle of reparenting.
483 polish();
484 }
485 QQuickItem::itemChange(change, value);
486}
487
488void QQuickImageBase::updatePolish()
489{
490 Q_D(QQuickImageBase);
491 if (d->needsReloadForDevicePixelRatio())
492 load();
493 QQuickItem::updatePolish();
494}
495
496void QQuickImageBase::componentComplete()
497{
498 Q_D(QQuickImageBase);
499 QQuickItem::componentComplete();
500 if (d->url.isValid())
501 load();
502}
503
504void QQuickImageBase::pixmapChange()
505{
506 Q_D(QQuickImageBase);
507 setImplicitSize(d->currentPix->width() / d->devicePixelRatio, d->currentPix->height() / d->devicePixelRatio);
508}
509
510void QQuickImageBase::resolve2xLocalFile(const QUrl &url, qreal targetDevicePixelRatio, QUrl *sourceUrl, qreal *sourceDevicePixelRatio)
511{
512 Q_ASSERT(sourceUrl);
513 Q_ASSERT(sourceDevicePixelRatio);
514
515 // Bail out if "@2x" image loading is disabled, don't change the source url or devicePixelRatio.
516 static const bool disable2xImageLoading = !qEnvironmentVariableIsEmpty("QT_HIGHDPI_DISABLE_2X_IMAGE_LOADING");
517 if (disable2xImageLoading)
518 return;
519
520 const QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
521
522 // Non-local file path: @2x loading is not supported.
523 if (localFile.isEmpty())
524 return;
525
526 // Special case: the url in the QML source refers directly to an "@2x" file.
527 int atLocation = localFile.lastIndexOf(QLatin1Char('@'));
528 if (atLocation > 0 && atLocation + 3 < localFile.size()) {
529 if (localFile[atLocation + 1].isDigit()
530 && localFile[atLocation + 2] == QLatin1Char('x')
531 && localFile[atLocation + 3] == QLatin1Char('.')) {
532 *sourceDevicePixelRatio = localFile[atLocation + 1].digitValue();
533 return;
534 }
535 }
536
537 // Look for an @2x version
538 QString localFileX = qt_findAtNxFile(localFile, targetDevicePixelRatio, sourceDevicePixelRatio);
539 if (localFileX != localFile)
540 *sourceUrl = QUrl::fromLocalFile(localFileX);
541}
542
543bool QQuickImageBase::autoTransform() const
544{
545 Q_D(const QQuickImageBase);
546 if (d->providerOptions.autoTransform() == QQuickImageProviderOptions::UsePluginDefaultTransform)
547 return d->currentPix->autoTransform() == QQuickImageProviderOptions::ApplyTransform;
548 return d->providerOptions.autoTransform() == QQuickImageProviderOptions::ApplyTransform;
549}
550
551void QQuickImageBase::setAutoTransform(bool transform)
552{
553 Q_D(QQuickImageBase);
554 if (d->providerOptions.autoTransform() != QQuickImageProviderOptions::UsePluginDefaultTransform &&
555 transform == (d->providerOptions.autoTransform() == QQuickImageProviderOptions::ApplyTransform))
556 return;
557 d->providerOptions.setAutoTransform(transform ? QQuickImageProviderOptions::ApplyTransform : QQuickImageProviderOptions::DoNotApplyTransform);
558 emitAutoTransformBaseChanged();
559}
560
561QColorSpace QQuickImageBase::colorSpace() const
562{
563 Q_D(const QQuickImageBase);
564 return d->colorSpace;
565}
566
567void QQuickImageBase::setColorSpace(const QColorSpace &colorSpace)
568{
569 Q_D(QQuickImageBase);
570 if (d->colorSpace == colorSpace)
571 return;
572 d->colorSpace = colorSpace;
573 d->providerOptions.setTargetColorSpace(colorSpace);
574 emit colorSpaceChanged();
575}
576
577bool QQuickImageBase::retainWhileLoading() const
578{
579 Q_D(const QQuickImageBase);
580 return d->retainWhileLoading;
581}
582
583void QQuickImageBase::setRetainWhileLoading(bool retainWhileLoading)
584{
585 Q_D(QQuickImageBase);
586 if (d->retainWhileLoading == retainWhileLoading)
587 return;
588
589 d->retainWhileLoading = retainWhileLoading;
590 if (d->retainWhileLoading) {
591 if (d->currentPix == &d->pix1)
592 d->pendingPix = &d->pix2;
593 else
594 d->pendingPix = &d->pix1;
595 } else {
596 d->pendingPix->clear();
597 d->pendingPix = d->currentPix;
598 }
599}
600
601QT_END_NAMESPACE
602
603#include "moc_qquickimagebase_p.cpp"
Combined button and popup list for selecting options.