diff options
Diffstat (limited to 'src/quick/items/qquickimage.cpp')
-rw-r--r-- | src/quick/items/qquickimage.cpp | 746 |
1 files changed, 746 insertions, 0 deletions
diff --git a/src/quick/items/qquickimage.cpp b/src/quick/items/qquickimage.cpp new file mode 100644 index 0000000000..19a8ef1199 --- /dev/null +++ b/src/quick/items/qquickimage.cpp @@ -0,0 +1,746 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickimage_p.h" +#include "qquickimage_p_p.h" + +#include <QtQuick/private/qsgtextureprovider_p.h> + +#include <QtQuick/private/qsgcontext_p.h> +#include <private/qsgadaptationlayer_p.h> + +#include <QtGui/qpainter.h> +#include <qmath.h> + +QT_BEGIN_NAMESPACE + +class QQuickImageTextureProvider : public QSGTextureProvider +{ + Q_OBJECT +public: + QQuickImageTextureProvider() + : m_texture(0) + , m_smooth(false) + { + } + + QSGTexture *texture() const { + + if (m_texture && m_texture->isAtlasTexture()) + const_cast<QQuickImageTextureProvider *>(this)->m_texture = m_texture->removedFromAtlas(); + + if (m_texture) { + m_texture->setFiltering(m_smooth ? QSGTexture::Linear : QSGTexture::Nearest); + m_texture->setMipmapFiltering(QSGTexture::Nearest); + m_texture->setHorizontalWrapMode(QSGTexture::ClampToEdge); + m_texture->setVerticalWrapMode(QSGTexture::ClampToEdge); + } + return m_texture; + } + + friend class QQuickImage; + + QSGTexture *m_texture; + bool m_smooth; +}; + +#include "qquickimage.moc" + +QQuickImagePrivate::QQuickImagePrivate() + : fillMode(QQuickImage::Stretch) + , paintedWidth(0) + , paintedHeight(0) + , pixmapChanged(false) + , hAlign(QQuickImage::AlignHCenter) + , vAlign(QQuickImage::AlignVCenter) + , provider(0) +{ +} + +/*! + \qmlclass Image QQuickImage + \inqmlmodule QtQuick 2 + \ingroup qml-basic-visual-elements + \brief The Image element displays an image in a declarative user interface + \inherits Item + + The Image element is used to display images in a declarative user interface. + + The source of the image is specified as a URL using the \l source property. + Images can be supplied in any of the standard image formats supported by Qt, + including bitmap formats such as PNG and JPEG, and vector graphics formats + such as SVG. If you need to display animated images, use the \l AnimatedImage + element. + + If the \l{Item::width}{width} and \l{Item::height}{height} properties are not + specified, the Image element automatically uses the size of the loaded image. + By default, specifying the width and height of the element causes the image + to be scaled to that size. This behavior can be changed by setting the + \l fillMode property, allowing the image to be stretched and tiled instead. + + \section1 Example Usage + + The following example shows the simplest usage of the Image element. + + \snippet doc/src/snippets/declarative/image.qml document + + \beginfloatleft + \image declarative-qtlogo.png + \endfloat + + \clearfloat + + \section1 Performance + + By default, locally available images are loaded immediately, and the user interface + is blocked until loading is complete. If a large image is to be loaded, it may be + preferable to load the image in a low priority thread, by enabling the \l asynchronous + property. + + If the image is obtained from a network rather than a local resource, it is + automatically loaded asynchronously, and the \l progress and \l status properties + are updated as appropriate. + + Images are cached and shared internally, so if several Image elements have the same \l source, + only one copy of the image will be loaded. + + \bold Note: Images are often the greatest user of memory in QML user interfaces. It is recommended + that images which do not form part of the user interface have their + size bounded via the \l sourceSize property. This is especially important for content + that is loaded from external sources or provided by the user. + + \sa {declarative/imageelements/image}{Image example}, QDeclarativeImageProvider +*/ + +QQuickImage::QQuickImage(QQuickItem *parent) + : QQuickImageBase(*(new QQuickImagePrivate), parent) +{ +} + +QQuickImage::QQuickImage(QQuickImagePrivate &dd, QQuickItem *parent) + : QQuickImageBase(dd, parent) +{ +} + +QQuickImage::~QQuickImage() +{ + Q_D(QQuickImage); + if (d->provider) + d->provider->deleteLater(); +} + +void QQuickImagePrivate::setImage(const QImage &image) +{ + Q_Q(QQuickImage); + pix.setImage(image); + + q->pixmapChange(); + status = pix.isNull() ? QQuickImageBase::Null : QQuickImageBase::Ready; + + q->update(); +} + +/*! + \qmlproperty enumeration QtQuick2::Image::fillMode + + Set this property to define what happens when the source image has a different size + than the item. + + \list + \o Image.Stretch - the image is scaled to fit + \o Image.PreserveAspectFit - the image is scaled uniformly to fit without cropping + \o Image.PreserveAspectCrop - the image is scaled uniformly to fill, cropping if necessary + \o Image.Tile - the image is duplicated horizontally and vertically + \o Image.TileVertically - the image is stretched horizontally and tiled vertically + \o Image.TileHorizontally - the image is stretched vertically and tiled horizontally + \o Image.Pad - the image is not transformed + \endlist + + \table + + \row + \o \image declarative-qtlogo-stretch.png + \o Stretch (default) + \qml + Image { + width: 130; height: 100 + smooth: true + source: "qtlogo.png" + } + \endqml + + \row + \o \image declarative-qtlogo-preserveaspectfit.png + \o PreserveAspectFit + \qml + Image { + width: 130; height: 100 + fillMode: Image.PreserveAspectFit + smooth: true + source: "qtlogo.png" + } + \endqml + + \row + \o \image declarative-qtlogo-preserveaspectcrop.png + \o PreserveAspectCrop + \qml + Image { + width: 130; height: 100 + fillMode: Image.PreserveAspectCrop + smooth: true + source: "qtlogo.png" + clip: true + } + \endqml + + \row + \o \image declarative-qtlogo-tile.png + \o Tile + \qml + Image { + width: 120; height: 120 + fillMode: Image.Tile + source: "qtlogo.png" + } + \endqml + + \row + \o \image declarative-qtlogo-tilevertically.png + \o TileVertically + \qml + Image { + width: 120; height: 120 + fillMode: Image.TileVertically + smooth: true + source: "qtlogo.png" + } + \endqml + + \row + \o \image declarative-qtlogo-tilehorizontally.png + \o TileHorizontally + \qml + Image { + width: 120; height: 120 + fillMode: Image.TileHorizontally + smooth: true + source: "qtlogo.png" + } + \endqml + + \endtable + + Note that \c clip is \c false by default which means that the element might + paint outside its bounding rectangle even if the fillMode is set to \c PreserveAspectCrop. + + \sa {declarative/imageelements/image}{Image example} +*/ +QQuickImage::FillMode QQuickImage::fillMode() const +{ + Q_D(const QQuickImage); + return d->fillMode; +} + +void QQuickImage::setFillMode(FillMode mode) +{ + Q_D(QQuickImage); + if (d->fillMode == mode) + return; + d->fillMode = mode; + update(); + updatePaintedGeometry(); + emit fillModeChanged(); +} + +/*! + + \qmlproperty real QtQuick2::Image::paintedWidth + \qmlproperty real QtQuick2::Image::paintedHeight + + These properties hold the size of the image that is actually painted. + In most cases it is the same as \c width and \c height, but when using a + \c fillMode \c PreserveAspectFit or \c fillMode \c PreserveAspectCrop + \c paintedWidth or \c paintedHeight can be smaller or larger than + \c width and \c height of the Image element. +*/ +qreal QQuickImage::paintedWidth() const +{ + Q_D(const QQuickImage); + return d->paintedWidth; +} + +qreal QQuickImage::paintedHeight() const +{ + Q_D(const QQuickImage); + return d->paintedHeight; +} + +/*! + \qmlproperty enumeration QtQuick2::Image::status + + This property holds the status of image loading. It can be one of: + \list + \o Image.Null - no image has been set + \o Image.Ready - the image has been loaded + \o Image.Loading - the image is currently being loaded + \o Image.Error - an error occurred while loading the image + \endlist + + Use this status to provide an update or respond to the status change in some way. + For example, you could: + + \list + \o Trigger a state change: + \qml + State { name: 'loaded'; when: image.status == Image.Ready } + \endqml + + \o Implement an \c onStatusChanged signal handler: + \qml + Image { + id: image + onStatusChanged: if (image.status == Image.Ready) console.log('Loaded') + } + \endqml + + \o Bind to the status value: + \qml + Text { text: image.status == Image.Ready ? 'Loaded' : 'Not loaded' } + \endqml + \endlist + + \sa progress +*/ + +/*! + \qmlproperty real QtQuick2::Image::progress + + This property holds the progress of image loading, from 0.0 (nothing loaded) + to 1.0 (finished). + + \sa status +*/ + +/*! + \qmlproperty bool QtQuick2::Image::smooth + + Set this property if you want the image to be smoothly filtered when scaled or + transformed. Smooth filtering gives better visual quality, but is slower. If + the image is displayed at its natural size, this property has no visual or + performance effect. + + \note Generally scaling artifacts are only visible if the image is stationary on + the screen. A common pattern when animating an image is to disable smooth + filtering at the beginning of the animation and reenable it at the conclusion. +*/ + +/*! + \qmlproperty QSize QtQuick2::Image::sourceSize + + This property holds the actual width and height of the loaded image. + + Unlike the \l {Item::}{width} and \l {Item::}{height} properties, which scale + the painting of the image, this property sets the actual number of pixels + stored for the loaded image so that large images do not use more + memory than necessary. For example, this ensures the image in memory is no + larger than 1024x1024 pixels, regardless of the Image's \l {Item::}{width} and + \l {Item::}{height} values: + + \code + Rectangle { + width: ... + height: ... + + Image { + anchors.fill: parent + source: "reallyBigImage.jpg" + sourceSize.width: 1024 + sourceSize.height: 1024 + } + } + \endcode + + If the image's actual size is larger than the sourceSize, the image is scaled down. + If only one dimension of the size is set to greater than 0, the + other dimension is set in proportion to preserve the source image's aspect ratio. + (The \l fillMode is independent of this.) + + If the source is an intrinsically scalable image (eg. SVG), this property + determines the size of the loaded image regardless of intrinsic size. + Avoid changing this property dynamically; rendering an SVG is \e slow compared + to an image. + + If the source is a non-scalable image (eg. JPEG), the loaded image will + be no greater than this property specifies. For some formats (currently only JPEG), + the whole image will never actually be loaded into memory. + + Since QtQuick 1.1 the sourceSize can be cleared to the natural size of the image + by setting sourceSize to \c undefined. + + \note \e {Changing this property dynamically causes the image source to be reloaded, + potentially even from the network, if it is not in the disk cache.} +*/ + +/*! + \qmlproperty url QtQuick2::Image::source + + Image can handle any image format supported by Qt, loaded from any URL scheme supported by Qt. + + The URL may be absolute, or relative to the URL of the component. + + \sa QDeclarativeImageProvider +*/ + +/*! + \qmlproperty bool QtQuick2::Image::asynchronous + + Specifies that images on the local filesystem should be loaded + asynchronously in a separate thread. The default value is + false, causing the user interface thread to block while the + image is loaded. Setting \a asynchronous to true is useful where + maintaining a responsive user interface is more desirable + than having images immediately visible. + + Note that this property is only valid for images read from the + local filesystem. Images loaded via a network resource (e.g. HTTP) + are always loaded asynchronously. +*/ + +/*! + \qmlproperty bool QtQuick2::Image::cache + + Specifies whether the image should be cached. The default value is + true. Setting \a cache to false is useful when dealing with large images, + to make sure that they aren't cached at the expense of small 'ui element' images. +*/ + +/*! + \qmlproperty bool QtQuick2::Image::mirror + + This property holds whether the image should be horizontally inverted + (effectively displaying a mirrored image). + + The default value is false. +*/ + +/*! + \qmlproperty enumeration QtQuick2::Image::horizontalAlignment + \qmlproperty enumeration QtQuick2::Image::verticalAlignment + + Sets the horizontal and vertical alignment of the image. By default, the image is top-left aligned. + + The valid values for \c horizontalAlignment are \c Image.AlignLeft, \c Image.AlignRight and \c Image.AlignHCenter. + The valid values for \c verticalAlignment are \c Image.AlignTop, \c Image.AlignBottom + and \c Image.AlignVCenter. +*/ +void QQuickImage::updatePaintedGeometry() +{ + Q_D(QQuickImage); + + if (d->fillMode == PreserveAspectFit) { + if (!d->pix.width() || !d->pix.height()) { + setImplicitSize(0, 0); + return; + } + qreal w = widthValid() ? width() : d->pix.width(); + qreal widthScale = w / qreal(d->pix.width()); + qreal h = heightValid() ? height() : d->pix.height(); + qreal heightScale = h / qreal(d->pix.height()); + if (widthScale <= heightScale) { + d->paintedWidth = w; + d->paintedHeight = widthScale * qreal(d->pix.height()); + } else if (heightScale < widthScale) { + d->paintedWidth = heightScale * qreal(d->pix.width()); + d->paintedHeight = h; + } + qreal iHeight = (widthValid() && !heightValid()) ? d->paintedHeight : d->pix.height(); + qreal iWidth = (heightValid() && !widthValid()) ? d->paintedWidth : d->pix.width(); + setImplicitSize(iWidth, iHeight); + + } else if (d->fillMode == PreserveAspectCrop) { + if (!d->pix.width() || !d->pix.height()) + return; + qreal widthScale = width() / qreal(d->pix.width()); + qreal heightScale = height() / qreal(d->pix.height()); + if (widthScale < heightScale) { + widthScale = heightScale; + } else if (heightScale < widthScale) { + heightScale = widthScale; + } + + d->paintedHeight = heightScale * qreal(d->pix.height()); + d->paintedWidth = widthScale * qreal(d->pix.width()); + } else if (d->fillMode == Pad) { + d->paintedWidth = d->pix.width(); + d->paintedHeight = d->pix.height(); + } else { + d->paintedWidth = width(); + d->paintedHeight = height(); + } + emit paintedGeometryChanged(); +} + +void QQuickImage::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + QQuickImageBase::geometryChanged(newGeometry, oldGeometry); + updatePaintedGeometry(); +} + +QRectF QQuickImage::boundingRect() const +{ + Q_D(const QQuickImage); + return QRectF(0, 0, qMax(width(), d->paintedWidth), qMax(height(), d->paintedHeight)); +} + +QSGTextureProvider *QQuickImage::textureProvider() const +{ + Q_D(const QQuickImage); + if (!d->provider) { + // Make sure it gets thread affinity on the rendering thread so deletion works properly.. + Q_ASSERT_X(d->canvas + && d->sceneGraphContext() + && QThread::currentThread() == d->sceneGraphContext()->thread(), + "QQuickImage::textureProvider", + "Cannot be used outside the GUI thread"); + QQuickImagePrivate *dd = const_cast<QQuickImagePrivate *>(d); + dd->provider = new QQuickImageTextureProvider; + dd->provider->m_smooth = d->smooth; + dd->provider->m_texture = d->sceneGraphContext()->textureForFactory(d->pix.textureFactory()); + } + + return d->provider; +} + +QSGNode *QQuickImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) +{ + Q_D(QQuickImage); + + QSGTexture *texture = d->sceneGraphContext()->textureForFactory(d->pix.textureFactory()); + + // Copy over the current texture state into the texture provider... + if (d->provider) { + d->provider->m_smooth = d->smooth; + d->provider->m_texture = texture; + } + + if (!texture || width() <= 0 || height() <= 0) { + delete oldNode; + return 0; + } + + QSGImageNode *node = static_cast<QSGImageNode *>(oldNode); + if (!node) { + d->pixmapChanged = true; + node = d->sceneGraphContext()->createImageNode(); + node->setTexture(texture); + } + + if (d->pixmapChanged) { + // force update the texture in the node to trigger reconstruction of + // geometry and the likes when a atlas segment has changed. + node->setTexture(0); + node->setTexture(texture); + d->pixmapChanged = false; + } + + QRectF targetRect; + QRectF sourceRect; + QSGTexture::WrapMode hWrap = QSGTexture::ClampToEdge; + QSGTexture::WrapMode vWrap = QSGTexture::ClampToEdge; + + qreal pixWidth = (d->fillMode == PreserveAspectFit) ? d->paintedWidth : d->pix.width(); + qreal pixHeight = (d->fillMode == PreserveAspectFit) ? d->paintedHeight : d->pix.height(); + + int xOffset = 0; + if (d->hAlign == QQuickImage::AlignHCenter) + xOffset = qCeil((width() - pixWidth) / 2.); + else if (d->hAlign == QQuickImage::AlignRight) + xOffset = qCeil(width() - pixWidth); + + int yOffset = 0; + if (d->vAlign == QQuickImage::AlignVCenter) + yOffset = qCeil((height() - pixHeight) / 2.); + else if (d->vAlign == QQuickImage::AlignBottom) + yOffset = qCeil(height() - pixHeight); + + switch (d->fillMode) { + default: + case Stretch: + targetRect = QRectF(0, 0, width(), height()); + sourceRect = d->pix.rect(); + break; + + case PreserveAspectFit: + targetRect = QRectF(xOffset, yOffset, d->paintedWidth, d->paintedHeight); + sourceRect = d->pix.rect(); + break; + + case PreserveAspectCrop: { + targetRect = QRect(0, 0, width(), height()); + qreal wscale = width() / qreal(d->pix.width()); + qreal hscale = height() / qreal(d->pix.height()); + + if (wscale > hscale) { + int src = (hscale / wscale) * qreal(d->pix.height()); + int y = 0; + if (d->vAlign == QQuickImage::AlignVCenter) + y = qCeil((d->pix.height() - src) / 2.); + else if (d->vAlign == QQuickImage::AlignBottom) + y = qCeil(d->pix.height() - src); + sourceRect = QRectF(0, y, d->pix.width(), src); + + } else { + int src = (wscale / hscale) * qreal(d->pix.width()); + int x = 0; + if (d->hAlign == QQuickImage::AlignHCenter) + x = qCeil((d->pix.width() - src) / 2.); + else if (d->hAlign == QQuickImage::AlignRight) + x = qCeil(d->pix.width() - src); + sourceRect = QRectF(x, 0, src, d->pix.height()); + } + } + break; + + case Tile: + targetRect = QRectF(0, 0, width(), height()); + sourceRect = QRectF(-xOffset, -yOffset, width(), height()); + hWrap = QSGTexture::Repeat; + vWrap = QSGTexture::Repeat; + break; + + case TileHorizontally: + targetRect = QRectF(0, 0, width(), height()); + sourceRect = QRectF(-xOffset, 0, width(), d->pix.height()); + hWrap = QSGTexture::Repeat; + break; + + case TileVertically: + targetRect = QRectF(0, 0, width(), height()); + sourceRect = QRectF(0, -yOffset, d->pix.width(), height()); + vWrap = QSGTexture::Repeat; + break; + + case Pad: + qreal w = qMin(qreal(d->pix.width()), width()); + qreal h = qMin(qreal(d->pix.height()), height()); + qreal x = (d->pix.width() > width()) ? -xOffset : 0; + qreal y = (d->pix.height() > height()) ? -yOffset : 0; + targetRect = QRectF(x + xOffset, y + yOffset, w, h); + sourceRect = QRectF(x, y, w, h); + break; + }; + + QRectF nsrect(sourceRect.x() / d->pix.width(), + sourceRect.y() / d->pix.height(), + sourceRect.width() / d->pix.width(), + sourceRect.height() / d->pix.height()); + + if (d->mirror) { + qreal oldLeft = nsrect.left(); + nsrect.setLeft(nsrect.right()); + nsrect.setRight(oldLeft); + } + + node->setHorizontalWrapMode(hWrap); + node->setVerticalWrapMode(vWrap); + node->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest); + + node->setTargetRect(targetRect); + node->setSourceRect(nsrect); + node->update(); + + return node; +} + +void QQuickImage::pixmapChange() +{ + Q_D(QQuickImage); + // PreserveAspectFit calculates the implicit size differently so we + // don't call our superclass pixmapChange(), since that would + // result in the implicit size being set incorrectly, then updated + // in updatePaintedGeometry() + if (d->fillMode != PreserveAspectFit) + QQuickImageBase::pixmapChange(); + updatePaintedGeometry(); + d->pixmapChanged = true; + + // When the pixmap changes, such as being deleted, we need to update the textures + update(); +} + +QQuickImage::VAlignment QQuickImage::verticalAlignment() const +{ + Q_D(const QQuickImage); + return d->vAlign; +} + +void QQuickImage::setVerticalAlignment(VAlignment align) +{ + Q_D(QQuickImage); + if (d->vAlign == align) + return; + + d->vAlign = align; + update(); + updatePaintedGeometry(); + emit verticalAlignmentChanged(align); +} + +QQuickImage::HAlignment QQuickImage::horizontalAlignment() const +{ + Q_D(const QQuickImage); + return d->hAlign; +} + +void QQuickImage::setHorizontalAlignment(HAlignment align) +{ + Q_D(QQuickImage); + if (d->hAlign == align) + return; + + d->hAlign = align; + update(); + updatePaintedGeometry(); + emit horizontalAlignmentChanged(align); +} + +QT_END_NAMESPACE |