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 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | using 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. |
22 | bool 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 | |
35 | void 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 | |
46 | void 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 | |
57 | QQuickImageBase::QQuickImageBase(QQuickItem *parent) |
58 | : QQuickImplicitSizeItem(*(new QQuickImageBasePrivate), parent) |
59 | { |
60 | setFlag(flag: ItemHasContents); |
61 | } |
62 | |
63 | QQuickImageBase::QQuickImageBase(QQuickImageBasePrivate &dd, QQuickItem *parent) |
64 | : QQuickImplicitSizeItem(dd, parent) |
65 | { |
66 | setFlag(flag: ItemHasContents); |
67 | } |
68 | |
69 | QQuickImageBase::~QQuickImageBase() |
70 | { |
71 | } |
72 | |
73 | QQuickImageBase::Status QQuickImageBase::status() const |
74 | { |
75 | Q_D(const QQuickImageBase); |
76 | return d->status; |
77 | } |
78 | |
79 | qreal QQuickImageBase::progress() const |
80 | { |
81 | Q_D(const QQuickImageBase); |
82 | return d->progress; |
83 | } |
84 | |
85 | bool QQuickImageBase::asynchronous() const |
86 | { |
87 | Q_D(const QQuickImageBase); |
88 | return d->async; |
89 | } |
90 | |
91 | void QQuickImageBase::setAsynchronous(bool async) |
92 | { |
93 | Q_D(QQuickImageBase); |
94 | if (d->async != async) { |
95 | d->async = async; |
96 | emit asynchronousChanged(); |
97 | } |
98 | } |
99 | |
100 | QUrl QQuickImageBase::source() const |
101 | { |
102 | Q_D(const QQuickImageBase); |
103 | return d->url; |
104 | } |
105 | |
106 | void 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 | |
120 | void 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 | |
132 | QSize 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 | |
141 | void QQuickImageBase::resetSourceSize() |
142 | { |
143 | setSourceSize(QSize()); |
144 | } |
145 | |
146 | QRectF QQuickImageBase::sourceClipRect() const |
147 | { |
148 | Q_D(const QQuickImageBase); |
149 | return d->sourceClipRect; |
150 | } |
151 | |
152 | void 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 | |
165 | void QQuickImageBase::resetSourceClipRect() |
166 | { |
167 | setSourceClipRect(QRect()); |
168 | } |
169 | |
170 | bool QQuickImageBase::cache() const |
171 | { |
172 | Q_D(const QQuickImageBase); |
173 | return d->cache; |
174 | } |
175 | |
176 | void 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 | |
188 | QImage QQuickImageBase::image() const |
189 | { |
190 | Q_D(const QQuickImageBase); |
191 | return d->currentPix->image(); |
192 | } |
193 | |
194 | void 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 | |
208 | bool QQuickImageBase::mirror() const |
209 | { |
210 | Q_D(const QQuickImageBase); |
211 | return d->mirrorHorizontally; |
212 | } |
213 | |
214 | void 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 | |
228 | bool QQuickImageBase::mirrorVertically() const |
229 | { |
230 | Q_D(const QQuickImageBase); |
231 | return d->mirrorVertically; |
232 | } |
233 | |
234 | void 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 | |
252 | int QQuickImageBase::currentFrame() const |
253 | { |
254 | Q_D(const QQuickImageBase); |
255 | return d->currentFrame; |
256 | } |
257 | |
258 | int QQuickImageBase::frameCount() const |
259 | { |
260 | Q_D(const QQuickImageBase); |
261 | return d->frameCount; |
262 | } |
263 | |
264 | void 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 | |
286 | void 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 | |
352 | void 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 | |
364 | void 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 | |
406 | void 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 | |
413 | void 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 | |
431 | void QQuickImageBase::componentComplete() |
432 | { |
433 | Q_D(QQuickImageBase); |
434 | QQuickItem::componentComplete(); |
435 | if (d->url.isValid()) |
436 | load(); |
437 | } |
438 | |
439 | void QQuickImageBase::pixmapChange() |
440 | { |
441 | Q_D(QQuickImageBase); |
442 | setImplicitSize(d->currentPix->width() / d->devicePixelRatio, d->currentPix->height() / d->devicePixelRatio); |
443 | } |
444 | |
445 | void 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 | |
478 | bool 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 | |
486 | void 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 | |
496 | QColorSpace QQuickImageBase::colorSpace() const |
497 | { |
498 | Q_D(const QQuickImageBase); |
499 | return d->colorSpace; |
500 | } |
501 | |
502 | void 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 | |
512 | bool QQuickImageBase::retainWhileLoading() const |
513 | { |
514 | Q_D(const QQuickImageBase); |
515 | return d->retainWhileLoading; |
516 | } |
517 | |
518 | void 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 | |
536 | QT_END_NAMESPACE |
537 | |
538 | #include "moc_qquickimagebase_p.cpp" |
539 |
Definitions
- updateDevicePixelRatio
- setStatus
- setProgress
- QQuickImageBase
- QQuickImageBase
- ~QQuickImageBase
- status
- progress
- asynchronous
- setAsynchronous
- source
- setSource
- setSourceSize
- sourceSize
- resetSourceSize
- sourceClipRect
- setSourceClipRect
- resetSourceClipRect
- cache
- setCache
- image
- setMirror
- mirror
- setMirrorVertically
- mirrorVertically
- setCurrentFrame
- currentFrame
- frameCount
- loadEmptyUrl
- loadPixmap
- load
- requestFinished
- requestProgress
- itemChange
- componentComplete
- pixmapChange
- resolve2xLocalFile
- autoTransform
- setAutoTransform
- colorSpace
- setColorSpace
- retainWhileLoading
Learn Advanced QML with KDAB
Find out more