aboutsummaryrefslogtreecommitdiffstats
path: root/src/declarative/items/context2d/qsgcanvasitem.cpp
diff options
context:
space:
mode:
authorCharles Yin <charles.yin@nokia.com>2011-08-09 16:44:38 +1000
committerQt by Nokia <qt-info@nokia.com>2011-09-12 17:04:03 +0200
commit82b21536e72a640699594b3e82a5a6182a95521c (patch)
treed641802de88c644e12b9cf3e2279f3aae084779b /src/declarative/items/context2d/qsgcanvasitem.cpp
parent6a15dae8c2f09c99bb124b4587ff8088b35a7273 (diff)
canvas item refactors
1.Supports tiled canvas with canvasSize, tileSize and canvasWindow 2.Supports different rendering targets: Canvas.Image and Canvas.FrameBufferObject by renderTarget property 3.Supports thread rendering when possible by threadRendering property. 4.Refactors QSGContext2D code, move some logic to QSGContext2DCommandBuffer,QSGContext2DTexture,QSGContext2DTile, etc 5.Updates/adds some canvas examples 6.Some improvements for context2d API 6.1 drawImage() now loads image asynchoronously and draw images automatically when they are ready 6.2 adds fillRule supports 6.3 add svg path supports 6.4 Pixel operations (getImageData/putImageData/createImageData) now have better performance by using V8 indexed array accessors 6.5 Uses QTransform instead of QMatrix 6.6 Gradients/patterns now are V8 values, not QObjects 6.7 Supports measureText and TextMetrics interface 6.8 Gives not support warnings for unimplemented functions (drawFocusRing,setCaretSelectionRect,caretBlinkRate) 6.9 Better error handling, throw standard DOM exceptions according to the HTML5 context2d spec. 6.10 Adds shear, resetTransform to matrix operations 6.11 Adds roundedRect, ellipse, text to path operations 6.12 Adds new features to CanvasImageData interface 1) adds mirror() function 2) adds filter() function, include the following filters: Threshold GrayScale Brightness Invert Blur Blend Opaque Convolute 7. Adds documentations Change-Id: Id19224260d6a3fdc589d1f9681c34a88a7e7b3e5 Reviewed-on: http://codereview.qt-project.org/3621 Reviewed-by: Charles Yin <charles.yin@nokia.com>
Diffstat (limited to 'src/declarative/items/context2d/qsgcanvasitem.cpp')
-rw-r--r--src/declarative/items/context2d/qsgcanvasitem.cpp529
1 files changed, 422 insertions, 107 deletions
diff --git a/src/declarative/items/context2d/qsgcanvasitem.cpp b/src/declarative/items/context2d/qsgcanvasitem.cpp
index 0bd9a47d8d..03eab72368 100644
--- a/src/declarative/items/context2d/qsgcanvasitem.cpp
+++ b/src/declarative/items/context2d/qsgcanvasitem.cpp
@@ -39,124 +39,366 @@
**
****************************************************************************/
-#include <QtGui/qpainter.h>
-
#include "private/qsgadaptationlayer_p.h"
#include "qsgcanvasitem_p.h"
-#include "qsgpainteditem_p.h"
+#include "qsgitem_p.h"
#include "qsgcontext2d_p.h"
-#include "private/qsgpainternode_p.h"
+#include "qsgcontext2dnode_p.h"
+#include "qsgcontext2dtexture_p.h"
+#include "qdeclarativepixmapcache_p.h"
+
#include <qdeclarativeinfo.h>
#include "qdeclarativeengine_p.h"
#include <QtCore/QBuffer>
QT_BEGIN_NAMESPACE
-class QSGCanvasItemPrivate : public QSGPaintedItemPrivate
+class QSGCanvasItemPrivate : public QSGItemPrivate
{
public:
QSGCanvasItemPrivate();
~QSGCanvasItemPrivate();
QSGContext2D* context;
- QList<QRect> dirtyRegions;
- QRect unitedDirtyRegion;
- qreal canvasX;
- qreal canvasY;
+ QSGContext2DTexture* texture;
+ QSizeF canvasSize;
+ QSize tileSize;
+ QRectF canvasWindow;
+ QRectF dirtyRect;
+ uint threadRendering : 1;
+ uint hasCanvasSize :1;
+ uint hasTileSize :1;
+ uint hasCanvasWindow :1;
+ uint componentCompleted :1;
+ QSGCanvasItem::RenderTarget renderTarget;
+ QHash<QUrl, QDeclarativePixmap*> images;
+ QUrl baseUrl;
};
-
-/*!
- \internal
-*/
QSGCanvasItemPrivate::QSGCanvasItemPrivate()
- : QSGPaintedItemPrivate()
+ : QSGItemPrivate()
, context(0)
- , unitedDirtyRegion()
- , canvasX(0.)
- , canvasY(0.)
+ , texture(0)
+ , canvasSize(1, 1)
+ , tileSize(1, 1)
+ , threadRendering(true)
+ , hasCanvasSize(false)
+ , hasTileSize(false)
+ , hasCanvasWindow(false)
+ , componentCompleted(false)
+ , renderTarget(QSGCanvasItem::Image)
{
}
QSGCanvasItemPrivate::~QSGCanvasItemPrivate()
{
+ qDeleteAll(images);
+}
+
+/*!
+ \qmlclass Canvas QSGCanvasItem
+ \inqmlmodule QtQuick 2
+ \since QtQuick 2.0
+ \brief The Canvas item provides HTML5 canvas element compatible scripts with a resolution-dependent bitmap canvas.
+ \inherits Item
+ \ingroup qml-basic-visual-elements
+
+ The canvas is used to render graphs, game graphics, or other visual images on the fly.
+
+ \section1 Example Usage
+
+ \section1 Thread Rendering Mode
+
+ \section1 Tiled Canvas
+
+ \section1 Quality and Performance
+
+ By default, all of the drawing commands are rendered by a dedicated thread for better
+ performance and avoid blocking the main GUI thread. Setting the \l threadRendering property
+ to false can make the canvas rendering stay in the main GUI thread.
+
+ \sa Context2D
+*/
+
+/*!
+ Constructs a QSGCanvasItem with the given \a parent item.
+ */
+QSGCanvasItem::QSGCanvasItem(QSGItem *parent)
+ : QSGItem(*(new QSGCanvasItemPrivate), parent)
+{
+ setFlag(ItemHasContents);
+}
+
+/*!
+ Destroys the QSGCanvasItem.
+*/
+QSGCanvasItem::~QSGCanvasItem()
+{
+ Q_D(QSGCanvasItem);
+ if (d->texture) {
+ d->texture->setItem(0);
+ d->texture->deleteLater();
+ }
+ delete d->context;
+}
+
+/*!
+ \qmlproperty size QtQuick2::Canvas::canvasSize
+ Holds the logical canvas size that the context paints on.
+
+ By default, the canvas size is the same with the current canvas item size.
+ \sa tileSize canvasWindow
+*/
+QSizeF QSGCanvasItem::canvasSize() const
+{
+ Q_D(const QSGCanvasItem);
+ return d->canvasSize;
+}
+
+void QSGCanvasItem::setCanvasSize(const QSizeF & size)
+{
+ Q_D(QSGCanvasItem);
+ if (d->canvasSize != size) {
+ d->hasCanvasSize = true;
+ d->canvasSize = size;
+ emit canvasSizeChanged();
+ polish();
+ }
}
+/*!
+ \qmlproperty size QtQuick2::Canvas::tileSize
+ Holds the canvas rendering tile size.
+
+ The canvas render can improve the rendering performance
+ by rendering and caching each tiles instead of rendering
+ the whole canvas everytime.
+
+ Additionally, the canvas size could be infinitely large
+ because only those tiles within the current visible region
+ are actually rendered.
+
+ By default, the tile size is the same with the canvas size.
+ \sa canvasSize
+*/
+QSize QSGCanvasItem::tileSize() const
+{
+ Q_D(const QSGCanvasItem);
+ return d->tileSize;
+}
-void QSGCanvasItem::setCanvasX(qreal x)
+void QSGCanvasItem::setTileSize(const QSize & size)
{
Q_D(QSGCanvasItem);
- if (d->canvasX != x) {
- d->canvasX = x;
- emit canvasXChanged();
+ if (d->tileSize != size) {
+ d->hasTileSize = true;
+ d->tileSize = size;
+
+ emit tileSizeChanged();
+ polish();
}
}
-void QSGCanvasItem::setCanvasY(qreal y)
+
+/*!
+ \qmlproperty rect QtQuick2::Canvas::canvasWindow
+ Holds the current canvas visible window.
+
+ This property is read only, a canvas window can
+ be changed by changing the canvas item width, height
+ or the canvas viewport properties.
+
+ When painting on a canvas item, even the item is visible
+ and focused, only the current canvas window area is actually
+ rendered even the painting commands may paint shaps out of
+ the canvas window.
+
+ The canvas window size is already synchronized with the canvas item size.
+ \sa width height canvasSize
+*/
+QRectF QSGCanvasItem::canvasWindow() const
+{
+ Q_D(const QSGCanvasItem);
+ return d->canvasWindow;
+}
+
+void QSGCanvasItem::setCanvasWindow(const QRectF& rect)
{
Q_D(QSGCanvasItem);
- if (d->canvasY != y) {
- d->canvasY = y;
- emit canvasYChanged();
+ if (d->canvasWindow != rect) {
+ d->canvasWindow = rect;
+
+ d->hasCanvasWindow = true;
+ emit canvasWindowChanged();
+ polish();
}
}
-qreal QSGCanvasItem::canvasX() const
+
+QSGContext2D* QSGCanvasItem::context() const
{
Q_D(const QSGCanvasItem);
- return d->canvasX;
+ return d->context;
}
-qreal QSGCanvasItem::canvasY() const
+bool QSGCanvasItem::threadRendering() const
{
Q_D(const QSGCanvasItem);
- return d->canvasY;
+ return d->threadRendering;
}
-QPointF QSGCanvasItem::canvasPos() const
+
+QSGCanvasItem::RenderTarget QSGCanvasItem::renderTarget() const
{
Q_D(const QSGCanvasItem);
- return QPointF(d->canvasX, d->canvasY);
+ return d->renderTarget;
}
-/*!
- Constructs a QSGCanvasItem with the given \a parent item.
- */
-QSGCanvasItem::QSGCanvasItem(QSGItem *parent)
- : QSGPaintedItem(*(new QSGCanvasItemPrivate), parent)
+void QSGCanvasItem::setRenderTarget(RenderTarget target)
{
+ Q_D(QSGCanvasItem);
+ if (d->renderTarget != target) {
+ d->renderTarget = target;
+
+ if (d->componentCompleted)
+ createTexture();
+ emit renderTargetChanged();
+ }
+}
+
+void QSGCanvasItem::_doPainting(const QRectF& region)
+{
+ Q_D(QSGCanvasItem);
+ emit paint(QDeclarativeV8Handle::fromHandle(d->context->v8value())
+ , QSGContext2DTexture::tiledRect(region, d->tileSize));
+ if (d->texture)
+ d->texture->wake();
}
/*!
- Destroys the QSGCanvasItem.
+ \qmlproperty bool QtQuick2::Canvas::threadRendering
+ Holds the current canvas rendering mode.
+
+ When this property is true, all canvas painting commands
+ are rendered in a background rendering thread, otherwise
+ the rendering happens in the main GUI thread.
+
+ The default threadRendering value is true.
*/
-QSGCanvasItem::~QSGCanvasItem()
+void QSGCanvasItem::setThreadRendering(bool threadRendering)
{
+ Q_D(QSGCanvasItem);
+ if (d->threadRendering != threadRendering) {
+ d->threadRendering = threadRendering;
+
+ if (d->componentCompleted)
+ createTexture();
+
+ if (d->threadRendering)
+ connect(this, SIGNAL(painted()), SLOT(update()));
+ else
+ disconnect(this, SIGNAL(painted()), this, SLOT(update()));
+ emit threadRenderingChanged();
+ polish();
+ }
}
-void QSGCanvasItem::paint(QPainter *painter)
+void QSGCanvasItem::geometryChanged(const QRectF &newGeometry,
+ const QRectF &oldGeometry)
{
Q_D(QSGCanvasItem);
- if (d->context && d->context->isDirty()) {
- painter->setWindow(-d->canvasX, -d->canvasY, d->width, d->height);
- painter->setViewport(0, 0, d->width, d->height);
- painter->scale(d->contentsScale, d->contentsScale);
+ QSGItem::geometryChanged(newGeometry, oldGeometry);
+
+ const qreal w = newGeometry.width();
+ const qreal h = newGeometry.height();
+
+ if (!d->hasCanvasSize) {
+ d->canvasSize = QSizeF(w, h);
+ emit canvasSizeChanged();
+ }
+
+ if (!d->hasTileSize) {
+ d->tileSize = d->canvasSize.toSize();
+ emit tileSizeChanged();
+ }
- d->context->paint(painter);
- emit painted();
+ if (!d->hasCanvasWindow) {
+ d->canvasWindow = newGeometry;
+ emit canvasWindowChanged();
}
+
+ polish();
}
void QSGCanvasItem::componentComplete()
{
- const QMetaObject *metaObject = this->metaObject();
- int propertyCount = metaObject->propertyCount();
- int requestPaintMethod = metaObject->indexOfMethod("requestPaint(const QRect&)");
- for (int ii = QSGCanvasItem::staticMetaObject.propertyCount(); ii < propertyCount; ++ii) {
- QMetaProperty p = metaObject->property(ii);
- if (p.hasNotifySignal())
- QMetaObject::connect(this, p.notifySignalIndex(), this, requestPaintMethod, 0, 0);
- }
+ Q_D(QSGCanvasItem);
createContext();
+ createTexture();
+
+ markDirty(d->canvasWindow);
+ QSGItem::componentComplete();
+
+ d->baseUrl = qmlEngine(this)->contextForObject(this)->baseUrl();
+ d->componentCompleted = true;
+}
+
+void QSGCanvasItem::updatePolish()
+{
+ Q_D(QSGCanvasItem);
+
+ QSGItem::updatePolish();
+ if (d->texture) {
+ if (!d->threadRendering && d->dirtyRect.isValid())
+ _doPainting(d->dirtyRect);
+
+ d->texture->canvasChanged(d->canvasSize.toSize()
+ , d->tileSize
+ , d->canvasWindow.toAlignedRect()
+ , d->dirtyRect.toAlignedRect()
+ , d->smooth);
+ d->dirtyRect = QRectF();
+ }
+}
+
+QSGNode *QSGCanvasItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
+{
+ Q_D(QSGCanvasItem);
+ QSGContext2DNode *node = static_cast<QSGContext2DNode *>(oldNode);
+ if (!node)
+ node = new QSGContext2DNode(this);
+
+ node->setTexture(d->texture);
+ node->update();
+ return node;
+}
+
+void QSGCanvasItem::createTexture()
+{
+ Q_D(QSGCanvasItem);
- QSGPaintedItem::componentComplete();
+ if (!d->texture
+ || d->texture->threadRendering() != d->threadRendering
+ || d->texture->renderTarget() != d->renderTarget) {
+ if (d->texture) {
+ d->texture->deleteLater();
+ d->texture = 0;
+ }
+
+ if (d->renderTarget == QSGCanvasItem::Image) {
+ d->texture = new QSGContext2DImageTexture(d->threadRendering);
+ } else if (d->renderTarget == QSGCanvasItem::FramebufferObject) {
+ d->texture = new QSGContext2DFBOTexture();
+ }
+
+ if (d->threadRendering && !d->texture->supportThreadRendering()) {
+ qWarning("Canvas: render target does not support thread rendering, force to non-thread rendering mode.");
+ d->threadRendering = false;
+ emit threadRenderingChanged();
+ }
+
+ if (d->threadRendering)
+ connect(d->texture, SIGNAL(textureChanged()), this, SLOT(update()));
+
+ d->texture->setItem(this);
+ }
}
void QSGCanvasItem::createContext()
@@ -171,6 +413,13 @@ void QSGCanvasItem::createContext()
d->context->setV8Engine(e);
}
+/*!
+ \qmlmethod QtQuick2::Context2D QtQuick2::Canvas::getContext(string contextId)
+
+ Currently, the canvas item only support the 2D context. If the \a contextId
+ parameter isn't provided or is "2d", then the QtQuick2::Context2D object is
+ returned, otherwise returns an invalid value.
+ */
QDeclarativeV8Handle QSGCanvasItem::getContext(const QString &contextId)
{
Q_D(QSGCanvasItem);
@@ -181,91 +430,157 @@ QDeclarativeV8Handle QSGCanvasItem::getContext(const QString &contextId)
return QDeclarativeV8Handle::fromHandle(v8::Undefined());
}
-void QSGCanvasItem::requestPaint(const QRect& r)
+/*!
+ \qmlmethod void QtQuick2::Canvas::markDirty(rect region)
+
+ Mark the given \a region as dirty, so that when this region is visible
+ the canvas render will redraw it. During the rendering stage, the
+ canvas renderer may emit the canvas' "paint" signal so the actual painting
+ scripts can be putted into the canvas's "onPaint" function.
+
+ \sa QtQuick2::Canvas::paint
+ */
+void QSGCanvasItem::markDirty(const QRectF& region)
{
Q_D(QSGCanvasItem);
+ d->dirtyRect |= region;
+ polish();
+}
- QRect region;
- if (!r.isValid())
- region = QRect(d->canvasX, d->canvasY, d->width, d->height);
- else
- region = r;
- foreach (const QRect& rect, d->dirtyRegions) {
- if (rect.contains(region))
- return;
- }
+/*!
+ \qmlmethod bool QtQuick2::Canvas::save(string filename)
- d->unitedDirtyRegion = d->unitedDirtyRegion.unite(region);
- d->dirtyRegions.append(region);
- polish();
- update(d->unitedDirtyRegion);
-}
+ Save the current canvas content into an image file \a filename.
+ The saved image format is automatically decided by the \a filename's
+ suffix.
+
+ Note: calling this method will force painting the whole canvas, not the
+ current canvas visible window.
+ \sa canvasWindow canvasSize toDataURL
+ */
bool QSGCanvasItem::save(const QString &filename) const
{
- Q_D(const QSGCanvasItem);
- QSGPainterNode* node = static_cast<QSGPainterNode*>(d->paintNode);
- if (node) {
- QImage image = node->toImage();
- image.save(filename);
+ return toImage().save(filename);
+}
+
+QImage QSGCanvasItem::loadedImage(const QUrl& url)
+{
+ Q_D(QSGCanvasItem);
+ QUrl fullPathUrl = d->baseUrl.resolved(url);
+ if (!d->images.contains(fullPathUrl)) {
+ loadImage(url);
+ }
+ QDeclarativePixmap* pix = d->images.value(fullPathUrl);
+ if (pix->isLoading() || pix->isError()) {
+ return QImage();
+ }
+ return pix->pixmap().toImage();
+}
+void QSGCanvasItem::loadImage(const QUrl& url)
+{
+ Q_D(QSGCanvasItem);
+ QUrl fullPathUrl = d->baseUrl.resolved(url);
+ if (!d->images.contains(fullPathUrl)) {
+ QDeclarativePixmap* pix = new QDeclarativePixmap();
+ d->images.insert(fullPathUrl, pix);
+
+ pix->load(qmlEngine(this)
+ , fullPathUrl
+ , QDeclarativePixmap::Cache | QDeclarativePixmap::Asynchronous);
+ pix->connectFinished(this, SIGNAL(imageLoaded()));
}
- return false;
}
-void QSGCanvasItem::updatePolish()
+void QSGCanvasItem::unloadImage(const QUrl& url)
{
Q_D(QSGCanvasItem);
- QDeclarativeV8Handle context = QDeclarativeV8Handle::fromHandle(d->context->v8value());
+ QUrl removeThis = d->baseUrl.resolved(url);
+ if (d->images.contains(removeThis)) {
+ delete d->images.value(removeThis);
+ d->images.remove(removeThis);
+ }
+}
+
+bool QSGCanvasItem::isImageError(const QUrl& url) const
+{
+ Q_D(const QSGCanvasItem);
+ QUrl fullPathUrl = d->baseUrl.resolved(url);
+ return d->images.contains(fullPathUrl)
+ && d->images.value(fullPathUrl)->isError();
+}
- d->context->setValid(true);
+bool QSGCanvasItem::isImageLoading(const QUrl& url) const
+{
+ Q_D(const QSGCanvasItem);
+ QUrl fullPathUrl = d->baseUrl.resolved(url);
+ return d->images.contains(fullPathUrl)
+ && d->images.value(fullPathUrl)->isLoading();
+}
- // d->context->setTileRect(QRectF(d->canvasX, d->canvasY, d->width, d->height).intersected(QRectF(0, 0, d->width, d->height)));
- // d->context->setTileRect(QRectF(d->canvasX, d->canvasY, d->width, d->height));
+bool QSGCanvasItem::isImageLoaded(const QUrl& url) const
+{
+ Q_D(const QSGCanvasItem);
+ QUrl fullPathUrl = d->baseUrl.resolved(url);
+ return d->images.contains(fullPathUrl)
+ && d->images.value(fullPathUrl)->isReady();
+}
- foreach (const QRect& region, d->dirtyRegions) {
- emit paint(context, region);
+QImage QSGCanvasItem::toImage(const QRectF& region) const
+{
+ Q_D(const QSGCanvasItem);
+ if (d->texture) {
+ if (region.isEmpty())
+ return d->texture->toImage(canvasWindow());
+ else
+ return d->texture->toImage(region);
}
- d->dirtyRegions.clear();
+ return QImage();
+}
- d->context->setValid(false);
+/*!
+ \qmlmethod string QtQuick2::Canvas::toDataURL(string mimeType)
- QSGPaintedItem::updatePolish();
-}
+ Returns a data: URL for the image in the canvas.
+ The default \a mimeType is "image/png".
+ Note: calling this method will force painting the whole canvas, not the
+ current canvas visible window.
+
+ \sa canvasWindow canvasSize save
+ */
QString QSGCanvasItem::toDataURL(const QString& mimeType) const
{
- Q_D(const QSGCanvasItem);
+ QImage image = toImage();
- QSGPainterNode* node = static_cast<QSGPainterNode*>(d->paintNode);
- if (node) {
- QImage image = node->toImage();
+ if (!image.isNull()) {
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
QString mime = mimeType;
QString type;
- if (mimeType == QLatin1String("image/bmp"))
- type = QLatin1String("BMP");
- else if (mimeType == QLatin1String("image/jpeg"))
- type = QLatin1String("JPEG");
- else if (mimeType == QLatin1String("image/x-portable-pixmap"))
- type = QLatin1String("PPM");
- else if (mimeType == QLatin1String("image/tiff"))
- type = QLatin1String("TIFF");
- else if (mimeType == QLatin1String("image/xbm"))
- type = QLatin1String("XBM");
- else if (mimeType == QLatin1String("image/xpm"))
- type = QLatin1String("XPM");
+ if (mimeType == QLatin1Literal("image/bmp"))
+ type = QLatin1Literal("BMP");
+ else if (mimeType == QLatin1Literal("image/jpeg"))
+ type = QLatin1Literal("JPEG");
+ else if (mimeType == QLatin1Literal("image/x-portable-pixmap"))
+ type = QLatin1Literal("PPM");
+ else if (mimeType == QLatin1Literal("image/tiff"))
+ type = QLatin1Literal("TIFF");
+ else if (mimeType == QLatin1Literal("image/xbm"))
+ type = QLatin1Literal("XBM");
+ else if (mimeType == QLatin1Literal("image/xpm"))
+ type = QLatin1Literal("XPM");
else {
- type = QLatin1String("PNG");
- mime = QLatin1String("image/png");
+ type = QLatin1Literal("PNG");
+ mime = QLatin1Literal("image/png");
}
image.save(&buffer, type.toAscii());
buffer.close();
- QString dataUrl = QLatin1String("data:%1;base64,%2");
- return dataUrl.arg(mime).arg(ba.toBase64().constData());
+ QString dataUrl = QLatin1Literal("data:%1;base64,%2");
+ return dataUrl.arg(mime).arg(QLatin1String(ba.toBase64().constData()));
}
- return QLatin1String("data:,");
+ return QLatin1Literal("data:,");
}
QT_END_NAMESPACE