1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquickimage_p.h"
43 #include "qquickimage_p_p.h"
45 #include <QtQuick/qsgtextureprovider.h>
47 #include <QtQuick/private/qsgcontext_p.h>
48 #include <private/qsgadaptationlayer_p.h>
50 #include <QtGui/qpainter.h>
55 class QQuickImageTextureProvider : public QSGTextureProvider
59 QQuickImageTextureProvider()
65 QSGTexture *texture() const {
67 if (m_texture && m_texture->isAtlasTexture())
68 const_cast<QQuickImageTextureProvider *>(this)->m_texture = m_texture->removedFromAtlas();
71 m_texture->setFiltering(m_smooth ? QSGTexture::Linear : QSGTexture::Nearest);
72 m_texture->setMipmapFiltering(QSGTexture::Nearest);
73 m_texture->setHorizontalWrapMode(QSGTexture::ClampToEdge);
74 m_texture->setVerticalWrapMode(QSGTexture::ClampToEdge);
79 friend class QQuickImage;
81 QSGTexture *m_texture;
85 #include "qquickimage.moc"
87 QQuickImagePrivate::QQuickImagePrivate()
88 : fillMode(QQuickImage::Stretch)
91 , pixmapChanged(false)
92 , hAlign(QQuickImage::AlignHCenter)
93 , vAlign(QQuickImage::AlignVCenter)
99 \qmlclass Image QQuickImage
100 \inqmlmodule QtQuick 2
101 \ingroup qml-basic-visual-elements
102 \brief The Image element displays an image in a declarative user interface
105 The Image element is used to display images in a declarative user interface.
107 The source of the image is specified as a URL using the \l source property.
108 Images can be supplied in any of the standard image formats supported by Qt,
109 including bitmap formats such as PNG and JPEG, and vector graphics formats
110 such as SVG. If you need to display animated images, use the \l AnimatedImage
113 If the \l{Item::width}{width} and \l{Item::height}{height} properties are not
114 specified, the Image element automatically uses the size of the loaded image.
115 By default, specifying the width and height of the element causes the image
116 to be scaled to that size. This behavior can be changed by setting the
117 \l fillMode property, allowing the image to be stretched and tiled instead.
119 \section1 Example Usage
121 The following example shows the simplest usage of the Image element.
123 \snippet doc/src/snippets/declarative/image.qml document
126 \image declarative-qtlogo.png
131 \section1 Performance
133 By default, locally available images are loaded immediately, and the user interface
134 is blocked until loading is complete. If a large image is to be loaded, it may be
135 preferable to load the image in a low priority thread, by enabling the \l asynchronous
138 If the image is obtained from a network rather than a local resource, it is
139 automatically loaded asynchronously, and the \l progress and \l status properties
140 are updated as appropriate.
142 Images are cached and shared internally, so if several Image elements have the same \l source,
143 only one copy of the image will be loaded.
145 \bold Note: Images are often the greatest user of memory in QML user interfaces. It is recommended
146 that images which do not form part of the user interface have their
147 size bounded via the \l sourceSize property. This is especially important for content
148 that is loaded from external sources or provided by the user.
150 \sa {declarative/imageelements/image}{Image example}, QDeclarativeImageProvider
153 QQuickImage::QQuickImage(QQuickItem *parent)
154 : QQuickImageBase(*(new QQuickImagePrivate), parent)
158 QQuickImage::QQuickImage(QQuickImagePrivate &dd, QQuickItem *parent)
159 : QQuickImageBase(dd, parent)
163 QQuickImage::~QQuickImage()
167 d->provider->deleteLater();
170 void QQuickImagePrivate::setImage(const QImage &image)
176 status = pix.isNull() ? QQuickImageBase::Null : QQuickImageBase::Ready;
182 \qmlproperty enumeration QtQuick2::Image::fillMode
184 Set this property to define what happens when the source image has a different size
188 \o Image.Stretch - the image is scaled to fit
189 \o Image.PreserveAspectFit - the image is scaled uniformly to fit without cropping
190 \o Image.PreserveAspectCrop - the image is scaled uniformly to fill, cropping if necessary
191 \o Image.Tile - the image is duplicated horizontally and vertically
192 \o Image.TileVertically - the image is stretched horizontally and tiled vertically
193 \o Image.TileHorizontally - the image is stretched vertically and tiled horizontally
194 \o Image.Pad - the image is not transformed
200 \o \image declarative-qtlogo-stretch.png
204 width: 130; height: 100
211 \o \image declarative-qtlogo-preserveaspectfit.png
215 width: 130; height: 100
216 fillMode: Image.PreserveAspectFit
223 \o \image declarative-qtlogo-preserveaspectcrop.png
224 \o PreserveAspectCrop
227 width: 130; height: 100
228 fillMode: Image.PreserveAspectCrop
236 \o \image declarative-qtlogo-tile.png
240 width: 120; height: 120
247 \o \image declarative-qtlogo-tilevertically.png
251 width: 120; height: 120
252 fillMode: Image.TileVertically
259 \o \image declarative-qtlogo-tilehorizontally.png
263 width: 120; height: 120
264 fillMode: Image.TileHorizontally
272 Note that \c clip is \c false by default which means that the element might
273 paint outside its bounding rectangle even if the fillMode is set to \c PreserveAspectCrop.
275 \sa {declarative/imageelements/image}{Image example}
277 QQuickImage::FillMode QQuickImage::fillMode() const
279 Q_D(const QQuickImage);
283 void QQuickImage::setFillMode(FillMode mode)
286 if (d->fillMode == mode)
290 updatePaintedGeometry();
291 emit fillModeChanged();
296 \qmlproperty real QtQuick2::Image::paintedWidth
297 \qmlproperty real QtQuick2::Image::paintedHeight
299 These properties hold the size of the image that is actually painted.
300 In most cases it is the same as \c width and \c height, but when using a
301 \c fillMode \c PreserveAspectFit or \c fillMode \c PreserveAspectCrop
302 \c paintedWidth or \c paintedHeight can be smaller or larger than
303 \c width and \c height of the Image element.
305 qreal QQuickImage::paintedWidth() const
307 Q_D(const QQuickImage);
308 return d->paintedWidth;
311 qreal QQuickImage::paintedHeight() const
313 Q_D(const QQuickImage);
314 return d->paintedHeight;
318 \qmlproperty enumeration QtQuick2::Image::status
320 This property holds the status of image loading. It can be one of:
322 \o Image.Null - no image has been set
323 \o Image.Ready - the image has been loaded
324 \o Image.Loading - the image is currently being loaded
325 \o Image.Error - an error occurred while loading the image
328 Use this status to provide an update or respond to the status change in some way.
329 For example, you could:
332 \o Trigger a state change:
334 State { name: 'loaded'; when: image.status == Image.Ready }
337 \o Implement an \c onStatusChanged signal handler:
341 onStatusChanged: if (image.status == Image.Ready) console.log('Loaded')
345 \o Bind to the status value:
347 Text { text: image.status == Image.Ready ? 'Loaded' : 'Not loaded' }
355 \qmlproperty real QtQuick2::Image::progress
357 This property holds the progress of image loading, from 0.0 (nothing loaded)
364 \qmlproperty bool QtQuick2::Image::smooth
366 Set this property if you want the image to be smoothly filtered when scaled or
367 transformed. Smooth filtering gives better visual quality, but is slower. If
368 the image is displayed at its natural size, this property has no visual or
371 \note Generally scaling artifacts are only visible if the image is stationary on
372 the screen. A common pattern when animating an image is to disable smooth
373 filtering at the beginning of the animation and reenable it at the conclusion.
377 \qmlproperty QSize QtQuick2::Image::sourceSize
379 This property holds the actual width and height of the loaded image.
381 Unlike the \l {Item::}{width} and \l {Item::}{height} properties, which scale
382 the painting of the image, this property sets the actual number of pixels
383 stored for the loaded image so that large images do not use more
384 memory than necessary. For example, this ensures the image in memory is no
385 larger than 1024x1024 pixels, regardless of the Image's \l {Item::}{width} and
386 \l {Item::}{height} values:
395 source: "reallyBigImage.jpg"
396 sourceSize.width: 1024
397 sourceSize.height: 1024
402 If the image's actual size is larger than the sourceSize, the image is scaled down.
403 If only one dimension of the size is set to greater than 0, the
404 other dimension is set in proportion to preserve the source image's aspect ratio.
405 (The \l fillMode is independent of this.)
407 If the source is an intrinsically scalable image (eg. SVG), this property
408 determines the size of the loaded image regardless of intrinsic size.
409 Avoid changing this property dynamically; rendering an SVG is \e slow compared
412 If the source is a non-scalable image (eg. JPEG), the loaded image will
413 be no greater than this property specifies. For some formats (currently only JPEG),
414 the whole image will never actually be loaded into memory.
416 Since QtQuick 1.1 the sourceSize can be cleared to the natural size of the image
417 by setting sourceSize to \c undefined.
419 \note \e {Changing this property dynamically causes the image source to be reloaded,
420 potentially even from the network, if it is not in the disk cache.}
424 \qmlproperty url QtQuick2::Image::source
426 Image can handle any image format supported by Qt, loaded from any URL scheme supported by Qt.
428 The URL may be absolute, or relative to the URL of the component.
430 \sa QDeclarativeImageProvider
434 \qmlproperty bool QtQuick2::Image::asynchronous
436 Specifies that images on the local filesystem should be loaded
437 asynchronously in a separate thread. The default value is
438 false, causing the user interface thread to block while the
439 image is loaded. Setting \a asynchronous to true is useful where
440 maintaining a responsive user interface is more desirable
441 than having images immediately visible.
443 Note that this property is only valid for images read from the
444 local filesystem. Images loaded via a network resource (e.g. HTTP)
445 are always loaded asynchronously.
449 \qmlproperty bool QtQuick2::Image::cache
451 Specifies whether the image should be cached. The default value is
452 true. Setting \a cache to false is useful when dealing with large images,
453 to make sure that they aren't cached at the expense of small 'ui element' images.
457 \qmlproperty bool QtQuick2::Image::mirror
459 This property holds whether the image should be horizontally inverted
460 (effectively displaying a mirrored image).
462 The default value is false.
466 \qmlproperty enumeration QtQuick2::Image::horizontalAlignment
467 \qmlproperty enumeration QtQuick2::Image::verticalAlignment
469 Sets the horizontal and vertical alignment of the image. By default, the image is top-left aligned.
471 The valid values for \c horizontalAlignment are \c Image.AlignLeft, \c Image.AlignRight and \c Image.AlignHCenter.
472 The valid values for \c verticalAlignment are \c Image.AlignTop, \c Image.AlignBottom
473 and \c Image.AlignVCenter.
475 void QQuickImage::updatePaintedGeometry()
479 if (d->fillMode == PreserveAspectFit) {
480 if (!d->pix.width() || !d->pix.height()) {
481 setImplicitSize(0, 0);
484 qreal w = widthValid() ? width() : d->pix.width();
485 qreal widthScale = w / qreal(d->pix.width());
486 qreal h = heightValid() ? height() : d->pix.height();
487 qreal heightScale = h / qreal(d->pix.height());
488 if (widthScale <= heightScale) {
490 d->paintedHeight = widthScale * qreal(d->pix.height());
491 } else if (heightScale < widthScale) {
492 d->paintedWidth = heightScale * qreal(d->pix.width());
493 d->paintedHeight = h;
495 qreal iHeight = (widthValid() && !heightValid()) ? d->paintedHeight : d->pix.height();
496 qreal iWidth = (heightValid() && !widthValid()) ? d->paintedWidth : d->pix.width();
497 setImplicitSize(iWidth, iHeight);
499 } else if (d->fillMode == PreserveAspectCrop) {
500 if (!d->pix.width() || !d->pix.height())
502 qreal widthScale = width() / qreal(d->pix.width());
503 qreal heightScale = height() / qreal(d->pix.height());
504 if (widthScale < heightScale) {
505 widthScale = heightScale;
506 } else if (heightScale < widthScale) {
507 heightScale = widthScale;
510 d->paintedHeight = heightScale * qreal(d->pix.height());
511 d->paintedWidth = widthScale * qreal(d->pix.width());
512 } else if (d->fillMode == Pad) {
513 d->paintedWidth = d->pix.width();
514 d->paintedHeight = d->pix.height();
516 d->paintedWidth = width();
517 d->paintedHeight = height();
519 emit paintedGeometryChanged();
522 void QQuickImage::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
524 QQuickImageBase::geometryChanged(newGeometry, oldGeometry);
525 updatePaintedGeometry();
528 QRectF QQuickImage::boundingRect() const
530 Q_D(const QQuickImage);
531 return QRectF(0, 0, qMax(width(), d->paintedWidth), qMax(height(), d->paintedHeight));
534 QSGTextureProvider *QQuickImage::textureProvider() const
536 Q_D(const QQuickImage);
538 // Make sure it gets thread affinity on the rendering thread so deletion works properly..
540 && d->sceneGraphContext()
541 && QThread::currentThread() == d->sceneGraphContext()->thread(),
542 "QQuickImage::textureProvider",
543 "Cannot be used outside the GUI thread");
544 QQuickImagePrivate *dd = const_cast<QQuickImagePrivate *>(d);
545 dd->provider = new QQuickImageTextureProvider;
546 dd->provider->m_smooth = d->smooth;
547 dd->provider->m_texture = d->sceneGraphContext()->textureForFactory(d->pix.textureFactory(), canvas());
553 QSGNode *QQuickImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
557 QSGTexture *texture = d->sceneGraphContext()->textureForFactory(d->pix.textureFactory(), canvas());
559 // Copy over the current texture state into the texture provider...
561 d->provider->m_smooth = d->smooth;
562 d->provider->m_texture = texture;
565 if (!texture || width() <= 0 || height() <= 0) {
570 QSGImageNode *node = static_cast<QSGImageNode *>(oldNode);
572 d->pixmapChanged = true;
573 node = d->sceneGraphContext()->createImageNode();
574 node->setTexture(texture);
577 if (d->pixmapChanged) {
578 // force update the texture in the node to trigger reconstruction of
579 // geometry and the likes when a atlas segment has changed.
581 node->setTexture(texture);
582 d->pixmapChanged = false;
587 QSGTexture::WrapMode hWrap = QSGTexture::ClampToEdge;
588 QSGTexture::WrapMode vWrap = QSGTexture::ClampToEdge;
590 qreal pixWidth = (d->fillMode == PreserveAspectFit) ? d->paintedWidth : d->pix.width();
591 qreal pixHeight = (d->fillMode == PreserveAspectFit) ? d->paintedHeight : d->pix.height();
594 if (d->hAlign == QQuickImage::AlignHCenter)
595 xOffset = qCeil((width() - pixWidth) / 2.);
596 else if (d->hAlign == QQuickImage::AlignRight)
597 xOffset = qCeil(width() - pixWidth);
600 if (d->vAlign == QQuickImage::AlignVCenter)
601 yOffset = qCeil((height() - pixHeight) / 2.);
602 else if (d->vAlign == QQuickImage::AlignBottom)
603 yOffset = qCeil(height() - pixHeight);
605 switch (d->fillMode) {
608 targetRect = QRectF(0, 0, width(), height());
609 sourceRect = d->pix.rect();
612 case PreserveAspectFit:
613 targetRect = QRectF(xOffset, yOffset, d->paintedWidth, d->paintedHeight);
614 sourceRect = d->pix.rect();
617 case PreserveAspectCrop: {
618 targetRect = QRect(0, 0, width(), height());
619 qreal wscale = width() / qreal(d->pix.width());
620 qreal hscale = height() / qreal(d->pix.height());
622 if (wscale > hscale) {
623 int src = (hscale / wscale) * qreal(d->pix.height());
625 if (d->vAlign == QQuickImage::AlignVCenter)
626 y = qCeil((d->pix.height() - src) / 2.);
627 else if (d->vAlign == QQuickImage::AlignBottom)
628 y = qCeil(d->pix.height() - src);
629 sourceRect = QRectF(0, y, d->pix.width(), src);
632 int src = (wscale / hscale) * qreal(d->pix.width());
634 if (d->hAlign == QQuickImage::AlignHCenter)
635 x = qCeil((d->pix.width() - src) / 2.);
636 else if (d->hAlign == QQuickImage::AlignRight)
637 x = qCeil(d->pix.width() - src);
638 sourceRect = QRectF(x, 0, src, d->pix.height());
644 targetRect = QRectF(0, 0, width(), height());
645 sourceRect = QRectF(-xOffset, -yOffset, width(), height());
646 hWrap = QSGTexture::Repeat;
647 vWrap = QSGTexture::Repeat;
650 case TileHorizontally:
651 targetRect = QRectF(0, 0, width(), height());
652 sourceRect = QRectF(-xOffset, 0, width(), d->pix.height());
653 hWrap = QSGTexture::Repeat;
657 targetRect = QRectF(0, 0, width(), height());
658 sourceRect = QRectF(0, -yOffset, d->pix.width(), height());
659 vWrap = QSGTexture::Repeat;
663 qreal w = qMin(qreal(d->pix.width()), width());
664 qreal h = qMin(qreal(d->pix.height()), height());
665 qreal x = (d->pix.width() > width()) ? -xOffset : 0;
666 qreal y = (d->pix.height() > height()) ? -yOffset : 0;
667 targetRect = QRectF(x + xOffset, y + yOffset, w, h);
668 sourceRect = QRectF(x, y, w, h);
672 QRectF nsrect(sourceRect.x() / d->pix.width(),
673 sourceRect.y() / d->pix.height(),
674 sourceRect.width() / d->pix.width(),
675 sourceRect.height() / d->pix.height());
678 qreal oldLeft = nsrect.left();
679 nsrect.setLeft(nsrect.right());
680 nsrect.setRight(oldLeft);
683 node->setHorizontalWrapMode(hWrap);
684 node->setVerticalWrapMode(vWrap);
685 node->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest);
687 node->setTargetRect(targetRect);
688 node->setSourceRect(nsrect);
694 void QQuickImage::pixmapChange()
697 // PreserveAspectFit calculates the implicit size differently so we
698 // don't call our superclass pixmapChange(), since that would
699 // result in the implicit size being set incorrectly, then updated
700 // in updatePaintedGeometry()
701 if (d->fillMode != PreserveAspectFit)
702 QQuickImageBase::pixmapChange();
703 updatePaintedGeometry();
704 d->pixmapChanged = true;
706 // When the pixmap changes, such as being deleted, we need to update the textures
710 QQuickImage::VAlignment QQuickImage::verticalAlignment() const
712 Q_D(const QQuickImage);
716 void QQuickImage::setVerticalAlignment(VAlignment align)
719 if (d->vAlign == align)
724 updatePaintedGeometry();
725 emit verticalAlignmentChanged(align);
728 QQuickImage::HAlignment QQuickImage::horizontalAlignment() const
730 Q_D(const QQuickImage);
734 void QQuickImage::setHorizontalAlignment(HAlignment align)
737 if (d->hAlign == align)
742 updatePaintedGeometry();
743 emit horizontalAlignmentChanged(align);