1 /****************************************************************************
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include <private/qsgadaptationlayer_p.h>
43 #include "qquickcanvasitem_p.h"
44 #include <private/qquickitem_p.h>
45 #include "qquickcontext2d_p.h"
46 #include "qquickcontext2dnode_p.h"
47 #include "qquickcontext2dtexture_p.h"
48 #include <QtQuick/private/qdeclarativepixmapcache_p.h>
50 #include <qdeclarativeinfo.h>
51 #include <private/qdeclarativeengine_p.h>
52 #include <QtCore/QBuffer>
56 class QQuickCanvasItemPrivate : public QQuickItemPrivate
59 QQuickCanvasItemPrivate();
60 ~QQuickCanvasItemPrivate();
61 QQuickContext2D* context;
62 QQuickContext2DTexture* texture;
67 uint renderInThread : 1;
68 uint hasCanvasSize :1;
70 uint hasCanvasWindow :1;
71 uint componentCompleted :1;
72 QQuickCanvasItem::RenderTarget renderTarget;
73 QHash<QUrl, QDeclarativePixmap*> images;
77 QQuickCanvasItemPrivate::QQuickCanvasItemPrivate()
83 , renderInThread(false)
84 , hasCanvasSize(false)
86 , hasCanvasWindow(false)
87 , componentCompleted(false)
88 , renderTarget(QQuickCanvasItem::FramebufferObject)
92 QQuickCanvasItemPrivate::~QQuickCanvasItemPrivate()
98 \qmlclass Canvas QQuickCanvasItem
99 \inqmlmodule QtQuick 2
101 \brief The Canvas item provides a 2D canvas element which enables drawing via Javascript.
103 \ingroup qml-basic-visual-elements
105 The Canvas item allows drawing of straight and curved lines, simple and
106 complex shapes, graphs, and referenced graphic images. It can also add text, colors,
107 shadows, gradients, and patterns, and do low level pixel operations. The Canvas
108 output may be saved as an image file or serialized to a url.
110 To define a drawing area in the Canvas item set the \c width and \c height properties.
111 For example, the following code creates a Canvas item which has a drawing area with a height of 100
112 pixels and width of 200 pixels:
122 Currently the Canvas item only supports the two-dimensional rendering context.
124 \section1 Threaded Rendering and Render Target
126 The Canvas item supports two render targets: \c Canvas.Image and \c Canvas.FramebufferObject.
128 The \c Canvas.Image render target is a \a QImage object. This render target supports background
129 thread rendering, allowing complex or long running painting to be executed without blocking the UI.
131 The Canvas.FramebufferObject render target utilizes OpenGL hardware accelaration rather than rendering into
132 system memory, which in many cases results in faster rendering.
134 The default render target is Canvas.Image and the default renderInThread property is
137 \section1 Tiled Canvas
138 The Canvas item supports tiled rendering by setting \l canvasSize, \l tileSize
139 and \l canvasWindow properties.
141 Tiling allows efficient display of a very large virtual via a smaller canvas
142 window. The actual memory consumption is in relatation to the canvas window size. The painting
143 code can draw within the virtual canvas without handling coordinate system transformations.
145 The tiles overlapping with the canvas window may be cached eliminating the need to redraw,
146 which can lead to significantly improved performance in some situations.
148 \section1 Pixel Operations
149 All HTML5 2D context pixel operations are supported. In order to ensure improved
150 pixel reading/writing performance the \a Canvas.Image render target should be chosen. The
151 \a Canvas.FramebufferObject render target requires the pixel data to be exchanged between
152 the system memory and the graphic card, which is significantly more expensive. Rendering
153 may also be synchronized with the V-sync signal (to avoid {en.wikipedia.org/wiki/Screen_tearing}{screen tearing})
154 which will futher impact pixel operations with \c Canvas.FrambufferObject render target.
156 \section1 Tips for Porting Existing HTML5 Canvas applications
158 Although the Canvas item is provides a HTML5 like API, HTML5 canvas applications
159 need to be modified to run in the Canvas item:
161 \o Replace all DOM API calls with QML property bindings or Canvas item methods.
162 \o Replace all HTML event handlers with the \a MouseArea item.
163 \o Change setInterval/setTimeout function calls with the \a Timer item.
164 \o Place painting code into the \a QtQuick2::Canvas::onPaint handler and trigger
165 painting by calling the \c markDirty or \c requestPaint methods.
166 \o To draw images, load them by calling the Canvas's loadImage method and then request to paint
167 them in the onImageLoaded handler.
170 \sa QtQuick2::Context2D
173 QQuickCanvasItem::QQuickCanvasItem(QQuickItem *parent)
174 : QQuickItem(*(new QQuickCanvasItemPrivate), parent)
176 setFlag(ItemHasContents);
179 QQuickCanvasItem::~QQuickCanvasItem()
181 Q_D(QQuickCanvasItem);
186 \qmlproperty size QtQuick2::Canvas::canvasSize
187 Holds the logical canvas size that the context paints on.
189 By default, the canvas size is the same size as the current canvas item size.
190 By setting the canvasSize, tileSize and canvasWindow, the Canvas
191 item can act as a large virtual canvas with many seperately rendered tile rectangles
192 Only those tiles within the current canvas window are painted by
193 the Canvas render engine.
195 \sa QtQuick2::Canvas::tileSize QtQuick2::Canvas::canvasWindow
197 QSizeF QQuickCanvasItem::canvasSize() const
199 Q_D(const QQuickCanvasItem);
200 return d->canvasSize;
203 void QQuickCanvasItem::setCanvasSize(const QSizeF & size)
205 Q_D(QQuickCanvasItem);
206 if (d->canvasSize != size) {
207 d->hasCanvasSize = true;
208 d->canvasSize = size;
209 emit canvasSizeChanged();
216 \qmlproperty size QtQuick2::Canvas::tileSize
217 Holds the canvas rendering tile size.
219 The Canvas item enters tiled mode by setting canvasSize, tileSize and
220 the canvasWindow. This can improve rendering performance
221 by rendering and caching tiles instead of rendering the whole canvas every time.
223 Memory will be consumed only by those tiles within the current visible region.
225 By default the tileSize is the same as the canvasSize.
227 \sa QtQuick2::Canvas::canvaasSize QtQuick2::Canvas::canvasWindow
229 QSize QQuickCanvasItem::tileSize() const
231 Q_D(const QQuickCanvasItem);
235 void QQuickCanvasItem::setTileSize(const QSize & size)
237 Q_D(QQuickCanvasItem);
238 if (d->tileSize != size) {
239 d->hasTileSize = true;
242 emit tileSizeChanged();
249 \qmlproperty rect QtQuick2::Canvas::canvasWindow
250 Holds the current canvas visible window.
252 By default the canvasWindow size is the same as the Canvas item
253 size with the topleft point as (0, 0).
255 If the canvasSize is different to the Canvas item size, the Canvas
256 item can display different visible areas by changing the canvas windowSize
259 \sa QtQuick2::Canvas::canvasSize QtQuick2::Canvas::tileSize
261 QRectF QQuickCanvasItem::canvasWindow() const
263 Q_D(const QQuickCanvasItem);
264 return d->canvasWindow;
267 void QQuickCanvasItem::setCanvasWindow(const QRectF& rect)
269 Q_D(QQuickCanvasItem);
270 if (d->canvasWindow != rect) {
271 d->canvasWindow = rect;
273 d->hasCanvasWindow = true;
274 emit canvasWindowChanged();
281 QQuickContext2D* QQuickCanvasItem::context() const
283 Q_D(const QQuickCanvasItem);
287 \qmlproperty bool QtQuick2::Canvas::renderInThread
288 Holds the current canvas rendering mode.
290 Set renderInThread to true to render complex and long
291 running painting in a dedicated background
292 thread, avoiding blocking the main UI.
294 \note: Not all renderTargets support background rendering. If background rendering
295 is not supported by the current renderTarget, the renderInThread
298 The default value is false.
299 \sa QtQuick2::Canvas::renderTarget
301 bool QQuickCanvasItem::renderInThread() const
303 Q_D(const QQuickCanvasItem);
304 return d->renderInThread;
307 \qmlproperty bool QtQuick2::Canvas::renderTarget
308 Holds the current canvas render target.
311 \o Canvas.Image - render to an in memory image buffer, the render
312 target supports background rendering.
313 \o Canvas.FramebufferObject - render to an OpenGL frame buffer,
314 this render target will ignore the
315 renderInThread property. The actual
316 rendering happens in the main QML rendering
317 process, which may be in a seperate render thread
318 or in the main GUI thread depending upon the platform.
321 The default render target is \c Canvas.Image.
322 \sa QtQuick2::Canvas::renderInThread
324 QQuickCanvasItem::RenderTarget QQuickCanvasItem::renderTarget() const
326 Q_D(const QQuickCanvasItem);
327 return d->renderTarget;
330 void QQuickCanvasItem::setRenderTarget(RenderTarget target)
332 Q_D(QQuickCanvasItem);
333 if (d->renderTarget != target) {
334 d->renderTarget = target;
336 if (d->componentCompleted)
338 emit renderTargetChanged();
342 void QQuickCanvasItem::_doPainting(const QRectF& region)
344 Q_D(QQuickCanvasItem);
345 emit paint(QDeclarativeV8Handle::fromHandle(d->context->v8value())
346 , QQuickContext2DTexture::tiledRect(region, d->tileSize));
351 void QQuickCanvasItem::setRenderInThread(bool renderInThread)
353 Q_D(QQuickCanvasItem);
354 if (d->renderInThread != renderInThread) {
355 d->renderInThread = renderInThread;
357 if (d->componentCompleted)
360 if (d->renderInThread)
361 connect(this, SIGNAL(painted()), SLOT(update()));
363 disconnect(this, SIGNAL(painted()), this, SLOT(update()));
364 emit renderInThreadChanged();
370 void QQuickCanvasItem::geometryChanged(const QRectF &newGeometry,
371 const QRectF &oldGeometry)
373 Q_D(QQuickCanvasItem);
374 QQuickItem::geometryChanged(newGeometry, oldGeometry);
376 const qreal w = newGeometry.width();
377 const qreal h = newGeometry.height();
379 if (!d->hasCanvasSize) {
380 d->canvasSize = QSizeF(w, h);
381 emit canvasSizeChanged();
384 if (!d->hasTileSize) {
385 d->tileSize = d->canvasSize.toSize();
386 emit tileSizeChanged();
389 if (!d->hasCanvasWindow) {
390 d->canvasWindow = newGeometry;
391 emit canvasWindowChanged();
398 void QQuickCanvasItem::componentComplete()
400 Q_D(QQuickCanvasItem);
401 QQuickItem::componentComplete();
407 d->baseUrl = qmlEngine(this)->contextForObject(this)->baseUrl();
409 updatePolish(); //force update the canvas sizes to texture for the first time
411 d->componentCompleted = true;
414 void QQuickCanvasItem::updatePolish()
416 Q_D(QQuickCanvasItem);
417 QQuickItem::updatePolish();
419 if (!d->renderInThread && d->dirtyRect.isValid())
420 _doPainting(d->dirtyRect);
422 d->texture->canvasChanged(d->canvasSize.toSize()
424 , d->canvasWindow.toAlignedRect()
425 , d->dirtyRect.toAlignedRect()
427 d->dirtyRect = QRectF();
431 QSGNode *QQuickCanvasItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
433 Q_D(QQuickCanvasItem);
434 QQuickContext2DNode *node = static_cast<QQuickContext2DNode *>(oldNode);
436 node = new QQuickContext2DNode(this);
438 node->setTexture(d->texture);
439 node->setSize(d->canvasWindow.size());
444 void QQuickCanvasItem::createTexture()
446 Q_D(QQuickCanvasItem);
449 || d->texture->threadRendering() != d->renderInThread
450 || d->texture->renderTarget() != d->renderTarget) {
452 d->texture->deleteLater();
456 if (d->renderTarget == QQuickCanvasItem::Image) {
457 d->texture = new QQuickContext2DImageTexture(d->renderInThread);
458 } else if (d->renderTarget == QQuickCanvasItem::FramebufferObject) {
459 d->texture = new QQuickContext2DFBOTexture();
462 if (d->renderInThread && !d->texture->supportThreadRendering()) {
463 qWarning("Canvas: render target does not support thread rendering, force to non-thread rendering mode.");
464 d->renderInThread = false;
465 emit renderInThreadChanged();
468 if (d->renderInThread)
469 connect(d->texture, SIGNAL(textureChanged()), this, SLOT(update()));
471 d->texture->setItem(this);
475 void QQuickCanvasItem::createContext()
477 Q_D(QQuickCanvasItem);
481 d->context = new QQuickContext2D(this);
483 QV8Engine *e = QDeclarativeEnginePrivate::getV8Engine(qmlEngine(this));
484 d->context->setV8Engine(e);
488 \qmlmethod object QtQuick2::Canvas::getContext(string contextId)
490 Currently, the canvas item only supports the 2D context. If the \a contextId
491 parameter isn't provided or is "2d", then the QtQuick2::Context2D object is
492 returned, otherwise returns an invalid value.
494 QDeclarativeV8Handle QQuickCanvasItem::getContext(const QString &contextId)
496 Q_D(QQuickCanvasItem);
498 if (contextId.toLower() != QLatin1String("2d"))
499 return QDeclarativeV8Handle::fromHandle(v8::Undefined());
503 return QDeclarativeV8Handle::fromHandle(d->context->v8value());
507 \qmlmethod void QtQuick2::Canvas::markDirty(rect region)
509 Mark the given \a region as dirty, so that when this region is visible
510 the canvas renderer will redraw it. This will trigger the "onPaint" signal
513 \sa QtQuick2::Canvas::paint QtQuick2::Canvas::requestPaint
515 void QQuickCanvasItem::markDirty(const QRectF& region)
517 Q_D(QQuickCanvasItem);
518 d->dirtyRect |= region;
519 if (d->componentCompleted)
526 \qmlmethod bool QtQuick2::Canvas::save(string filename)
528 Save the current canvas content into an image file \a filename.
529 The saved image format is automatically decided by the \a filename's
532 Note: calling this method will force painting the whole canvas, not just the
533 current canvas visible window.
535 \sa canvasWindow canvasSize toDataURL
537 bool QQuickCanvasItem::save(const QString &filename) const
539 Q_D(const QQuickCanvasItem);
540 QUrl url = d->baseUrl.resolved(QUrl::fromLocalFile(filename));
541 return toImage().save(url.toLocalFile());
544 QImage QQuickCanvasItem::loadedImage(const QUrl& url)
546 Q_D(QQuickCanvasItem);
547 QUrl fullPathUrl = d->baseUrl.resolved(url);
548 if (!d->images.contains(fullPathUrl)) {
551 QDeclarativePixmap* pix = d->images.value(fullPathUrl);
552 if (pix->isLoading() || pix->isError()) {
559 \qmlmethod void QtQuick2::Canvas::loadImage(url image)
560 Loads the given \c image asynchronously.
562 When the image is ready, onImageLoaded will be emitted.
563 The loaded image can be unloaded by the \a QtQuick2::Canvas::unloadImage method.
565 Note: Only loaded images can be painted on the Canvas item.
566 \sa QtQuick2::Canvas::unloadImage QtQuick2::Canvas::imageLoaded QtQuick2::Canvas::isImageLoaded
567 \sa QtQuick2::Context2D::createImageData QtQuick2::Context2D::drawImage
569 void QQuickCanvasItem::loadImage(const QUrl& url)
571 Q_D(QQuickCanvasItem);
572 QUrl fullPathUrl = d->baseUrl.resolved(url);
573 if (!d->images.contains(fullPathUrl)) {
574 QDeclarativePixmap* pix = new QDeclarativePixmap();
575 d->images.insert(fullPathUrl, pix);
577 pix->load(qmlEngine(this)
579 , QDeclarativePixmap::Cache | QDeclarativePixmap::Asynchronous);
580 pix->connectFinished(this, SIGNAL(imageLoaded()));
584 \qmlmethod void QtQuick2::Canvas::unloadImage(url image)
585 Unloads the \c image.
587 Once an image is unloaded it cannot be painted by the canvas context
588 unless it is loaded again.
590 \sa QtQuick2::Canvas::loadImage QtQuick2::Canvas::imageLoaded QtQuick2::Canvas::isImageLoaded
591 \sa QtQuick2::Context2D::createImageData QtQuick2::Context2D::drawImage
593 void QQuickCanvasItem::unloadImage(const QUrl& url)
595 Q_D(QQuickCanvasItem);
596 QUrl removeThis = d->baseUrl.resolved(url);
597 if (d->images.contains(removeThis)) {
598 delete d->images.value(removeThis);
599 d->images.remove(removeThis);
604 \qmlmethod void QtQuick2::Canvas::isImageError(url image)
605 Returns true if the \a image failed to load.
607 \sa QtQuick2::Canvas::loadImage
609 bool QQuickCanvasItem::isImageError(const QUrl& url) const
611 Q_D(const QQuickCanvasItem);
612 QUrl fullPathUrl = d->baseUrl.resolved(url);
613 return d->images.contains(fullPathUrl)
614 && d->images.value(fullPathUrl)->isError();
618 \qmlmethod void QtQuick2::Canvas::isImageLoading(url image)
619 Returns true if the \a image is currently loading.
621 \sa QtQuick2::Canvas::loadImage
623 bool QQuickCanvasItem::isImageLoading(const QUrl& url) const
625 Q_D(const QQuickCanvasItem);
626 QUrl fullPathUrl = d->baseUrl.resolved(url);
627 return d->images.contains(fullPathUrl)
628 && d->images.value(fullPathUrl)->isLoading();
631 \qmlmethod void QtQuick2::Canvas::isImageLoaded(url image)
632 Returns true if the \a image is sucessfully loaded and ready to use.
634 \sa QtQuick2::Canvas::loadImage
636 bool QQuickCanvasItem::isImageLoaded(const QUrl& url) const
638 Q_D(const QQuickCanvasItem);
639 QUrl fullPathUrl = d->baseUrl.resolved(url);
640 return d->images.contains(fullPathUrl)
641 && d->images.value(fullPathUrl)->isReady();
644 QImage QQuickCanvasItem::toImage(const QRectF& region) const
646 Q_D(const QQuickCanvasItem);
648 if (region.isEmpty())
649 return d->texture->toImage(canvasWindow());
651 return d->texture->toImage(region);
657 \qmlmethod string QtQuick2::Canvas::toDataURL(string mimeType)
659 Returns a data URL for the image in the canvas.
661 The default \a mimeType is "image/png".
663 \sa QtQuick2::Canvas::save
665 QString QQuickCanvasItem::toDataURL(const QString& mimeType) const
667 QImage image = toImage();
669 if (!image.isNull()) {
672 buffer.open(QIODevice::WriteOnly);
673 QString mime = mimeType.toLower();
675 if (mime == QLatin1Literal("image/png")) {
676 type = QLatin1Literal("PNG");
677 } else if (mime == QLatin1Literal("image/bmp"))
678 type = QLatin1Literal("BMP");
679 else if (mime == QLatin1Literal("image/jpeg"))
680 type = QLatin1Literal("JPEG");
681 else if (mime == QLatin1Literal("image/x-portable-pixmap"))
682 type = QLatin1Literal("PPM");
683 else if (mime == QLatin1Literal("image/tiff"))
684 type = QLatin1Literal("TIFF");
685 else if (mime == QLatin1Literal("image/xpm"))
686 type = QLatin1Literal("XPM");
688 return QLatin1Literal("data:,");
690 image.save(&buffer, type.toAscii());
692 QString dataUrl = QLatin1Literal("data:%1;base64,%2");
693 return dataUrl.arg(mime).arg(QLatin1String(ba.toBase64().constData()));
695 return QLatin1Literal("data:,");
699 \qmlsignal QtQuick2::Canvas::onPaint(QtQuick2::Context2D context, rect region)
701 This handler is called to render the \a region.
703 This signal can be triggered by QtQuick2::Canvas::markdirty, QtQuick2::Canvas::requestPaint
704 or by changing the current canvas window.
708 \qmlsignal QtQuick2::Canvas::onPainted()
710 This handler is called after all context painting commands are executed and
711 the Canvas has been rendered.