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 <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 if (pix->isLoading())
581 pix->connectFinished(this, SIGNAL(imageLoaded()));
585 \qmlmethod void QtQuick2::Canvas::unloadImage(url image)
586 Unloads the \c image.
588 Once an image is unloaded it cannot be painted by the canvas context
589 unless it is loaded again.
591 \sa QtQuick2::Canvas::loadImage QtQuick2::Canvas::imageLoaded QtQuick2::Canvas::isImageLoaded
592 \sa QtQuick2::Context2D::createImageData QtQuick2::Context2D::drawImage
594 void QQuickCanvasItem::unloadImage(const QUrl& url)
596 Q_D(QQuickCanvasItem);
597 QUrl removeThis = d->baseUrl.resolved(url);
598 if (d->images.contains(removeThis)) {
599 delete d->images.value(removeThis);
600 d->images.remove(removeThis);
605 \qmlmethod void QtQuick2::Canvas::isImageError(url image)
606 Returns true if the \a image failed to load.
608 \sa QtQuick2::Canvas::loadImage
610 bool QQuickCanvasItem::isImageError(const QUrl& url) const
612 Q_D(const QQuickCanvasItem);
613 QUrl fullPathUrl = d->baseUrl.resolved(url);
614 return d->images.contains(fullPathUrl)
615 && d->images.value(fullPathUrl)->isError();
619 \qmlmethod void QtQuick2::Canvas::isImageLoading(url image)
620 Returns true if the \a image is currently loading.
622 \sa QtQuick2::Canvas::loadImage
624 bool QQuickCanvasItem::isImageLoading(const QUrl& url) const
626 Q_D(const QQuickCanvasItem);
627 QUrl fullPathUrl = d->baseUrl.resolved(url);
628 return d->images.contains(fullPathUrl)
629 && d->images.value(fullPathUrl)->isLoading();
632 \qmlmethod void QtQuick2::Canvas::isImageLoaded(url image)
633 Returns true if the \a image is sucessfully loaded and ready to use.
635 \sa QtQuick2::Canvas::loadImage
637 bool QQuickCanvasItem::isImageLoaded(const QUrl& url) const
639 Q_D(const QQuickCanvasItem);
640 QUrl fullPathUrl = d->baseUrl.resolved(url);
641 return d->images.contains(fullPathUrl)
642 && d->images.value(fullPathUrl)->isReady();
645 QImage QQuickCanvasItem::toImage(const QRectF& region) const
647 Q_D(const QQuickCanvasItem);
649 if (region.isEmpty())
650 return d->texture->toImage(canvasWindow());
652 return d->texture->toImage(region);
658 \qmlmethod string QtQuick2::Canvas::toDataURL(string mimeType)
660 Returns a data URL for the image in the canvas.
662 The default \a mimeType is "image/png".
664 \sa QtQuick2::Canvas::save
666 QString QQuickCanvasItem::toDataURL(const QString& mimeType) const
668 QImage image = toImage();
670 if (!image.isNull()) {
673 buffer.open(QIODevice::WriteOnly);
674 QString mime = mimeType.toLower();
676 if (mime == QLatin1Literal("image/png")) {
677 type = QLatin1Literal("PNG");
678 } else if (mime == QLatin1Literal("image/bmp"))
679 type = QLatin1Literal("BMP");
680 else if (mime == QLatin1Literal("image/jpeg"))
681 type = QLatin1Literal("JPEG");
682 else if (mime == QLatin1Literal("image/x-portable-pixmap"))
683 type = QLatin1Literal("PPM");
684 else if (mime == QLatin1Literal("image/tiff"))
685 type = QLatin1Literal("TIFF");
686 else if (mime == QLatin1Literal("image/xpm"))
687 type = QLatin1Literal("XPM");
689 return QLatin1Literal("data:,");
691 image.save(&buffer, type.toAscii());
693 QString dataUrl = QLatin1Literal("data:%1;base64,%2");
694 return dataUrl.arg(mime).arg(QLatin1String(ba.toBase64().constData()));
696 return QLatin1Literal("data:,");
700 \qmlsignal QtQuick2::Canvas::onPaint(QtQuick2::Context2D context, rect region)
702 This handler is called to render the \a region.
704 This signal can be triggered by QtQuick2::Canvas::markdirty, QtQuick2::Canvas::requestPaint
705 or by changing the current canvas window.
709 \qmlsignal QtQuick2::Canvas::onPainted()
711 This handler is called after all context painting commands are executed and
712 the Canvas has been rendered.