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 | |
52 | QT_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. |
56 | bool 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 | |
79 | QQuickImageBase::QQuickImageBase(QQuickItem *parent) |
80 | : QQuickImplicitSizeItem(*(new QQuickImageBasePrivate), parent) |
81 | { |
82 | setFlag(flag: ItemHasContents); |
83 | } |
84 | |
85 | QQuickImageBase::QQuickImageBase(QQuickImageBasePrivate &dd, QQuickItem *parent) |
86 | : QQuickImplicitSizeItem(dd, parent) |
87 | { |
88 | setFlag(flag: ItemHasContents); |
89 | } |
90 | |
91 | QQuickImageBase::~QQuickImageBase() |
92 | { |
93 | } |
94 | |
95 | QQuickImageBase::Status QQuickImageBase::status() const |
96 | { |
97 | Q_D(const QQuickImageBase); |
98 | return d->status; |
99 | } |
100 | |
101 | |
102 | qreal QQuickImageBase::progress() const |
103 | { |
104 | Q_D(const QQuickImageBase); |
105 | return d->progress; |
106 | } |
107 | |
108 | |
109 | bool QQuickImageBase::asynchronous() const |
110 | { |
111 | Q_D(const QQuickImageBase); |
112 | return d->async; |
113 | } |
114 | |
115 | void QQuickImageBase::setAsynchronous(bool async) |
116 | { |
117 | Q_D(QQuickImageBase); |
118 | if (d->async != async) { |
119 | d->async = async; |
120 | emit asynchronousChanged(); |
121 | } |
122 | } |
123 | |
124 | QUrl QQuickImageBase::source() const |
125 | { |
126 | Q_D(const QQuickImageBase); |
127 | return d->url; |
128 | } |
129 | |
130 | void 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 | |
144 | void 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 | |
156 | QSize 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 | |
165 | void QQuickImageBase::resetSourceSize() |
166 | { |
167 | setSourceSize(QSize()); |
168 | } |
169 | |
170 | QRectF QQuickImageBase::sourceClipRect() const |
171 | { |
172 | Q_D(const QQuickImageBase); |
173 | return d->sourceClipRect; |
174 | } |
175 | |
176 | void 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 | |
188 | void QQuickImageBase::resetSourceClipRect() |
189 | { |
190 | setSourceClipRect(QRect()); |
191 | } |
192 | |
193 | bool QQuickImageBase::cache() const |
194 | { |
195 | Q_D(const QQuickImageBase); |
196 | return d->cache; |
197 | } |
198 | |
199 | void 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 | |
211 | QImage QQuickImageBase::image() const |
212 | { |
213 | Q_D(const QQuickImageBase); |
214 | return d->pix.image(); |
215 | } |
216 | |
217 | void 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 | |
231 | bool QQuickImageBase::mirror() const |
232 | { |
233 | Q_D(const QQuickImageBase); |
234 | return d->mirror; |
235 | } |
236 | |
237 | void 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 | |
255 | int QQuickImageBase::currentFrame() const |
256 | { |
257 | Q_D(const QQuickImageBase); |
258 | return d->currentFrame; |
259 | } |
260 | |
261 | int QQuickImageBase::frameCount() const |
262 | { |
263 | Q_D(const QQuickImageBase); |
264 | return d->frameCount; |
265 | } |
266 | |
267 | void 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 | |
291 | void 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 | |
354 | void 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 | |
366 | void 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 | |
407 | void 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 | |
416 | void 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 | |
434 | void QQuickImageBase::componentComplete() |
435 | { |
436 | Q_D(QQuickImageBase); |
437 | QQuickItem::componentComplete(); |
438 | if (d->url.isValid()) |
439 | load(); |
440 | } |
441 | |
442 | void QQuickImageBase::pixmapChange() |
443 | { |
444 | Q_D(QQuickImageBase); |
445 | setImplicitSize(d->pix.width() / d->devicePixelRatio, d->pix.height() / d->devicePixelRatio); |
446 | } |
447 | |
448 | void 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 | |
481 | bool 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 | |
489 | void 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 | |
499 | QColorSpace QQuickImageBase::colorSpace() const |
500 | { |
501 | Q_D(const QQuickImageBase); |
502 | return d->colorSpace; |
503 | } |
504 | |
505 | void 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 | |
515 | QT_END_NAMESPACE |
516 | |
517 | #include "moc_qquickimagebase_p.cpp" |
518 | |