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 // itemgrabber: images carry their own devicePixelRatio (set by whatever grabbed
381 // them), which isn't known ahead of load the way "@2x" and scalable-image-format
382 // ratios are, so pick it up here once the pixmap has actually loaded.
383 if (d->url.scheme() == QQuickPixmap::itemGrabberScheme)
384 d->devicePixelRatio = d->currentPix->devicePixelRatio();
385 d->status = Ready; // do not emit statusChanged until after setImplicitSize
386 d->setProgress(1);
387 }
388
389 pixmapChange();
390 emit statusChanged(d->status);
391
392 if (sourceSize() != d->oldSourceSize) {
393 d->oldSourceSize = sourceSize();
394 emit sourceSizeChanged();
395 }
396 if (autoTransform() != d->oldAutoTransform) {
397 d->oldAutoTransform = autoTransform();
398 emitAutoTransformBaseChanged();
399 }
400 if (d->frameCount != d->currentPix->frameCount()) {
401 d->frameCount = d->currentPix->frameCount();
402 emit frameCountChanged();
403 }
404 if (d->colorSpace != d->currentPix->colorSpace()) {
405 d->colorSpace = d->currentPix->colorSpace();
406 emit colorSpaceChanged();
407 }
408
409 update();
410}
411
412void QQuickImageBase::requestProgress(qint64 received, qint64 total)
413{
414 Q_D(QQuickImageBase);
415 if (d->status == Loading && total > 0)
416 d->setProgress(qreal(received) / total);
417}
418
419void QQuickImageBase::itemChange(ItemChange change, const ItemChangeData &value)
420{
421 Q_D(QQuickImageBase);
422 // If the screen DPI changed, reload image.
423 if (change == ItemDevicePixelRatioHasChanged && value.realValue != d->devicePixelRatio) {
424 const auto oldDpr = d->devicePixelRatio;
425 // ### how can we get here with !qmlEngine(this)? that implies
426 // itemChange() on an item pending deletion, which seems strange.
427 // Note: We call load here without checking for a valid URL or
428 // any other properties, as we can't know whether the subclass'
429 // implementation of load() is based on URLs or not.
430 if (qmlEngine(this) && isComponentComplete()) {
431 load();
432 // not changed when loading (sourceSize might not be set)
433 if (d->devicePixelRatio == oldDpr)
434 d->updateDevicePixelRatio(value.realValue);
435 }
436 }
437 QQuickItem::itemChange(change, value);
438}
439
440void QQuickImageBase::componentComplete()
441{
442 Q_D(QQuickImageBase);
443 QQuickItem::componentComplete();
444 if (d->url.isValid())
445 load();
446}
447
448void QQuickImageBase::pixmapChange()
449{
450 Q_D(QQuickImageBase);
451 setImplicitSize(d->currentPix->width() / d->devicePixelRatio, d->currentPix->height() / d->devicePixelRatio);
452}
453
454void QQuickImageBase::resolve2xLocalFile(const QUrl &url, qreal targetDevicePixelRatio, QUrl *sourceUrl, qreal *sourceDevicePixelRatio)
455{
456 Q_ASSERT(sourceUrl);
457 Q_ASSERT(sourceDevicePixelRatio);
458
459 // Bail out if "@2x" image loading is disabled, don't change the source url or devicePixelRatio.
460 static const bool disable2xImageLoading = !qEnvironmentVariableIsEmpty("QT_HIGHDPI_DISABLE_2X_IMAGE_LOADING");
461 if (disable2xImageLoading)
462 return;
463
464 const QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
465
466 // Non-local file path: @2x loading is not supported.
467 if (localFile.isEmpty())
468 return;
469
470 // Special case: the url in the QML source refers directly to an "@2x" file.
471 int atLocation = localFile.lastIndexOf(QLatin1Char('@'));
472 if (atLocation > 0 && atLocation + 3 < localFile.size()) {
473 if (localFile[atLocation + 1].isDigit()
474 && localFile[atLocation + 2] == QLatin1Char('x')
475 && localFile[atLocation + 3] == QLatin1Char('.')) {
476 *sourceDevicePixelRatio = localFile[atLocation + 1].digitValue();
477 return;
478 }
479 }
480
481 // Look for an @2x version
482 QString localFileX = qt_findAtNxFile(localFile, targetDevicePixelRatio, sourceDevicePixelRatio);
483 if (localFileX != localFile)
484 *sourceUrl = QUrl::fromLocalFile(localFileX);
485}
486
487bool QQuickImageBase::autoTransform() const
488{
489 Q_D(const QQuickImageBase);
490 if (d->providerOptions.autoTransform() == QQuickImageProviderOptions::UsePluginDefaultTransform)
491 return d->currentPix->autoTransform() == QQuickImageProviderOptions::ApplyTransform;
492 return d->providerOptions.autoTransform() == QQuickImageProviderOptions::ApplyTransform;
493}
494
495void QQuickImageBase::setAutoTransform(bool transform)
496{
497 Q_D(QQuickImageBase);
498 if (d->providerOptions.autoTransform() != QQuickImageProviderOptions::UsePluginDefaultTransform &&
499 transform == (d->providerOptions.autoTransform() == QQuickImageProviderOptions::ApplyTransform))
500 return;
501 d->providerOptions.setAutoTransform(transform ? QQuickImageProviderOptions::ApplyTransform : QQuickImageProviderOptions::DoNotApplyTransform);
502 emitAutoTransformBaseChanged();
503}
504
505QColorSpace QQuickImageBase::colorSpace() const
506{
507 Q_D(const QQuickImageBase);
508 return d->colorSpace;
509}
510
511void QQuickImageBase::setColorSpace(const QColorSpace &colorSpace)
512{
513 Q_D(QQuickImageBase);
514 if (d->colorSpace == colorSpace)
515 return;
516 d->colorSpace = colorSpace;
517 d->providerOptions.setTargetColorSpace(colorSpace);
518 emit colorSpaceChanged();
519}
520
521bool QQuickImageBase::retainWhileLoading() const
522{
523 Q_D(const QQuickImageBase);
524 return d->retainWhileLoading;
525}
526
527void QQuickImageBase::setRetainWhileLoading(bool retainWhileLoading)
528{
529 Q_D(QQuickImageBase);
530 if (d->retainWhileLoading == retainWhileLoading)
531 return;
532
533 d->retainWhileLoading = retainWhileLoading;
534 if (d->retainWhileLoading) {
535 if (d->currentPix == &d->pix1)
536 d->pendingPix = &d->pix2;
537 else
538 d->pendingPix = &d->pix1;
539 } else {
540 d->pendingPix->clear();
541 d->pendingPix = d->currentPix;
542 }
543}
544
545QT_END_NAMESPACE
546
547#include "moc_qquickimagebase_p.cpp"
Combined button and popup list for selecting options.