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

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