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 "qsgcanvasitem_p.h"
44 #include "qsgitem_p.h"
45 #include "qsgcontext2d_p.h"
46 #include "qsgcontext2dnode_p.h"
47 #include "qsgcontext2dtexture_p.h"
48 #include "qdeclarativepixmapcache_p.h"
50 #include <qdeclarativeinfo.h>
51 #include "qdeclarativeengine_p.h"
52 #include <QtCore/QBuffer>
56 class QSGCanvasItemPrivate : public QSGItemPrivate
59 QSGCanvasItemPrivate();
60 ~QSGCanvasItemPrivate();
61 QSGContext2D* context;
62 QSGContext2DTexture* texture;
67 uint renderInThread : 1;
68 uint hasCanvasSize :1;
70 uint hasCanvasWindow :1;
71 uint componentCompleted :1;
72 QSGCanvasItem::RenderTarget renderTarget;
73 QHash<QUrl, QDeclarativePixmap*> images;
77 QSGCanvasItemPrivate::QSGCanvasItemPrivate()
83 , renderInThread(false)
84 , hasCanvasSize(false)
86 , hasCanvasWindow(false)
87 , componentCompleted(false)
88 , renderTarget(QSGCanvasItem::FramebufferObject)
92 QSGCanvasItemPrivate::~QSGCanvasItemPrivate()
98 \qmlclass Canvas QSGCanvasItem
99 \inqmlmodule QtQuick 2
101 \brief The Canvas item provides HTML5 like canvas element which enables you to
102 draw within the item area by using Javascript.
104 \ingroup qml-basic-visual-elements
106 With the Canvas item, users can draw straight and curved lines, simple and
107 complex shapes, graphs, and referenced graphic images. can also add texts, colors,
108 shadows, gradients, and patterns, and do low level pixel operations, etc. The Canvas item
109 also enables you to save or export the canvas as a image file or serialize the image data
112 To define a drawing area in the Canvas item, just set the \c width and \c height properties.
113 For example, the following code creates a Canvas item which has a drawing area with a height of 100
114 pixels and width of 200 pixels:
124 Currently the Canvas item only supports the two-dimensional rendering context.
126 \section1 Thread Rendering and Render Target
127 The Canvas item supports two render targets:Canvas.Image and Canvas.FramebufferObject.
128 The Canvas.Image render target is a \a QImage object which is actually a block of system
129 memory. This render target support background thread rendering. So if some complex or long
130 running painting need to be done, the Canvas.Image with thread rendering mode should be
131 chosen to avoid blocking the UI. Otherwise the Canvas.FramebufferObject render target should
132 be chosen as it could be much faster with good OpenGL hardware accelaration than rendering into
133 system memory, especially when the CPU is already very busy.
135 The default render target is Canvas.Image and the default renderInThread property is
138 \section1 Tiled Canvas
139 The Canvas item also supports tiled rendering mode by setting the proper canvasSize, tileSize
140 and the canvasWindow properties.
142 With tiled canvas, a virtually very large canvas can be provided by a relatively small canvas
143 window. The actual memory consumption only relates to the canvas window size. So the canvas size
144 can be chosen freely as needed. The painting code then doesn't need to worry about the coordinate
145 system and complex matrix transformations at all.
147 As a side effect, by setting a good tile size, the tiles overlapped with the canvas window could be
148 cached and don't need to redraw, which can improve the performance significantly in some situations.
150 \section1 Pixel Operations
151 The Canvas item support all HTML5 2d context pixel operations. In order to get better
152 pixel reading/writing performance, the Canvas.Image render target should be chosen. As
153 for Canvas.FramebufferObject render target, the pixel data need to be exchanged between
154 the system memory and the graphic card, which can't be benefit from the hardware acceleration
155 at all. And the OpenGL rendering may synchronise with the V-Sync signal to avoid the
156 {en.wikipedia.org/wiki/Screen_tearing}{screen tearing} which makes the pixel operations
157 even slower with the Canvas.FrambufferObject render target.
159 \section1 Tips for Porting Existing HTML5 Canvas applications
161 Although the Canvas item is provided as a HTML5 like API, and
162 the canvas context API is as compatible with HTML5 2d context standard
163 as possible, the working HTML5 canvas applications are still need to
164 be modified to run in the Canvas item:
166 \o Removes and replaces all DOM API calls with QML property bindings or Canvas item methods.
167 \o Removes and replaces all HTML envent handlers with the \a MouseArea item.
168 \o Changes the setInterval/setTimeout function calls with the \a Timer item.
169 \o Puts the actual painting code into the \a QtQuick2::Canvas::onPaint handler and triggers the
170 painting by calling the Canvas's \c markDirty or \c requestPaint methods.
171 \o For drawing images, loads them by calling the Canvas's loadImage method and then request to paint
172 them in the onImageLoaded handler.
175 \sa QtQuick2::Context2D
178 QSGCanvasItem::QSGCanvasItem(QSGItem *parent)
179 : QSGItem(*(new QSGCanvasItemPrivate), parent)
181 setFlag(ItemHasContents);
184 QSGCanvasItem::~QSGCanvasItem()
188 d->texture->setItem(0);
189 d->texture->deleteLater();
195 \qmlproperty size QtQuick2::Canvas::canvasSize
196 Holds the logical canvas size that the context paints on.
198 By default, the canvas size is the same size as the current canvas item size.
199 By setting the canvas size, tile size and canvas window, the Canvas
200 item can act as a virtual large canvas with many seperately rendered tile rectangle
201 areas. Only those tiles within the current canvas window would be painted by
202 the Canvas render engine.
203 \sa QtQuick2::Canvas::tileSize QtQuick2::Canvas::canvasWindow
205 QSizeF QSGCanvasItem::canvasSize() const
207 Q_D(const QSGCanvasItem);
208 return d->canvasSize;
211 void QSGCanvasItem::setCanvasSize(const QSizeF & size)
214 if (d->canvasSize != size) {
215 d->hasCanvasSize = true;
216 d->canvasSize = size;
217 emit canvasSizeChanged();
224 \qmlproperty size QtQuick2::Canvas::tileSize
225 Holds the canvas rendering tile size.
227 When the Canvas item in tiled mode by setting the canvas size, tile size and
228 the canvas window. The canvas render can improve the rendering performance
229 by rendering and caching tiles instead of rendering the whole canvas everytime.
231 Additionally, the canvas size could be infinitely large without allocating more
232 memories because only those tiles within the current visible region
233 are actually rendered.
235 By default, the tile size is the same with the canvas size.
236 \sa QtQuick2::Canvas::canvaasSize QtQuick2::Canvas::canvasWindow
238 QSize QSGCanvasItem::tileSize() const
240 Q_D(const QSGCanvasItem);
244 void QSGCanvasItem::setTileSize(const QSize & size)
247 if (d->tileSize != size) {
248 d->hasTileSize = true;
251 emit tileSizeChanged();
258 \qmlproperty rect QtQuick2::Canvas::canvasWindow
259 Holds the current canvas visible window.
261 By default, the canvas window size is the same as the Canvas item
262 size with the topleft point as (0, 0).
264 If the canvas size is different with the Canvas item size, the Canvas
265 item can display different visible areas by changing the canvas window's size
267 \sa QtQuick2::Canvas::canvasSize QtQuick2::Canvas::tileSize
269 QRectF QSGCanvasItem::canvasWindow() const
271 Q_D(const QSGCanvasItem);
272 return d->canvasWindow;
275 void QSGCanvasItem::setCanvasWindow(const QRectF& rect)
278 if (d->canvasWindow != rect) {
279 d->canvasWindow = rect;
281 d->hasCanvasWindow = true;
282 emit canvasWindowChanged();
289 QSGContext2D* QSGCanvasItem::context() const
291 Q_D(const QSGCanvasItem);
295 \qmlproperty bool QtQuick2::Canvas::renderInThread
296 Holds the current canvas rendering mode.
298 By setting the renderInThread to true, complex and long
299 running painting can be rendered in a dedicated background
300 rendering thread to avoid blocking the main GUI.
302 Note: Different renderTarget may or may not support the
303 background rendering thread, if not, the renderInThread
304 property will be ignored.
306 The default value is false.
307 \sa QtQuick2::Canvas::renderTarget
309 bool QSGCanvasItem::renderInThread() const
311 Q_D(const QSGCanvasItem);
312 return d->renderInThread;
315 \qmlproperty bool QtQuick2::Canvas::renderTarget
316 Holds the current canvas render target.
319 \o Canvas.Image - render to an in memory image buffer, the render
320 target supports background rendering.
321 \o Canvas.FramebufferObject - render to an OpenGL frame buffer,
322 this render target will ignore the
323 renderInThread property. The actual
324 rendering happens in the main QML rendering
325 process, which may be in a seperate render thread
326 or in the main GUI thread depends on the platforms.
329 The default render target is \c Canvas.Image.
330 \sa QtQuick2::Canvas::renderInThread
332 QSGCanvasItem::RenderTarget QSGCanvasItem::renderTarget() const
334 Q_D(const QSGCanvasItem);
335 return d->renderTarget;
338 void QSGCanvasItem::setRenderTarget(RenderTarget target)
341 if (d->renderTarget != target) {
342 d->renderTarget = target;
344 if (d->componentCompleted)
346 emit renderTargetChanged();
350 void QSGCanvasItem::_doPainting(const QRectF& region)
353 emit paint(QDeclarativeV8Handle::fromHandle(d->context->v8value())
354 , QSGContext2DTexture::tiledRect(region, d->tileSize));
360 \qmlproperty bool QtQuick2::Canvas::renderInThread
361 Holds the current canvas rendering mode.
363 When this property is true, all canvas painting commands
364 are rendered in a background rendering thread, otherwise
365 the rendering happens in the main GUI thread.
367 The default renderInThread value is false.
369 void QSGCanvasItem::setRenderInThread(bool renderInThread)
372 if (d->renderInThread != renderInThread) {
373 d->renderInThread = renderInThread;
375 if (d->componentCompleted)
378 if (d->renderInThread)
379 connect(this, SIGNAL(painted()), SLOT(update()));
381 disconnect(this, SIGNAL(painted()), this, SLOT(update()));
382 emit renderInThreadChanged();
388 void QSGCanvasItem::geometryChanged(const QRectF &newGeometry,
389 const QRectF &oldGeometry)
392 QSGItem::geometryChanged(newGeometry, oldGeometry);
394 const qreal w = newGeometry.width();
395 const qreal h = newGeometry.height();
397 if (!d->hasCanvasSize) {
398 d->canvasSize = QSizeF(w, h);
399 emit canvasSizeChanged();
402 if (!d->hasTileSize) {
403 d->tileSize = d->canvasSize.toSize();
404 emit tileSizeChanged();
407 if (!d->hasCanvasWindow) {
408 d->canvasWindow = newGeometry;
409 emit canvasWindowChanged();
416 void QSGCanvasItem::componentComplete()
419 QSGItem::componentComplete();
425 d->baseUrl = qmlEngine(this)->contextForObject(this)->baseUrl();
427 updatePolish(); //force update the canvas sizes to texture for the first time
428 d->componentCompleted = true;
431 void QSGCanvasItem::updatePolish()
434 QSGItem::updatePolish();
436 if (!d->renderInThread && d->dirtyRect.isValid())
437 _doPainting(d->dirtyRect);
439 d->texture->canvasChanged(d->canvasSize.toSize()
441 , d->canvasWindow.toAlignedRect()
442 , d->dirtyRect.toAlignedRect()
444 d->dirtyRect = QRectF();
448 QSGNode *QSGCanvasItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
451 QSGContext2DNode *node = static_cast<QSGContext2DNode *>(oldNode);
453 node = new QSGContext2DNode(this);
455 node->setTexture(d->texture);
460 void QSGCanvasItem::createTexture()
465 || d->texture->threadRendering() != d->renderInThread
466 || d->texture->renderTarget() != d->renderTarget) {
468 d->texture->deleteLater();
472 if (d->renderTarget == QSGCanvasItem::Image) {
473 d->texture = new QSGContext2DImageTexture(d->renderInThread);
474 } else if (d->renderTarget == QSGCanvasItem::FramebufferObject) {
475 d->texture = new QSGContext2DFBOTexture();
478 if (d->renderInThread && !d->texture->supportThreadRendering()) {
479 qWarning("Canvas: render target does not support thread rendering, force to non-thread rendering mode.");
480 d->renderInThread = false;
481 emit renderInThreadChanged();
484 if (d->renderInThread)
485 connect(d->texture, SIGNAL(textureChanged()), this, SLOT(update()));
487 d->texture->setItem(this);
491 void QSGCanvasItem::createContext()
497 d->context = new QSGContext2D(this);
499 QV8Engine *e = QDeclarativeEnginePrivate::getV8Engine(qmlEngine(this));
500 d->context->setV8Engine(e);
504 \qmlmethod object QtQuick2::Canvas::getContext(string contextId)
506 Currently, the canvas item only support the 2D context. If the \a contextId
507 parameter isn't provided or is "2d", then the QtQuick2::Context2D object is
508 returned, otherwise returns an invalid value.
510 QDeclarativeV8Handle QSGCanvasItem::getContext(const QString &contextId)
514 if (contextId.toLower() != QLatin1String("2d"))
515 return QDeclarativeV8Handle::fromHandle(v8::Undefined());
519 return QDeclarativeV8Handle::fromHandle(d->context->v8value());
523 \qmlmethod void QtQuick2::Canvas::markDirty(rect region)
525 Mark the given \a region as dirty, so that when this region is visible
526 the canvas render will redraw it. During the rendering process, the
527 canvas renderer may emit the canvas' "paint" signal so the actual painting
528 scripts can be putted into the canvas's "onPaint" signal handler function.
530 \sa QtQuick2::Canvas::paint QtQuick2::Canvas::requestPaint
532 void QSGCanvasItem::markDirty(const QRectF& region)
535 d->dirtyRect |= region;
536 if (d->componentCompleted)
543 \qmlmethod bool QtQuick2::Canvas::save(string filename)
545 Save the current canvas content into an image file \a filename.
546 The saved image format is automatically decided by the \a filename's
549 Note: calling this method will force painting the whole canvas, not the
550 current canvas visible window.
552 \sa canvasWindow canvasSize toDataURL
554 bool QSGCanvasItem::save(const QString &filename) const
556 Q_D(const QSGCanvasItem);
557 QUrl url = d->baseUrl.resolved(QUrl::fromLocalFile(filename));
558 return toImage().save(url.toLocalFile());
561 QImage QSGCanvasItem::loadedImage(const QUrl& url)
564 QUrl fullPathUrl = d->baseUrl.resolved(url);
565 if (!d->images.contains(fullPathUrl)) {
568 QDeclarativePixmap* pix = d->images.value(fullPathUrl);
569 if (pix->isLoading() || pix->isError()) {
572 return pix->pixmap().toImage();
576 \qmlmethod void QtQuick2::Canvas::loadImage(url image)
577 Loads the given \c image asynchronously, when the image is
578 ready, an imageLoaded signal will be emitted.
579 The loaded image can be unloaded by the \a QtQuick2::Canvas::unloadImage method.
581 Note: Only loaded images can be painted on the Canvas item.
582 \sa QtQuick2::Canvas::unloadImage QtQuick2::Canvas::imageLoaded QtQuick2::Canvas::isImageLoaded
583 \sa QtQuick2::Context2D::createImageData QtQuick2::Context2D::drawImage
585 void QSGCanvasItem::loadImage(const QUrl& url)
588 QUrl fullPathUrl = d->baseUrl.resolved(url);
589 if (!d->images.contains(fullPathUrl)) {
590 QDeclarativePixmap* pix = new QDeclarativePixmap();
591 d->images.insert(fullPathUrl, pix);
593 pix->load(qmlEngine(this)
595 , QDeclarativePixmap::Cache | QDeclarativePixmap::Asynchronous);
596 pix->connectFinished(this, SIGNAL(imageLoaded()));
600 \qmlmethod void QtQuick2::Canvas::loadImage(url image)
601 Unloads the \c image.
603 If the image is unloaded from the Canvas item, it can't be painted by the canvas context
604 until it's loaded again.
606 \sa QtQuick2::Canvas::loadImage QtQuick2::Canvas::imageLoaded QtQuick2::Canvas::isImageLoaded
607 \sa QtQuick2::Context2D::createImageData QtQuick2::Context2D::drawImage
609 void QSGCanvasItem::unloadImage(const QUrl& url)
612 QUrl removeThis = d->baseUrl.resolved(url);
613 if (d->images.contains(removeThis)) {
614 delete d->images.value(removeThis);
615 d->images.remove(removeThis);
620 \qmlmethod void QtQuick2::Canvas::isImageError(url image)
621 Returns true if the image can't be loaded because of error happens.
623 \sa QtQuick2::Canvas::loadImage
625 bool QSGCanvasItem::isImageError(const QUrl& url) const
627 Q_D(const QSGCanvasItem);
628 QUrl fullPathUrl = d->baseUrl.resolved(url);
629 return d->images.contains(fullPathUrl)
630 && d->images.value(fullPathUrl)->isError();
634 \qmlmethod void QtQuick2::Canvas::isImageLoading(url image)
635 Returns true if the Canvas item still is loading the \c image.
637 \sa QtQuick2::Canvas::loadImage
639 bool QSGCanvasItem::isImageLoading(const QUrl& url) const
641 Q_D(const QSGCanvasItem);
642 QUrl fullPathUrl = d->baseUrl.resolved(url);
643 return d->images.contains(fullPathUrl)
644 && d->images.value(fullPathUrl)->isLoading();
647 \qmlmethod void QtQuick2::Canvas::isImageLoaded(url image)
648 Returns true if the \c image is sucessfully loaded and ready to use.
650 \sa QtQuick2::Canvas::loadImage
652 bool QSGCanvasItem::isImageLoaded(const QUrl& url) const
654 Q_D(const QSGCanvasItem);
655 QUrl fullPathUrl = d->baseUrl.resolved(url);
656 return d->images.contains(fullPathUrl)
657 && d->images.value(fullPathUrl)->isReady();
660 QImage QSGCanvasItem::toImage(const QRectF& region) const
662 Q_D(const QSGCanvasItem);
664 if (region.isEmpty())
665 return d->texture->toImage(canvasWindow());
667 return d->texture->toImage(region);
673 \qmlmethod string QtQuick2::Canvas::toDataURL(string mimeType)
675 Returns a data: URL for the image in the canvas.
677 The default \a mimeType is "image/png".
679 \sa QtQuick2::Canvas::save
681 QString QSGCanvasItem::toDataURL(const QString& mimeType) const
683 QImage image = toImage();
685 if (!image.isNull()) {
688 buffer.open(QIODevice::WriteOnly);
689 QString mime = mimeType.toLower();
691 if (mime == QLatin1Literal("image/png")) {
692 type = QLatin1Literal("PNG");
693 } else if (mime == QLatin1Literal("image/bmp"))
694 type = QLatin1Literal("BMP");
695 else if (mime == QLatin1Literal("image/jpeg"))
696 type = QLatin1Literal("JPEG");
697 else if (mime == QLatin1Literal("image/x-portable-pixmap"))
698 type = QLatin1Literal("PPM");
699 else if (mime == QLatin1Literal("image/tiff"))
700 type = QLatin1Literal("TIFF");
701 else if (mime == QLatin1Literal("image/xpm"))
702 type = QLatin1Literal("XPM");
704 return QLatin1Literal("data:,");
706 image.save(&buffer, type.toAscii());
708 QString dataUrl = QLatin1Literal("data:%1;base64,%2");
709 return dataUrl.arg(mime).arg(QLatin1String(ba.toBase64().constData()));
711 return QLatin1Literal("data:,");
715 \qmlsignal QtQuick2::Canvas::onPaint(QtQuick2::Context2D context, rect region)
717 This handler is called before the given \c region needs to be rendered.
719 This signal can be triggered by QtQuick2::Canvas::markdirty, QtQuick2::Canvas::requestPaint
720 or by changing the current canvas window.
724 \qmlsignal QtQuick2::Canvas::onPainted()
726 This handler is called after all context painting commands are executed and
727 the Canvas is actually rendered.