1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickimagebase_p.h"
41#include "qquickimagebase_p_p.h"
42
43#include <QtGui/qguiapplication.h>
44#include <QtGui/qscreen.h>
45#include <QtGui/qicon.h>
46
47#include <QtQml/qqmlinfo.h>
48#include <QtQml/qqmlfile.h>
49#include <QtQml/qqmlabstracturlinterceptor.h>
50
51
52QT_BEGIN_NAMESPACE
53
54// This function gives derived classes the chance set the devicePixelRatio
55// if they're not happy with our implementation of it.
56bool QQuickImageBasePrivate::updateDevicePixelRatio(qreal targetDevicePixelRatio)
57{
58 // QQuickImageProvider and SVG and PDF can generate a high resolution image when
59 // sourceSize is set. If sourceSize is not set then the provider default size will
60 // be used, as usual.
61 bool setDevicePixelRatio = false;
62 if (url.scheme() == QLatin1String("image")) {
63 setDevicePixelRatio = true;
64 } else {
65 QString stringUrl = url.path(options: QUrl::PrettyDecoded);
66 if (stringUrl.endsWith(s: QLatin1String("svg")) ||
67 stringUrl.endsWith(s: QLatin1String("svgz")) ||
68 stringUrl.endsWith(s: QLatin1String("pdf"))) {
69 setDevicePixelRatio = true;
70 }
71 }
72
73 if (setDevicePixelRatio)
74 devicePixelRatio = targetDevicePixelRatio;
75
76 return setDevicePixelRatio;
77}
78
79QQuickImageBase::QQuickImageBase(QQuickItem *parent)
80: QQuickImplicitSizeItem(*(new QQuickImageBasePrivate), parent)
81{
82 setFlag(flag: ItemHasContents);
83}
84
85QQuickImageBase::QQuickImageBase(QQuickImageBasePrivate &dd, QQuickItem *parent)
86: QQuickImplicitSizeItem(dd, parent)
87{
88 setFlag(flag: ItemHasContents);
89}
90
91QQuickImageBase::~QQuickImageBase()
92{
93}
94
95QQuickImageBase::Status QQuickImageBase::status() const
96{
97 Q_D(const QQuickImageBase);
98 return d->status;
99}
100
101
102qreal QQuickImageBase::progress() const
103{
104 Q_D(const QQuickImageBase);
105 return d->progress;
106}
107
108
109bool QQuickImageBase::asynchronous() const
110{
111 Q_D(const QQuickImageBase);
112 return d->async;
113}
114
115void QQuickImageBase::setAsynchronous(bool async)
116{
117 Q_D(QQuickImageBase);
118 if (d->async != async) {
119 d->async = async;
120 emit asynchronousChanged();
121 }
122}
123
124QUrl QQuickImageBase::source() const
125{
126 Q_D(const QQuickImageBase);
127 return d->url;
128}
129
130void QQuickImageBase::setSource(const QUrl &url)
131{
132 Q_D(QQuickImageBase);
133
134 if (url == d->url)
135 return;
136
137 d->url = url;
138 emit sourceChanged(d->url);
139
140 if (isComponentComplete())
141 load();
142}
143
144void QQuickImageBase::setSourceSize(const QSize& size)
145{
146 Q_D(QQuickImageBase);
147 if (d->sourcesize == size)
148 return;
149
150 d->sourcesize = size;
151 emit sourceSizeChanged();
152 if (isComponentComplete())
153 load();
154}
155
156QSize QQuickImageBase::sourceSize() const
157{
158 Q_D(const QQuickImageBase);
159
160 int width = d->sourcesize.width();
161 int height = d->sourcesize.height();
162 return QSize(width != -1 ? width : d->pix.width(), height != -1 ? height : d->pix.height());
163}
164
165void QQuickImageBase::resetSourceSize()
166{
167 setSourceSize(QSize());
168}
169
170QRectF QQuickImageBase::sourceClipRect() const
171{
172 Q_D(const QQuickImageBase);
173 return d->sourceClipRect;
174}
175
176void QQuickImageBase::setSourceClipRect(const QRectF &r)
177{
178 Q_D(QQuickImageBase);
179 if (d->sourceClipRect == r)
180 return;
181
182 d->sourceClipRect = r;
183 emit sourceClipRectChanged();
184 if (isComponentComplete())
185 load();
186}
187
188void QQuickImageBase::resetSourceClipRect()
189{
190 setSourceClipRect(QRect());
191}
192
193bool QQuickImageBase::cache() const
194{
195 Q_D(const QQuickImageBase);
196 return d->cache;
197}
198
199void QQuickImageBase::setCache(bool cache)
200{
201 Q_D(QQuickImageBase);
202 if (d->cache == cache)
203 return;
204
205 d->cache = cache;
206 emit cacheChanged();
207 if (isComponentComplete())
208 load();
209}
210
211QImage QQuickImageBase::image() const
212{
213 Q_D(const QQuickImageBase);
214 return d->pix.image();
215}
216
217void QQuickImageBase::setMirror(bool mirror)
218{
219 Q_D(QQuickImageBase);
220 if (mirror == d->mirror)
221 return;
222
223 d->mirror = mirror;
224
225 if (isComponentComplete())
226 update();
227
228 emit mirrorChanged();
229}
230
231bool QQuickImageBase::mirror() const
232{
233 Q_D(const QQuickImageBase);
234 return d->mirror;
235}
236
237void QQuickImageBase::setCurrentFrame(int frame)
238{
239 Q_D(QQuickImageBase);
240 if (frame == d->currentFrame || frame < 0 || (isComponentComplete() && frame >= d->pix.frameCount()))
241 return;
242
243 d->currentFrame = frame;
244
245 if (isComponentComplete()) {
246 if (frame > 0)
247 d->cache = false;
248 load();
249 update();
250 }
251
252 emit currentFrameChanged();
253}
254
255int QQuickImageBase::currentFrame() const
256{
257 Q_D(const QQuickImageBase);
258 return d->currentFrame;
259}
260
261int QQuickImageBase::frameCount() const
262{
263 Q_D(const QQuickImageBase);
264 return d->frameCount;
265}
266
267void QQuickImageBase::loadEmptyUrl()
268{
269 Q_D(QQuickImageBase);
270 d->pix.clear(this);
271 if (d->progress != 0.0) {
272 d->progress = 0.0;
273 emit progressChanged(progress: d->progress);
274 }
275 d->status = Null;
276 setImplicitSize(0, 0); // also called in QQuickImageBase::pixmapChange, but not QQuickImage/QQuickBorderImage overrides
277 pixmapChange(); // This calls update() in QQuickBorderImage and QQuickImage, not in QQuickImageBase...
278
279 emit statusChanged(d->status);
280 if (sourceSize() != d->oldSourceSize) {
281 d->oldSourceSize = sourceSize();
282 emit sourceSizeChanged();
283 }
284 if (autoTransform() != d->oldAutoTransform) {
285 d->oldAutoTransform = autoTransform();
286 emitAutoTransformBaseChanged();
287 }
288 update(); // .. but double updating should be harmless
289}
290
291void QQuickImageBase::loadPixmap(const QUrl &url, LoadPixmapOptions loadOptions)
292{
293 Q_D(QQuickImageBase);
294 QQuickPixmap::Options options;
295 if (d->async)
296 options |= QQuickPixmap::Asynchronous;
297 if (d->cache)
298 options |= QQuickPixmap::Cache;
299 d->pix.clear(this);
300 QUrl loadUrl = url;
301 QQmlEngine* engine = qmlEngine(this);
302 if (engine && engine->urlInterceptor())
303 loadUrl = engine->urlInterceptor()->intercept(path: loadUrl, type: QQmlAbstractUrlInterceptor::UrlString);
304
305 if (loadOptions & HandleDPR) {
306 const qreal targetDevicePixelRatio = (window() ? window()->effectiveDevicePixelRatio() : qApp->devicePixelRatio());
307 d->devicePixelRatio = 1.0;
308 bool updatedDevicePixelRatio = false;
309 if (d->sourcesize.isValid())
310 updatedDevicePixelRatio = d->updateDevicePixelRatio(targetDevicePixelRatio);
311
312 if (!updatedDevicePixelRatio) {
313 // (possible) local file: loadUrl and d->devicePixelRatio will be modified if
314 // an "@2x" file is found.
315 resolve2xLocalFile(url: d->url, targetDevicePixelRatio, sourceUrl: &loadUrl, sourceDevicePixelRatio: &d->devicePixelRatio);
316 }
317 }
318
319 d->pix.load(qmlEngine(this),
320 loadUrl,
321 requestRegion: d->sourceClipRect.toRect(),
322 requestSize: (loadOptions & HandleDPR) ? d->sourcesize * d->devicePixelRatio : QSize(),
323 options,
324 providerOptions: (loadOptions & UseProviderOptions) ? d->providerOptions : QQuickImageProviderOptions(),
325 frame: d->currentFrame, frameCount: d->frameCount);
326
327 if (d->pix.isLoading()) {
328 if (d->progress != 0.0) {
329 d->progress = 0.0;
330 emit progressChanged(progress: d->progress);
331 }
332 if (d->status != Loading) {
333 d->status = Loading;
334 emit statusChanged(d->status);
335 }
336
337 static int thisRequestProgress = -1;
338 static int thisRequestFinished = -1;
339 if (thisRequestProgress == -1) {
340 thisRequestProgress =
341 QQuickImageBase::staticMetaObject.indexOfSlot(slot: "requestProgress(qint64,qint64)");
342 thisRequestFinished =
343 QQuickImageBase::staticMetaObject.indexOfSlot(slot: "requestFinished()");
344 }
345
346 d->pix.connectFinished(this, thisRequestFinished);
347 d->pix.connectDownloadProgress(this, thisRequestProgress);
348 update(); //pixmap may have invalidated texture, updatePaintNode needs to be called before the next repaint
349 } else {
350 requestFinished();
351 }
352}
353
354void QQuickImageBase::load()
355{
356 Q_D(QQuickImageBase);
357
358 if (d->url.isEmpty()) {
359 loadEmptyUrl();
360 update();
361 } else {
362 loadPixmap(url: d->url, loadOptions: LoadPixmapOptions(HandleDPR | UseProviderOptions));
363 }
364}
365
366void QQuickImageBase::requestFinished()
367{
368 Q_D(QQuickImageBase);
369
370 if (d->pix.isError()) {
371 qmlWarning(me: this) << d->pix.error();
372 d->pix.clear(this);
373 d->status = Error;
374 if (d->progress != 0.0) {
375 d->progress = 0.0;
376 emit progressChanged(progress: d->progress);
377 }
378 } else {
379 d->status = Ready;
380 if (d->progress != 1.0) {
381 d->progress = 1.0;
382 emit progressChanged(progress: d->progress);
383 }
384 }
385 pixmapChange();
386 emit statusChanged(d->status);
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->pix.frameCount()) {
396 d->frameCount = d->pix.frameCount();
397 emit frameCountChanged();
398 }
399 if (d->colorSpace != d->pix.colorSpace()) {
400 d->colorSpace = d->pix.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->progress = qreal(received)/total;
412 emit progressChanged(progress: d->progress);
413 }
414}
415
416void QQuickImageBase::itemChange(ItemChange change, const ItemChangeData &value)
417{
418 Q_D(QQuickImageBase);
419 // If the screen DPI changed, reload image.
420 if (change == ItemDevicePixelRatioHasChanged && value.realValue != d->devicePixelRatio) {
421 const auto oldDpr = d->devicePixelRatio;
422 // ### how can we get here with !qmlEngine(this)? that implies
423 // itemChange() on an item pending deletion, which seems strange.
424 if (qmlEngine(this) && isComponentComplete() && d->url.isValid()) {
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->pix.width() / d->devicePixelRatio, d->pix.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->pix.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
515QT_END_NAMESPACE
516
517#include "moc_qquickimagebase_p.cpp"
518

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