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 "qquickimage_p.h"
5#include "qquickimage_p_p.h"
6
7#include <QtQuick/qsgtextureprovider.h>
8
9#include <QtQuick/private/qsgcontext_p.h>
10#include <private/qsgadaptationlayer_p.h>
11#include <private/qnumeric_p.h>
12
13#include <QtCore/qmath.h>
14#include <QtGui/qpainter.h>
15#include <QtCore/QRunnable>
16
17QT_BEGIN_NAMESPACE
18
19QQuickImageTextureProvider::QQuickImageTextureProvider()
20 : m_texture(nullptr)
21 , m_smooth(false)
22{
23}
24
25void QQuickImageTextureProvider::updateTexture(QSGTexture *texture) {
26 if (m_texture == texture)
27 return;
28
29 if (m_texture)
30 disconnect(sender: m_texture, signal: &QSGTexture::destroyed, receiver: this, zero: nullptr);
31
32 m_texture = texture;
33
34 if (m_texture)
35 connect(sender: m_texture, signal: &QSGTexture::destroyed, context: this, slot: [this]() { updateTexture(texture: nullptr); });
36
37 emit textureChanged();
38}
39
40QSGTexture *QQuickImageTextureProvider::texture() const {
41 if (m_texture) {
42 m_texture->setFiltering(m_smooth ? QSGTexture::Linear : QSGTexture::Nearest);
43 m_texture->setMipmapFiltering(m_mipmap ? QSGTexture::Linear : QSGTexture::None);
44 m_texture->setHorizontalWrapMode(QSGTexture::ClampToEdge);
45 m_texture->setVerticalWrapMode(QSGTexture::ClampToEdge);
46 }
47 return m_texture;
48}
49
50QQuickImagePrivate::QQuickImagePrivate()
51 : pixmapChanged(false)
52 , mipmap(false)
53{
54}
55
56/*!
57 \qmltype Image
58 \nativetype QQuickImage
59 \inqmlmodule QtQuick
60 \ingroup qtquick-visual
61 \inherits Item
62 \brief Displays an image.
63
64 The Image type displays an image.
65
66 The source of the image is specified as a URL using the \l source property.
67 Images can be supplied in any of the standard image formats supported by Qt,
68 including bitmap formats such as PNG and JPEG, and vector graphics formats
69 such as SVG. If you need to display animated images, use \l AnimatedSprite
70 or \l AnimatedImage.
71
72 If the \l{Item::width}{width} and \l{Item::height}{height} properties are not
73 specified, the Image automatically uses the size of the loaded image.
74 By default, specifying the width and height of the item causes the image
75 to be scaled to that size. This behavior can be changed by setting the
76 \l fillMode property, allowing the image to be stretched and tiled instead.
77
78 It is possible to provide \l {High Resolution Versions of Images}{"@nx" high DPI syntax}.
79
80 \section1 Example Usage
81
82 The following example shows the simplest usage of the Image type.
83
84 \snippet qml/image.qml document
85
86 \beginfloatleft
87 \image declarative-qtlogo.png
88 \endfloat
89
90 \clearfloat
91
92 \section1 Compressed Texture Files
93
94 When supported by the implementation of the underlying graphics API at run
95 time, images can also be supplied in compressed texture files. The content
96 must be a simple RGB(A) format 2D texture. Supported compression schemes are
97 only limited by the underlying driver and GPU. The following container file
98 formats are supported:
99
100 \list
101 \li \c PKM (since Qt 5.10)
102 \li \c KTX (since Qt 5.11)
103 \li \c ASTC (since Qt 5.13)
104 \endlist
105
106 \note The intended vertical orientation of an image in a texture file is not generally well
107 defined. Different texture compression tools have different defaults and options of when to
108 perform vertical flipping of the input image. If an image from a texture file appears upside
109 down, flipping may need to be toggled in the asset conditioning process. Alternatively, the
110 Image element itself can be flipped by either applying a suitable transformation via the
111 transform property or, more conveniently, by setting the mirrorVertically property:
112 \badcode
113 transform: [ Translate { y: -myImage.height }, Scale { yScale: -1 } ]
114 \endcode
115 or
116 \badcode
117 mirrorVertically: true
118 \endcode
119
120 \note Semi-transparent original images require alpha pre-multiplication
121 prior to texture compression in order to be correctly displayed in Qt
122 Quick. This can be done with the following ImageMagick command
123 line:
124 \badcode
125 convert foo.png \( +clone -alpha Extract \) -channel RGB -compose Multiply -composite foo_pm.png
126 \endcode
127
128 Do not confuse container formats, such as, \c KTX, and the format of the
129 actual texture data stored in the container file. For example, reading a
130 \c KTX file is supported on all platforms, independently of what GPU driver is
131 used at run time. However, this does not guarantee that the compressed
132 texture format, used by the data in the file, is supported at run time. For
133 example, if the KTX file contains compressed data with the format
134 \c{ETC2 RGBA8}, and the 3D graphics API implementation used at run time does not
135 support \c ETC2 compressed textures, the Image item will not display
136 anything.
137
138 \note Compressed texture format support is not under Qt's control, and it
139 is up to the application or device developer to ensure the compressed
140 texture data is provided in the appropriate format for the target
141 environment(s).
142
143 Do not assume that compressed format support is specific to a platform. It
144 may also be specific to the driver and 3D API implementation in use on that
145 particular platform. In practice, implementations of different 3D graphics
146 APIs (e.g., Vulkan and OpenGL) on the same platform (e.g., Windows) from
147 the same vendor for the same hardware may offer a different set of
148 compressed texture formats.
149
150 When targeting desktop environments (Windows, macOS, Linux) only, a general
151 recommendation is to consider using the \c{DXTn}/\c{BCn} formats since
152 these tend to have the widest support amongst the implementations of Direct
153 3D, Vulkan, OpenGL, and Metal on these platforms. In contrast, when
154 targeting mobile or embedded devices, the \c ETC2 or \c ASTC formats are
155 likely to be a better choice since these are typically the formats
156 supported by the OpenGL ES implementations on such hardware.
157
158 An application that intends to run across desktop, mobile, and embedded
159 hardware should plan and design its use of compressed textures carefully.
160 It is highly likely that relying on a single format is not going to be
161 sufficient, and therefore the application will likely need to branch based
162 on the platform to use compressed textures in a format appropriate there,
163 or perhaps to skip using compressed textures in some cases.
164
165 \section1 Automatic Detection of File Extension
166
167 If the \l source URL indicates a non-existing local file or resource, the
168 Image element attempts to auto-detect the file extension. If an existing
169 file can be found by appending any of the supported image file extensions
170 to the \l source URL, then that file will be loaded.
171
172 The file search attempts to look for compressed texture container file
173 extensions first. If the search is unsuccessful, it attempts to search with
174 the file extensions for the
175 \l{QImageReader::supportedImageFormats()}{conventional image file
176 types}. For example:
177
178 \snippet qml/image-ext.qml ext
179
180 This functionality facilitates deploying different image asset file types
181 on different target platforms. This can be useful in order to tune
182 application performance and adapt to different graphics hardware.
183
184 This functionality was introduced in Qt 5.11.
185
186 \section1 Performance
187
188 By default, locally available images are loaded immediately, and the user interface
189 is blocked until loading is complete. If a large image is to be loaded, it may be
190 preferable to load the image in a low priority thread, by enabling the \l asynchronous
191 property.
192
193 If the image is obtained from a network rather than a local resource, it is
194 automatically loaded asynchronously, and the \l progress and \l status properties
195 are updated as appropriate.
196
197 Images are cached and shared internally, so if several Image items have the same \l source,
198 only one copy of the image will be loaded.
199
200 \b Note: Images are often the greatest user of memory in QML user interfaces. It is recommended
201 that images which do not form part of the user interface have their
202 size bounded via the \l sourceSize property. This is especially important for content
203 that is loaded from external sources or provided by the user.
204
205 \sa {Qt Quick Examples - Image Elements}, QQuickImageProvider, QImageReader::setAutoDetectImageFormat()
206*/
207
208QQuickImage::QQuickImage(QQuickItem *parent)
209 : QQuickImageBase(*(new QQuickImagePrivate), parent)
210{
211}
212
213QQuickImage::QQuickImage(QQuickImagePrivate &dd, QQuickItem *parent)
214 : QQuickImageBase(dd, parent)
215{
216}
217
218QQuickImage::~QQuickImage()
219{
220 Q_D(QQuickImage);
221 if (d->provider) {
222 // We're guaranteed to have a window() here because the provider would have
223 // been released in releaseResources() if we were gone from a window.
224 QQuickWindowQObjectCleanupJob::schedule(window: window(), object: d->provider);
225 }
226}
227
228void QQuickImagePrivate::setImage(const QImage &image)
229{
230 Q_Q(QQuickImage);
231 currentPix->setImage(image);
232 q->pixmapChange();
233 q->update();
234}
235
236void QQuickImagePrivate::setPixmap(const QQuickPixmap &pixmap)
237{
238 Q_Q(QQuickImage);
239 currentPix->setPixmap(pixmap);
240 q->pixmapChange();
241 q->update();
242}
243
244/*!
245 \qmlproperty enumeration QtQuick::Image::fillMode
246
247 Set this property to define what happens when the source image has a different size
248 than the item.
249
250 \value Image.Stretch the image is scaled to fit
251 \value Image.PreserveAspectFit the image is scaled uniformly to fit without cropping
252 \value Image.PreserveAspectCrop the image is scaled uniformly to fill, cropping if necessary
253 \value Image.Tile the image is duplicated horizontally and vertically
254 \value Image.TileVertically the image is stretched horizontally and tiled vertically
255 \value Image.TileHorizontally the image is stretched vertically and tiled horizontally
256 \value Image.Pad the image is not transformed
257 \br
258
259 \table
260
261 \row
262 \li \image declarative-qtlogo-stretch.png
263 \li Stretch (default)
264 \qml
265 Image {
266 width: 130; height: 100
267 source: "qtlogo.png"
268 }
269 \endqml
270
271 \row
272 \li \image declarative-qtlogo-preserveaspectfit.png
273 \li PreserveAspectFit
274 \qml
275 Image {
276 width: 130; height: 100
277 fillMode: Image.PreserveAspectFit
278 source: "qtlogo.png"
279 }
280 \endqml
281
282 \row
283 \li \image declarative-qtlogo-preserveaspectcrop.png
284 \li PreserveAspectCrop
285 \qml
286 Image {
287 width: 130; height: 100
288 fillMode: Image.PreserveAspectCrop
289 source: "qtlogo.png"
290 clip: true
291 }
292 \endqml
293
294 \row
295 \li \image declarative-qtlogo-tile.png
296 \li Tile
297 \qml
298 Image {
299 width: 120; height: 120
300 fillMode: Image.Tile
301 horizontalAlignment: Image.AlignLeft
302 verticalAlignment: Image.AlignTop
303 source: "qtlogo.png"
304 }
305 \endqml
306
307 \row
308 \li \image declarative-qtlogo-tilevertically.png
309 \li TileVertically
310 \qml
311 Image {
312 width: 120; height: 120
313 fillMode: Image.TileVertically
314 verticalAlignment: Image.AlignTop
315 source: "qtlogo.png"
316 }
317 \endqml
318
319 \row
320 \li \image declarative-qtlogo-tilehorizontally.png
321 \li TileHorizontally
322 \qml
323 Image {
324 width: 120; height: 120
325 fillMode: Image.TileHorizontally
326 verticalAlignment: Image.AlignLeft
327 source: "qtlogo.png"
328 }
329 \endqml
330
331 \endtable
332
333 Note that \c clip is \c false by default which means that the item might
334 paint outside its bounding rectangle even if the fillMode is set to \c PreserveAspectCrop.
335
336 \sa {Qt Quick Examples - Image Elements}
337*/
338QQuickImage::FillMode QQuickImage::fillMode() const
339{
340 Q_D(const QQuickImage);
341 return d->fillMode;
342}
343
344void QQuickImage::setFillMode(FillMode mode)
345{
346 Q_D(QQuickImage);
347 if (d->fillMode == mode)
348 return;
349 d->fillMode = mode;
350 if ((mode == PreserveAspectCrop) != d->providerOptions.preserveAspectRatioCrop()) {
351 d->providerOptions.setPreserveAspectRatioCrop(mode == PreserveAspectCrop);
352 if (isComponentComplete())
353 load();
354 } else if ((mode == PreserveAspectFit) != d->providerOptions.preserveAspectRatioFit()) {
355 d->providerOptions.setPreserveAspectRatioFit(mode == PreserveAspectFit);
356 if (isComponentComplete())
357 load();
358 }
359 update();
360 updatePaintedGeometry();
361 emit fillModeChanged();
362}
363
364/*!
365 \qmlproperty real QtQuick::Image::paintedWidth
366 \qmlproperty real QtQuick::Image::paintedHeight
367 \readonly
368
369 These properties hold the size of the image that is actually painted.
370 In most cases it is the same as \c width and \c height, but when using an
371 \l {fillMode}{Image.PreserveAspectFit} or an \l {fillMode}{Image.PreserveAspectCrop}
372 \c paintedWidth or \c paintedHeight can be smaller or larger than
373 \c width and \c height of the Image item.
374*/
375qreal QQuickImage::paintedWidth() const
376{
377 Q_D(const QQuickImage);
378 return d->paintedWidth;
379}
380
381qreal QQuickImage::paintedHeight() const
382{
383 Q_D(const QQuickImage);
384 return d->paintedHeight;
385}
386
387/*!
388 \qmlproperty enumeration QtQuick::Image::status
389 \readonly
390
391 This property holds the status of image loading. It can be one of:
392
393 \value Image.Null No image has been set
394 \value Image.Ready The image has been loaded
395 \value Image.Loading The image is currently being loaded
396 \value Image.Error An error occurred while loading the image
397
398 Use this status to provide an update or respond to the status change in some way.
399 For example, you could:
400
401 \list
402 \li Trigger a state change:
403 \qml
404 State { name: 'loaded'; when: image.status == Image.Ready }
405 \endqml
406
407 \li Implement an \c onStatusChanged signal handler:
408 \qml
409 Image {
410 id: image
411 onStatusChanged: if (image.status == Image.Ready) console.log('Loaded')
412 }
413 \endqml
414
415 \li Bind to the status value:
416 \qml
417 Text { text: image.status == Image.Ready ? 'Loaded' : 'Not loaded' }
418 \endqml
419 \endlist
420
421 \sa progress
422*/
423
424/*!
425 \qmlproperty real QtQuick::Image::progress
426 \readonly
427
428 This property holds the progress of image loading, from 0.0 (nothing loaded)
429 to 1.0 (finished).
430
431 \sa status
432*/
433
434/*!
435 \qmlproperty bool QtQuick::Image::smooth
436
437 This property holds whether the image is smoothly filtered when scaled or
438 transformed. Smooth filtering gives better visual quality, but it may be slower
439 on some hardware. If the image is displayed at its natural size, this property has
440 no visual or performance effect.
441
442 By default, this property is set to true.
443
444 \sa mipmap
445*/
446
447/*!
448 \qmlproperty size QtQuick::Image::sourceSize
449
450 This property holds the scaled width and height of the full-frame image.
451
452 Unlike the \l {Item::}{width} and \l {Item::}{height} properties, which scale
453 the painting of the image, this property sets the maximum number of pixels
454 stored for the loaded image so that large images do not use more
455 memory than necessary. For example, this ensures the image in memory is no
456 larger than 1024x1024 pixels, regardless of the Image's \l {Item::}{width} and
457 \l {Item::}{height} values:
458
459 \code
460 Rectangle {
461 width: ...
462 height: ...
463
464 Image {
465 anchors.fill: parent
466 source: "reallyBigImage.jpg"
467 sourceSize.width: 1024
468 sourceSize.height: 1024
469 }
470 }
471 \endcode
472
473 If the image's actual size is larger than the sourceSize, the image is scaled down.
474 If only one dimension of the size is set to greater than 0, the
475 other dimension is set in proportion to preserve the source image's aspect ratio.
476 (The \l fillMode is independent of this.)
477
478 If both the sourceSize.width and sourceSize.height are set, the image will be scaled
479 down to fit within the specified size (unless PreserveAspectCrop or PreserveAspectFit
480 are used, then it will be scaled to match the optimal size for cropping/fitting),
481 maintaining the image's aspect ratio. The actual
482 size of the image after scaling is available via \l Item::implicitWidth and \l Item::implicitHeight.
483
484 If the source is an intrinsically scalable image (eg. SVG), this property
485 determines the size of the loaded image regardless of intrinsic size.
486 Avoid changing this property dynamically; rendering an SVG is \e slow compared
487 to an image.
488
489 If the source is a non-scalable image (eg. JPEG), the loaded image will
490 be no greater than this property specifies. For some formats (currently only JPEG),
491 the whole image will never actually be loaded into memory.
492
493 If the \l sourceClipRect property is also set, \c sourceSize determines the scale,
494 but it will be clipped to the size of the clip rectangle.
495
496 sourceSize can be cleared to the natural size of the image
497 by setting sourceSize to \c undefined.
498
499 \note \e {Changing this property dynamically causes the image source to be reloaded,
500 potentially even from the network, if it is not in the disk cache.}
501
502 \sa {Qt Quick Examples - Pointer Handlers}
503*/
504
505/*!
506 \qmlproperty rect QtQuick::Image::sourceClipRect
507 \since 5.15
508
509 This property, if set, holds the rectangular region of the source image to
510 be loaded.
511
512 The \c sourceClipRect works together with the \l sourceSize property to
513 conserve system resources when only a portion of an image needs to be
514 loaded.
515
516 \code
517 Rectangle {
518 width: ...
519 height: ...
520
521 Image {
522 anchors.fill: parent
523 source: "reallyBigImage.svg"
524 sourceSize.width: 1024
525 sourceSize.height: 1024
526 sourceClipRect: Qt.rect(100, 100, 512, 512)
527 }
528 }
529 \endcode
530
531 In the above example, we conceptually scale the SVG graphic to 1024x1024
532 first, and then cut out a region of interest that is 512x512 pixels from a
533 location 100 pixels from the top and left edges. Thus \c sourceSize
534 determines the scale, but the actual output image is 512x512 pixels.
535
536 Some image formats are able to conserve CPU time by rendering only the
537 specified region. Others will need to load the entire image first and then
538 clip it to the specified region.
539
540 This property can be cleared to reload the entire image by setting
541 \c sourceClipRect to \c undefined.
542
543 \note \e {Changing this property dynamically causes the image source to be reloaded,
544 potentially even from the network, if it is not in the disk cache.}
545
546 \note Sub-pixel clipping is not supported: the given rectangle will be
547 passed to \l QImageReader::setScaledClipRect().
548*/
549
550/*!
551 \qmlproperty url QtQuick::Image::source
552
553 Image can handle any image format supported by Qt, loaded from any URL scheme supported by Qt.
554
555 The URL may be absolute, or relative to the URL of the component.
556
557 \sa QQuickImageProvider, {Compressed Texture Files}, {Automatic Detection of File Extension}
558*/
559
560/*!
561 \qmlproperty bool QtQuick::Image::asynchronous
562
563 Specifies that images on the local filesystem should be loaded
564 asynchronously in a separate thread. The default value is
565 false, causing the user interface thread to block while the
566 image is loaded. Setting \a asynchronous to true is useful where
567 maintaining a responsive user interface is more desirable
568 than having images immediately visible.
569
570 Note that this property is only valid for images read from the
571 local filesystem. Images loaded via a network resource (e.g. HTTP)
572 are always loaded asynchronously.
573*/
574
575/*!
576 \qmlproperty bool QtQuick::Image::cache
577
578 Specifies whether the image should be cached. The default value is
579 true. Setting \a cache to false is useful when dealing with large images,
580 to make sure that they aren't cached at the expense of small 'ui element' images.
581*/
582
583/*!
584 \qmlproperty bool QtQuick::Image::mirror
585
586 This property holds whether the image should be horizontally inverted
587 (effectively displaying a mirrored image).
588
589 The default value is false.
590*/
591
592/*!
593 \qmlproperty bool QtQuick::Image::mirrorVertically
594
595 This property holds whether the image should be vertically inverted
596 (effectively displaying a mirrored image).
597
598 The default value is false.
599
600 \since 6.2
601*/
602
603/*!
604 \qmlproperty enumeration QtQuick::Image::horizontalAlignment
605 \qmlproperty enumeration QtQuick::Image::verticalAlignment
606
607 Sets the horizontal and vertical alignment of the image. By default, the image is center aligned.
608
609 The valid values for \c horizontalAlignment are \c Image.AlignLeft, \c Image.AlignRight and \c Image.AlignHCenter.
610 The valid values for \c verticalAlignment are \c Image.AlignTop, \c Image.AlignBottom
611 and \c Image.AlignVCenter.
612*/
613void QQuickImage::updatePaintedGeometry()
614{
615 Q_D(QQuickImage);
616
617 if (d->fillMode == PreserveAspectFit) {
618 if (!d->currentPix->width() || !d->currentPix->height()) {
619 setImplicitSize(0, 0);
620 return;
621 }
622 const qreal pixWidth = d->currentPix->width() / d->devicePixelRatio;
623 const qreal pixHeight = d->currentPix->height() / d->devicePixelRatio;
624 const qreal w = widthValid() ? width() : pixWidth;
625 const qreal widthScale = w / pixWidth;
626 const qreal h = heightValid() ? height() : pixHeight;
627 const qreal heightScale = h / pixHeight;
628 if (widthScale <= heightScale) {
629 d->paintedWidth = w;
630 d->paintedHeight = widthScale * pixHeight;
631 } else if (heightScale < widthScale) {
632 d->paintedWidth = heightScale * pixWidth;
633 d->paintedHeight = h;
634 }
635 const qreal iHeight = (widthValid() && !heightValid()) ? d->paintedHeight : pixHeight;
636 const qreal iWidth = (heightValid() && !widthValid()) ? d->paintedWidth : pixWidth;
637 setImplicitSize(iWidth, iHeight);
638
639 } else if (d->fillMode == PreserveAspectCrop) {
640 if (!d->currentPix->width() || !d->currentPix->height())
641 return;
642 const qreal pixWidth = d->currentPix->width() / d->devicePixelRatio;
643 const qreal pixHeight = d->currentPix->height() / d->devicePixelRatio;
644 qreal widthScale = width() / pixWidth;
645 qreal heightScale = height() / pixHeight;
646 if (widthScale < heightScale) {
647 widthScale = heightScale;
648 } else if (heightScale < widthScale) {
649 heightScale = widthScale;
650 }
651
652 d->paintedHeight = heightScale * pixHeight;
653 d->paintedWidth = widthScale * pixWidth;
654 } else if (d->fillMode == Pad) {
655 d->paintedWidth = d->currentPix->width() / d->devicePixelRatio;
656 d->paintedHeight = d->currentPix->height() / d->devicePixelRatio;
657 } else {
658 d->paintedWidth = width();
659 d->paintedHeight = height();
660 }
661 emit paintedGeometryChanged();
662}
663
664void QQuickImage::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
665{
666 QQuickImageBase::geometryChange(newGeometry, oldGeometry);
667 if (newGeometry.size() != oldGeometry.size())
668 updatePaintedGeometry();
669}
670
671QRectF QQuickImage::boundingRect() const
672{
673 Q_D(const QQuickImage);
674 return QRectF(0, 0, qMax(a: width(), b: d->paintedWidth), qMax(a: height(), b: d->paintedHeight));
675}
676
677QSGTextureProvider *QQuickImage::textureProvider() const
678{
679 Q_D(const QQuickImage);
680
681 // When Item::layer::enabled == true, QQuickItem will be a texture
682 // provider. In this case we should prefer to return the layer rather
683 // than the image itself. The layer will include any children and any
684 // the image's wrap and fill mode.
685 if (QQuickItem::isTextureProvider())
686 return QQuickItem::textureProvider();
687
688 if (!d->window || !d->sceneGraphRenderContext() || QThread::currentThread() != d->sceneGraphRenderContext()->thread()) {
689 qWarning(msg: "QQuickImage::textureProvider: can only be queried on the rendering thread of an exposed window");
690 return nullptr;
691 }
692
693 if (!d->provider) {
694 QQuickImagePrivate *dd = const_cast<QQuickImagePrivate *>(d);
695 dd->provider = new QQuickImageTextureProvider;
696 dd->provider->m_smooth = d->smooth;
697 dd->provider->m_mipmap = d->mipmap;
698 dd->provider->updateTexture(texture: d->sceneGraphRenderContext()->textureForFactory(factory: d->currentPix->textureFactory(), window: window()));
699 }
700
701 return d->provider;
702}
703
704void QQuickImage::invalidateSceneGraph()
705{
706 Q_D(QQuickImage);
707 delete d->provider;
708 d->provider = nullptr;
709}
710
711void QQuickImage::releaseResources()
712{
713 Q_D(QQuickImage);
714 if (d->provider) {
715 QQuickWindowQObjectCleanupJob::schedule(window: window(), object: d->provider);
716 d->provider = nullptr;
717 }
718}
719
720QSGNode *QQuickImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
721{
722 Q_D(QQuickImage);
723
724 QSGTexture *texture = d->sceneGraphRenderContext()->textureForFactory(factory: d->currentPix->textureFactory(), window: window());
725
726 // Copy over the current texture state into the texture provider...
727 if (d->provider) {
728 d->provider->m_smooth = d->smooth;
729 d->provider->m_mipmap = d->mipmap;
730 d->provider->updateTexture(texture);
731 }
732
733 if (!texture || width() <= 0 || height() <= 0) {
734 delete oldNode;
735 return nullptr;
736 }
737
738 QSGInternalImageNode *node = static_cast<QSGInternalImageNode *>(oldNode);
739 if (!node) {
740 d->pixmapChanged = true;
741 node = d->sceneGraphContext()->createInternalImageNode(renderContext: d->sceneGraphRenderContext());
742 }
743
744 QRectF targetRect;
745 QRectF sourceRect;
746 QSGTexture::WrapMode hWrap = QSGTexture::ClampToEdge;
747 QSGTexture::WrapMode vWrap = QSGTexture::ClampToEdge;
748
749 qreal pixWidth = (d->fillMode == PreserveAspectFit) ? d->paintedWidth : d->currentPix->width() / d->devicePixelRatio;
750 qreal pixHeight = (d->fillMode == PreserveAspectFit) ? d->paintedHeight : d->currentPix->height() / d->devicePixelRatio;
751
752 int xOffset = 0;
753 if (d->hAlign == QQuickImage::AlignHCenter)
754 xOffset = (width() - pixWidth) / 2;
755 else if (d->hAlign == QQuickImage::AlignRight)
756 xOffset = qCeil(v: width() - pixWidth);
757
758 int yOffset = 0;
759 if (d->vAlign == QQuickImage::AlignVCenter)
760 yOffset = (height() - pixHeight) / 2;
761 else if (d->vAlign == QQuickImage::AlignBottom)
762 yOffset = qCeil(v: height() - pixHeight);
763
764 switch (d->fillMode) {
765 case Stretch:
766 targetRect = QRectF(0, 0, width(), height());
767 sourceRect = d->currentPix->rect();
768 break;
769
770 case PreserveAspectFit:
771 targetRect = QRectF(xOffset, yOffset, d->paintedWidth, d->paintedHeight);
772 sourceRect = d->currentPix->rect();
773 break;
774
775 case PreserveAspectCrop: {
776 targetRect = QRectF(0, 0, width(), height());
777 qreal wscale = width() / qreal(d->currentPix->width());
778 qreal hscale = height() / qreal(d->currentPix->height());
779
780 if (wscale > hscale) {
781 int src = (hscale / wscale) * qreal(d->currentPix->height());
782 int y = 0;
783 if (d->vAlign == QQuickImage::AlignVCenter)
784 y = qCeil(v: (d->currentPix->height() - src) / 2.);
785 else if (d->vAlign == QQuickImage::AlignBottom)
786 y = qCeil(v: d->currentPix->height() - src);
787 sourceRect = QRectF(0, y, d->currentPix->width(), src);
788
789 } else {
790 int src = (wscale / hscale) * qreal(d->currentPix->width());
791 int x = 0;
792 if (d->hAlign == QQuickImage::AlignHCenter)
793 x = qCeil(v: (d->currentPix->width() - src) / 2.);
794 else if (d->hAlign == QQuickImage::AlignRight)
795 x = qCeil(v: d->currentPix->width() - src);
796 sourceRect = QRectF(x, 0, src, d->currentPix->height());
797 }
798 }
799 break;
800
801 case Tile:
802 targetRect = QRectF(0, 0, width(), height());
803 sourceRect = QRectF(-xOffset, -yOffset, width(), height());
804 hWrap = QSGTexture::Repeat;
805 vWrap = QSGTexture::Repeat;
806 break;
807
808 case TileHorizontally:
809 targetRect = QRectF(0, 0, width(), height());
810 sourceRect = QRectF(-xOffset, 0, width(), d->currentPix->height());
811 hWrap = QSGTexture::Repeat;
812 break;
813
814 case TileVertically:
815 targetRect = QRectF(0, 0, width(), height());
816 sourceRect = QRectF(0, -yOffset, d->currentPix->width(), height());
817 vWrap = QSGTexture::Repeat;
818 break;
819
820 case Pad:
821 qreal w = qMin(a: qreal(pixWidth), b: width());
822 qreal h = qMin(a: qreal(pixHeight), b: height());
823 qreal x = (pixWidth > width()) ? -xOffset : 0;
824 qreal y = (pixHeight > height()) ? -yOffset : 0;
825 targetRect = QRectF(x + xOffset, y + yOffset, w, h);
826 sourceRect = QRectF(x, y, w, h);
827 break;
828 }
829
830 qreal nsWidth = (hWrap == QSGTexture::Repeat || d->fillMode == Pad) ? d->currentPix->width() / d->devicePixelRatio : d->currentPix->width();
831 qreal nsHeight = (vWrap == QSGTexture::Repeat || d->fillMode == Pad) ? d->currentPix->height() / d->devicePixelRatio : d->currentPix->height();
832 QRectF nsrect(sourceRect.x() / nsWidth,
833 sourceRect.y() / nsHeight,
834 sourceRect.width() / nsWidth,
835 sourceRect.height() / nsHeight);
836
837 if (targetRect.isEmpty()
838 || !qt_is_finite(d: targetRect.width()) || !qt_is_finite(d: targetRect.height())
839 || nsrect.isEmpty()
840 || !qt_is_finite(d: nsrect.width()) || !qt_is_finite(d: nsrect.height())) {
841 delete node;
842 return nullptr;
843 }
844
845 if (d->pixmapChanged) {
846 // force update the texture in the node to trigger reconstruction of
847 // geometry and the likes when a atlas segment has changed.
848 if (texture->isAtlasTexture() && (hWrap == QSGTexture::Repeat || vWrap == QSGTexture::Repeat || d->mipmap))
849 node->setTexture(texture->removedFromAtlas());
850 else
851 node->setTexture(texture);
852 d->pixmapChanged = false;
853 }
854
855 node->setMipmapFiltering(d->mipmap ? QSGTexture::Linear : QSGTexture::None);
856 node->setHorizontalWrapMode(hWrap);
857 node->setVerticalWrapMode(vWrap);
858 node->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest);
859
860 node->setTargetRect(targetRect);
861 node->setInnerTargetRect(targetRect);
862 node->setSubSourceRect(nsrect);
863 node->setMirror(horizontally: d->mirrorHorizontally, vertically: d->mirrorVertically);
864 node->setAntialiasing(d->antialiasing);
865 node->update();
866
867 return node;
868}
869
870void QQuickImage::pixmapChange()
871{
872 Q_D(QQuickImage);
873 // PreserveAspectFit calculates the implicit size differently so we
874 // don't call our superclass pixmapChange(), since that would
875 // result in the implicit size being set incorrectly, then updated
876 // in updatePaintedGeometry()
877 if (d->fillMode != PreserveAspectFit)
878 QQuickImageBase::pixmapChange();
879 updatePaintedGeometry();
880 d->pixmapChanged = true;
881
882 // When the pixmap changes, such as being deleted, we need to update the textures
883 update();
884}
885
886QQuickImage::VAlignment QQuickImage::verticalAlignment() const
887{
888 Q_D(const QQuickImage);
889 return d->vAlign;
890}
891
892void QQuickImage::setVerticalAlignment(VAlignment align)
893{
894 Q_D(QQuickImage);
895 if (d->vAlign == align)
896 return;
897
898 d->vAlign = align;
899 update();
900 updatePaintedGeometry();
901 emit verticalAlignmentChanged(alignment: align);
902}
903
904QQuickImage::HAlignment QQuickImage::horizontalAlignment() const
905{
906 Q_D(const QQuickImage);
907 return d->hAlign;
908}
909
910void QQuickImage::setHorizontalAlignment(HAlignment align)
911{
912 Q_D(QQuickImage);
913 if (d->hAlign == align)
914 return;
915
916 d->hAlign = align;
917 update();
918 updatePaintedGeometry();
919 emit horizontalAlignmentChanged(alignment: align);
920}
921
922/*!
923 \qmlproperty bool QtQuick::Image::mipmap
924 \since 5.3
925
926 This property holds whether the image uses mipmap filtering when scaled or
927 transformed.
928
929 Mipmap filtering gives better visual quality when scaling down
930 compared to smooth, but it may come at a performance cost (both when
931 initializing the image and during rendering).
932
933 By default, this property is set to false.
934
935 \sa smooth
936 */
937
938bool QQuickImage::mipmap() const
939{
940 Q_D(const QQuickImage);
941 return d->mipmap;
942}
943
944void QQuickImage::setMipmap(bool use)
945{
946 Q_D(QQuickImage);
947 if (d->mipmap == use)
948 return;
949 d->mipmap = use;
950 emit mipmapChanged(d->mipmap);
951
952 d->pixmapChanged = true;
953 if (isComponentComplete())
954 load();
955 update();
956}
957
958/*!
959 \qmlproperty bool QtQuick::Image::autoTransform
960 \since 5.5
961
962 This property holds whether the image should automatically apply
963 image transformation metadata such as EXIF orientation.
964
965 By default, this property is set to false.
966 */
967
968/*!
969 \qmlproperty int QtQuick::Image::currentFrame
970 \qmlproperty int QtQuick::Image::frameCount
971 \since 5.14
972
973 currentFrame is the frame that is currently visible. The default is \c 0.
974 You can set it to a number between \c 0 and \c {frameCount - 1} to display a
975 different frame, if the image contains multiple frames.
976
977 frameCount is the number of frames in the image. Most images have only one frame.
978*/
979
980/*!
981 \qmlproperty bool QtQuick::Image::retainWhileLoading
982 \since 6.8
983
984//! [qml-image-retainwhileloading]
985 This property defines the behavior when the \l source property is changed and loading happens
986 asynchronously. This is the case when the \l asynchronous property is set to \c true, or if the
987 image is not on the local file system.
988
989 If \c retainWhileLoading is \c false (the default), the old image is discarded immediately, and
990 the component is cleared while the new image is being loaded. If set to \c true, the old image
991 is retained and remains visible until the new one is ready.
992
993 Enabling this property can avoid flickering in cases where loading the new image takes a long
994 time. It comes at the cost of some extra memory use for double buffering while the new image is
995 being loaded.
996//! [qml-image-retainwhileloading]
997 */
998
999QT_END_NAMESPACE
1000
1001#include "moc_qquickimage_p_p.cpp"
1002
1003#include "moc_qquickimage_p.cpp"
1004

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