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 = (window() ? window()->effectiveDevicePixelRatio() : qApp->devicePixelRatio());
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 if (qmlEngine(this) && isComponentComplete() && d->url.isValid()) {
422 load();
423 // not changed when loading (sourceSize might not be set)
424 if (d->devicePixelRatio == oldDpr)
425 d->updateDevicePixelRatio(targetDevicePixelRatio: value.realValue);
426 }
427 }
428 QQuickItem::itemChange(change, value);
429}
430
431void QQuickImageBase::componentComplete()
432{
433 Q_D(QQuickImageBase);
434 QQuickItem::componentComplete();
435 if (d->url.isValid())
436 load();
437}
438
439void QQuickImageBase::pixmapChange()
440{
441 Q_D(QQuickImageBase);
442 setImplicitSize(d->currentPix->width() / d->devicePixelRatio, d->currentPix->height() / d->devicePixelRatio);
443}
444
445void QQuickImageBase::resolve2xLocalFile(const QUrl &url, qreal targetDevicePixelRatio, QUrl *sourceUrl, qreal *sourceDevicePixelRatio)
446{
447 Q_ASSERT(sourceUrl);
448 Q_ASSERT(sourceDevicePixelRatio);
449
450 // Bail out if "@2x" image loading is disabled, don't change the source url or devicePixelRatio.
451 static const bool disable2xImageLoading = !qEnvironmentVariableIsEmpty(varName: "QT_HIGHDPI_DISABLE_2X_IMAGE_LOADING");
452 if (disable2xImageLoading)
453 return;
454
455 const QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
456
457 // Non-local file path: @2x loading is not supported.
458 if (localFile.isEmpty())
459 return;
460
461 // Special case: the url in the QML source refers directly to an "@2x" file.
462 int atLocation = localFile.lastIndexOf(c: QLatin1Char('@'));
463 if (atLocation > 0 && atLocation + 3 < localFile.size()) {
464 if (localFile[atLocation + 1].isDigit()
465 && localFile[atLocation + 2] == QLatin1Char('x')
466 && localFile[atLocation + 3] == QLatin1Char('.')) {
467 *sourceDevicePixelRatio = localFile[atLocation + 1].digitValue();
468 return;
469 }
470 }
471
472 // Look for an @2x version
473 QString localFileX = qt_findAtNxFile(baseFileName: localFile, targetDevicePixelRatio, sourceDevicePixelRatio);
474 if (localFileX != localFile)
475 *sourceUrl = QUrl::fromLocalFile(localfile: localFileX);
476}
477
478bool QQuickImageBase::autoTransform() const
479{
480 Q_D(const QQuickImageBase);
481 if (d->providerOptions.autoTransform() == QQuickImageProviderOptions::UsePluginDefaultTransform)
482 return d->currentPix->autoTransform() == QQuickImageProviderOptions::ApplyTransform;
483 return d->providerOptions.autoTransform() == QQuickImageProviderOptions::ApplyTransform;
484}
485
486void QQuickImageBase::setAutoTransform(bool transform)
487{
488 Q_D(QQuickImageBase);
489 if (d->providerOptions.autoTransform() != QQuickImageProviderOptions::UsePluginDefaultTransform &&
490 transform == (d->providerOptions.autoTransform() == QQuickImageProviderOptions::ApplyTransform))
491 return;
492 d->providerOptions.setAutoTransform(transform ? QQuickImageProviderOptions::ApplyTransform : QQuickImageProviderOptions::DoNotApplyTransform);
493 emitAutoTransformBaseChanged();
494}
495
496QColorSpace QQuickImageBase::colorSpace() const
497{
498 Q_D(const QQuickImageBase);
499 return d->colorSpace;
500}
501
502void QQuickImageBase::setColorSpace(const QColorSpace &colorSpace)
503{
504 Q_D(QQuickImageBase);
505 if (d->colorSpace == colorSpace)
506 return;
507 d->colorSpace = colorSpace;
508 d->providerOptions.setTargetColorSpace(colorSpace);
509 emit colorSpaceChanged();
510}
511
512bool QQuickImageBase::retainWhileLoading() const
513{
514 Q_D(const QQuickImageBase);
515 return d->retainWhileLoading;
516}
517
518void QQuickImageBase::setRetainWhileLoading(bool retainWhileLoading)
519{
520 Q_D(QQuickImageBase);
521 if (d->retainWhileLoading == retainWhileLoading)
522 return;
523
524 d->retainWhileLoading = retainWhileLoading;
525 if (d->retainWhileLoading) {
526 if (d->currentPix == &d->pix1)
527 d->pendingPix = &d->pix2;
528 else
529 d->pendingPix = &d->pix1;
530 } else {
531 d->pendingPix->clear();
532 d->pendingPix = d->currentPix;
533 }
534}
535
536QT_END_NAMESPACE
537
538#include "moc_qquickimagebase_p.cpp"
539

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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