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

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