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)
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 const bool setDevicePixelRatio = QQuickPixmap::isScalableImageFormat(url);
29
30 if (setDevicePixelRatio)
31 devicePixelRatio = targetDevicePixelRatio;
32
33 return setDevicePixelRatio;
34}
35
36void QQuickImageBasePrivate::setStatus(QQuickImageBase::Status value)
37{
38 Q_Q(QQuickImageBase);
39
40 if (status == value)
41 return;
42
43 status = value;
44 emit q->statusChanged(status);
45}
46
47void QQuickImageBasePrivate::setProgress(qreal value)
48{
49 Q_Q(QQuickImageBase);
50
51 if (qFuzzyCompare(progress, value))
52 return;
53
54 progress = value;
55 emit q->progressChanged(progress);
56}
57
58QQuickImageBase::QQuickImageBase(QQuickItem *parent)
59: QQuickImplicitSizeItem(*(new QQuickImageBasePrivate), parent)
60{
61 setFlag(ItemHasContents);
62}
63
64QQuickImageBase::QQuickImageBase(QQuickImageBasePrivate &dd, QQuickItem *parent)
65: QQuickImplicitSizeItem(dd, parent)
66{
67 setFlag(ItemHasContents);
68}
69
70QQuickImageBase::~QQuickImageBase()
71{
72}
73
74QQuickImageBase::Status QQuickImageBase::status() const
75{
76 Q_D(const QQuickImageBase);
77 return d->status;
78}
79
80qreal QQuickImageBase::progress() const
81{
82 Q_D(const QQuickImageBase);
83 return d->progress;
84}
85
86bool QQuickImageBase::asynchronous() const
87{
88 Q_D(const QQuickImageBase);
89 return d->async;
90}
91
92void QQuickImageBase::setAsynchronous(bool async)
93{
94 Q_D(QQuickImageBase);
95 if (d->async != async) {
96 d->async = async;
97 emit asynchronousChanged();
98 }
99}
100
101QUrl QQuickImageBase::source() const
102{
103 Q_D(const QQuickImageBase);
104 return d->url;
105}
106
107void QQuickImageBase::setSource(const QUrl &url)
108{
109 Q_D(QQuickImageBase);
110
111 if (url == d->url)
112 return;
113
114 d->url = url;
115 emit sourceChanged(d->url);
116
117 if (isComponentComplete())
118 load();
119}
120
121void QQuickImageBase::setSourceSize(const QSize& size)
122{
123 Q_D(QQuickImageBase);
124 if (d->sourcesize == size)
125 return;
126
127 d->sourcesize = size;
128 emit sourceSizeChanged();
129 if (isComponentComplete())
130 load();
131}
132
133QSize QQuickImageBase::sourceSize() const
134{
135 Q_D(const QQuickImageBase);
136
137 int width = d->sourcesize.width();
138 int height = d->sourcesize.height();
139 return QSize(width != -1 ? width : d->currentPix->width(), height != -1 ? height : d->currentPix->height());
140}
141
142void QQuickImageBase::resetSourceSize()
143{
144 setSourceSize(QSize());
145}
146
147QRectF QQuickImageBase::sourceClipRect() const
148{
149 Q_D(const QQuickImageBase);
150 return d->sourceClipRect;
151}
152
153void QQuickImageBase::setSourceClipRect(const QRectF &r)
154{
155 Q_D(QQuickImageBase);
156 if (d->sourceClipRect == r)
157 return;
158
159 d->sourceClipRect = r;
160 d->providerOptions.setSourceClipRect(r);
161 emit sourceClipRectChanged();
162 if (isComponentComplete())
163 load();
164}
165
166void QQuickImageBase::resetSourceClipRect()
167{
168 setSourceClipRect(QRect());
169}
170
171bool QQuickImageBase::cache() const
172{
173 Q_D(const QQuickImageBase);
174 return d->cache;
175}
176
177void QQuickImageBase::setCache(bool cache)
178{
179 Q_D(QQuickImageBase);
180 if (d->cache == cache)
181 return;
182
183 d->cache = cache;
184 emit cacheChanged();
185 if (isComponentComplete())
186 load();
187}
188
189QImage QQuickImageBase::image() const
190{
191 Q_D(const QQuickImageBase);
192 return d->currentPix->image();
193}
194
195void QQuickImageBase::setMirror(bool mirror)
196{
197 Q_D(QQuickImageBase);
198 if (mirror == d->mirrorHorizontally)
199 return;
200
201 d->mirrorHorizontally = mirror;
202
203 if (isComponentComplete())
204 update();
205
206 emit mirrorChanged();
207}
208
209bool QQuickImageBase::mirror() const
210{
211 Q_D(const QQuickImageBase);
212 return d->mirrorHorizontally;
213}
214
215void QQuickImageBase::setMirrorVertically(bool mirror)
216{
217 Q_D(QQuickImageBase);
218 if (mirror == d->mirrorVertically)
219 return;
220
221 d->mirrorVertically = mirror;
222
223 if (isComponentComplete())
224 update();
225
226 emit mirrorVerticallyChanged();
227}
228
229bool QQuickImageBase::mirrorVertically() const
230{
231 Q_D(const QQuickImageBase);
232 return d->mirrorVertically;
233}
234
235void QQuickImageBase::setCurrentFrame(int frame)
236{
237 Q_D(QQuickImageBase);
238 if (frame == d->currentFrame || frame < 0 || (isComponentComplete() && frame >= d->currentPix->frameCount()))
239 return;
240
241 d->currentFrame = frame;
242
243 if (isComponentComplete()) {
244 if (frame > 0)
245 d->cache = false;
246 load();
247 update();
248 }
249
250 emit currentFrameChanged();
251}
252
253int QQuickImageBase::currentFrame() const
254{
255 Q_D(const QQuickImageBase);
256 return d->currentFrame;
257}
258
259int QQuickImageBase::frameCount() const
260{
261 Q_D(const QQuickImageBase);
262 return d->frameCount;
263}
264
265void QQuickImageBase::loadEmptyUrl()
266{
267 Q_D(QQuickImageBase);
268 d->currentPix->clear(this);
269 d->pendingPix->clear(this);
270 d->setProgress(0);
271 d->status = Null; // do not emit statusChanged until after setImplicitSize
272 setImplicitSize(0, 0); // also called in QQuickImageBase::pixmapChange, but not QQuickImage/QQuickBorderImage overrides
273 pixmapChange(); // This calls update() in QQuickBorderImage and QQuickImage, not in QQuickImageBase...
274
275 emit statusChanged(d->status);
276 if (sourceSize() != d->oldSourceSize) {
277 d->oldSourceSize = sourceSize();
278 emit sourceSizeChanged();
279 }
280 if (autoTransform() != d->oldAutoTransform) {
281 d->oldAutoTransform = autoTransform();
282 emitAutoTransformBaseChanged();
283 }
284 update(); // .. but double updating should be harmless
285}
286
287void QQuickImageBase::loadPixmap(const QUrl &url, LoadPixmapOptions loadOptions)
288{
289 Q_D(QQuickImageBase);
290 QQuickPixmap::Options options;
291 if (d->async)
292 options |= QQuickPixmap::Asynchronous;
293 if (d->cache)
294 options |= QQuickPixmap::Cache;
295 d->pendingPix->clear(this);
296 QUrl loadUrl = url;
297 const QQmlContext *context = qmlContext(this);
298 if (context)
299 loadUrl = context->resolvedUrl(url);
300
301 if (loadOptions & HandleDPR) {
302 const qreal targetDevicePixelRatio = d->effectiveDevicePixelRatio();
303 d->devicePixelRatio = 1.0;
304 bool updatedDevicePixelRatio = false;
305 if (d->sourcesize.isValid()
306 || (QQuickPixmap::isScalableImageFormat(d->url) && d->url.scheme() != "image"_L1)) {
307 updatedDevicePixelRatio = d->updateDevicePixelRatio(targetDevicePixelRatio);
308 }
309
310 if (!updatedDevicePixelRatio) {
311 // (possible) local file: loadUrl and d->devicePixelRatio will be modified if
312 // an "@2x" file is found.
313 resolve2xLocalFile(context ? context->resolvedUrl(d->url) : d->url,
314 targetDevicePixelRatio, &loadUrl, &d->devicePixelRatio);
315 }
316 }
317
318 d->status = Null; // reset status, no emit
319
320 auto engine = qmlEngine(this);
321 if (engine) {
322 d->pendingPix->load(engine,
323 loadUrl,
324 d->sourceClipRect.toRect(),
325 (loadOptions & HandleDPR) ? d->sourcesize * d->devicePixelRatio : QSize(),
326 options,
327 (loadOptions & UseProviderOptions) ? d->providerOptions : QQuickImageProviderOptions(),
328 d->currentFrame, d->frameCount,
329 d->devicePixelRatio);
330 }
331 if (d->pendingPix->isLoading()) {
332 d->setProgress(0);
333 d->setStatus(Loading);
334
335 static int thisRequestProgress = -1;
336 static int thisRequestFinished = -1;
337 if (thisRequestProgress == -1) {
338 thisRequestProgress =
339 QQuickImageBase::staticMetaObject.indexOfSlot("requestProgress(qint64,qint64)");
340 thisRequestFinished =
341 QQuickImageBase::staticMetaObject.indexOfSlot("requestFinished()");
342 }
343
344 d->pendingPix->connectFinished(this, thisRequestFinished);
345 d->pendingPix->connectDownloadProgress(this, thisRequestProgress);
346 if (!d->retainWhileLoading)
347 update(); //pixmap may have invalidated texture, updatePaintNode needs to be called before the next repaint
348 } else {
349 requestFinished();
350 }
351}
352
353void QQuickImageBase::load()
354{
355 Q_D(QQuickImageBase);
356
357 if (d->url.isEmpty()) {
358 loadEmptyUrl();
359 update();
360 } else {
361 loadPixmap(d->url, LoadPixmapOptions(HandleDPR | UseProviderOptions));
362 }
363}
364
365void QQuickImageBase::requestFinished()
366{
367 Q_D(QQuickImageBase);
368 if (d->pendingPix != d->currentPix
369 && d->pendingPix->status() != QQuickPixmap::Null
370 && d->pendingPix->status() != QQuickPixmap::Loading) {
371 std::swap(d->pendingPix, d->currentPix);
372 d->pendingPix->clear(this); // Clear the old image
373 }
374
375 if (d->currentPix->isError()) {
376 qmlWarning(this) << d->currentPix->error();
377 d->status = Error;
378 d->setProgress(0);
379 } else {
380 d->status = Ready; // do not emit statusChanged until after setImplicitSize
381 d->setProgress(1);
382 }
383
384 pixmapChange();
385 emit statusChanged(d->status);
386
387 if (sourceSize() != d->oldSourceSize) {
388 d->oldSourceSize = sourceSize();
389 emit sourceSizeChanged();
390 }
391 if (autoTransform() != d->oldAutoTransform) {
392 d->oldAutoTransform = autoTransform();
393 emitAutoTransformBaseChanged();
394 }
395 if (d->frameCount != d->currentPix->frameCount()) {
396 d->frameCount = d->currentPix->frameCount();
397 emit frameCountChanged();
398 }
399 if (d->colorSpace != d->currentPix->colorSpace()) {
400 d->colorSpace = d->currentPix->colorSpace();
401 emit colorSpaceChanged();
402 }
403
404 update();
405}
406
407void QQuickImageBase::requestProgress(qint64 received, qint64 total)
408{
409 Q_D(QQuickImageBase);
410 if (d->status == Loading && total > 0)
411 d->setProgress(qreal(received) / total);
412}
413
414void QQuickImageBase::itemChange(ItemChange change, const ItemChangeData &value)
415{
416 Q_D(QQuickImageBase);
417 // If the screen DPI changed, reload image.
418 if (change == ItemDevicePixelRatioHasChanged && value.realValue != d->devicePixelRatio) {
419 const auto oldDpr = d->devicePixelRatio;
420 // ### how can we get here with !qmlEngine(this)? that implies
421 // itemChange() on an item pending deletion, which seems strange.
422 // Note: We call load here without checking for a valid URL or
423 // any other properties, as we can't know whether the subclass'
424 // implementation of load() is based on URLs or not.
425 if (qmlEngine(this) && isComponentComplete()) {
426 load();
427 // not changed when loading (sourceSize might not be set)
428 if (d->devicePixelRatio == oldDpr)
429 d->updateDevicePixelRatio(value.realValue);
430 }
431 }
432 QQuickItem::itemChange(change, value);
433}
434
435void QQuickImageBase::componentComplete()
436{
437 Q_D(QQuickImageBase);
438 QQuickItem::componentComplete();
439 if (d->url.isValid())
440 load();
441}
442
443void QQuickImageBase::pixmapChange()
444{
445 Q_D(QQuickImageBase);
446 setImplicitSize(d->currentPix->width() / d->devicePixelRatio, d->currentPix->height() / d->devicePixelRatio);
447}
448
449void QQuickImageBase::resolve2xLocalFile(const QUrl &url, qreal targetDevicePixelRatio, QUrl *sourceUrl, qreal *sourceDevicePixelRatio)
450{
451 Q_ASSERT(sourceUrl);
452 Q_ASSERT(sourceDevicePixelRatio);
453
454 // Bail out if "@2x" image loading is disabled, don't change the source url or devicePixelRatio.
455 static const bool disable2xImageLoading = !qEnvironmentVariableIsEmpty("QT_HIGHDPI_DISABLE_2X_IMAGE_LOADING");
456 if (disable2xImageLoading)
457 return;
458
459 const QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
460
461 // Non-local file path: @2x loading is not supported.
462 if (localFile.isEmpty())
463 return;
464
465 // Special case: the url in the QML source refers directly to an "@2x" file.
466 int atLocation = localFile.lastIndexOf(QLatin1Char('@'));
467 if (atLocation > 0 && atLocation + 3 < localFile.size()) {
468 if (localFile[atLocation + 1].isDigit()
469 && localFile[atLocation + 2] == QLatin1Char('x')
470 && localFile[atLocation + 3] == QLatin1Char('.')) {
471 *sourceDevicePixelRatio = localFile[atLocation + 1].digitValue();
472 return;
473 }
474 }
475
476 // Look for an @2x version
477 QString localFileX = qt_findAtNxFile(localFile, targetDevicePixelRatio, sourceDevicePixelRatio);
478 if (localFileX != localFile)
479 *sourceUrl = QUrl::fromLocalFile(localFileX);
480}
481
482bool QQuickImageBase::autoTransform() const
483{
484 Q_D(const QQuickImageBase);
485 if (d->providerOptions.autoTransform() == QQuickImageProviderOptions::UsePluginDefaultTransform)
486 return d->currentPix->autoTransform() == QQuickImageProviderOptions::ApplyTransform;
487 return d->providerOptions.autoTransform() == QQuickImageProviderOptions::ApplyTransform;
488}
489
490void QQuickImageBase::setAutoTransform(bool transform)
491{
492 Q_D(QQuickImageBase);
493 if (d->providerOptions.autoTransform() != QQuickImageProviderOptions::UsePluginDefaultTransform &&
494 transform == (d->providerOptions.autoTransform() == QQuickImageProviderOptions::ApplyTransform))
495 return;
496 d->providerOptions.setAutoTransform(transform ? QQuickImageProviderOptions::ApplyTransform : QQuickImageProviderOptions::DoNotApplyTransform);
497 emitAutoTransformBaseChanged();
498}
499
500QColorSpace QQuickImageBase::colorSpace() const
501{
502 Q_D(const QQuickImageBase);
503 return d->colorSpace;
504}
505
506void QQuickImageBase::setColorSpace(const QColorSpace &colorSpace)
507{
508 Q_D(QQuickImageBase);
509 if (d->colorSpace == colorSpace)
510 return;
511 d->colorSpace = colorSpace;
512 d->providerOptions.setTargetColorSpace(colorSpace);
513 emit colorSpaceChanged();
514}
515
516bool QQuickImageBase::retainWhileLoading() const
517{
518 Q_D(const QQuickImageBase);
519 return d->retainWhileLoading;
520}
521
522void QQuickImageBase::setRetainWhileLoading(bool retainWhileLoading)
523{
524 Q_D(QQuickImageBase);
525 if (d->retainWhileLoading == retainWhileLoading)
526 return;
527
528 d->retainWhileLoading = retainWhileLoading;
529 if (d->retainWhileLoading) {
530 if (d->currentPix == &d->pix1)
531 d->pendingPix = &d->pix2;
532 else
533 d->pendingPix = &d->pix1;
534 } else {
535 d->pendingPix->clear();
536 d->pendingPix = d->currentPix;
537 }
538}
539
540QT_END_NAMESPACE
541
542#include "moc_qquickimagebase_p.cpp"