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