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

source code of qtdeclarative/src/quick/items/qquickimagebase.cpp