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()
191 \qmlproperty size QtQuick2::Canvas::canvasSize
192 Holds the logical canvas size that the context paints on.
194 By default, the canvas size is the same size as the current canvas item size.
195 By setting the canvas size, tile size and canvas window, the Canvas
196 item can act as a virtual large canvas with many seperately rendered tile rectangle
197 areas. Only those tiles within the current canvas window would be painted by
198 the Canvas render engine.
199 \sa QtQuick2::Canvas::tileSize QtQuick2::Canvas::canvasWindow
201 QSizeF QSGCanvasItem::canvasSize() const
203 Q_D(const QSGCanvasItem);
204 return d->canvasSize;
207 void QSGCanvasItem::setCanvasSize(const QSizeF & size)
210 if (d->canvasSize != size) {
211 d->hasCanvasSize = true;
212 d->canvasSize = size;
213 emit canvasSizeChanged();
220 \qmlproperty size QtQuick2::Canvas::tileSize
221 Holds the canvas rendering tile size.
223 When the Canvas item in tiled mode by setting the canvas size, tile size and
224 the canvas window. The canvas render can improve the rendering performance
225 by rendering and caching tiles instead of rendering the whole canvas everytime.
227 Additionally, the canvas size could be infinitely large without allocating more
228 memories because only those tiles within the current visible region
229 are actually rendered.
231 By default, the tile size is the same with the canvas size.
232 \sa QtQuick2::Canvas::canvaasSize QtQuick2::Canvas::canvasWindow
234 QSize QSGCanvasItem::tileSize() const
236 Q_D(const QSGCanvasItem);
240 void QSGCanvasItem::setTileSize(const QSize & size)
243 if (d->tileSize != size) {
244 d->hasTileSize = true;
247 emit tileSizeChanged();
254 \qmlproperty rect QtQuick2::Canvas::canvasWindow
255 Holds the current canvas visible window.
257 By default, the canvas window size is the same as the Canvas item
258 size with the topleft point as (0, 0).
260 If the canvas size is different with the Canvas item size, the Canvas
261 item can display different visible areas by changing the canvas window's size
263 \sa QtQuick2::Canvas::canvasSize QtQuick2::Canvas::tileSize
265 QRectF QSGCanvasItem::canvasWindow() const
267 Q_D(const QSGCanvasItem);
268 return d->canvasWindow;
271 void QSGCanvasItem::setCanvasWindow(const QRectF& rect)
274 if (d->canvasWindow != rect) {
275 d->canvasWindow = rect;
277 d->hasCanvasWindow = true;
278 emit canvasWindowChanged();
285 QSGContext2D* QSGCanvasItem::context() const
287 Q_D(const QSGCanvasItem);
291 \qmlproperty bool QtQuick2::Canvas::renderInThread
292 Holds the current canvas rendering mode.
294 By setting the renderInThread to true, complex and long
295 running painting can be rendered in a dedicated background
296 rendering thread to avoid blocking the main GUI.
298 Note: Different renderTarget may or may not support the
299 background rendering thread, if not, the renderInThread
300 property will be ignored.
302 The default value is false.
303 \sa QtQuick2::Canvas::renderTarget
305 bool QSGCanvasItem::renderInThread() const
307 Q_D(const QSGCanvasItem);
308 return d->renderInThread;
311 \qmlproperty bool QtQuick2::Canvas::renderTarget
312 Holds the current canvas render target.
315 \o Canvas.Image - render to an in memory image buffer, the render
316 target supports background rendering.
317 \o Canvas.FramebufferObject - render to an OpenGL frame buffer,
318 this render target will ignore the
319 renderInThread property. The actual
320 rendering happens in the main QML rendering
321 process, which may be in a seperate render thread
322 or in the main GUI thread depends on the platforms.
325 The default render target is \c Canvas.Image.
326 \sa QtQuick2::Canvas::renderInThread
328 QSGCanvasItem::RenderTarget QSGCanvasItem::renderTarget() const
330 Q_D(const QSGCanvasItem);
331 return d->renderTarget;
334 void QSGCanvasItem::setRenderTarget(RenderTarget target)
337 if (d->renderTarget != target) {
338 d->renderTarget = target;
340 if (d->componentCompleted)
342 emit renderTargetChanged();
346 void QSGCanvasItem::_doPainting(const QRectF& region)
349 emit paint(QDeclarativeV8Handle::fromHandle(d->context->v8value())
350 , QSGContext2DTexture::tiledRect(region, d->tileSize));
356 \qmlproperty bool QtQuick2::Canvas::renderInThread
357 Holds the current canvas rendering mode.
359 When this property is true, all canvas painting commands
360 are rendered in a background rendering thread, otherwise
361 the rendering happens in the main GUI thread.
363 The default renderInThread value is false.
365 void QSGCanvasItem::setRenderInThread(bool renderInThread)
368 if (d->renderInThread != renderInThread) {
369 d->renderInThread = renderInThread;
371 if (d->componentCompleted)
374 if (d->renderInThread)
375 connect(this, SIGNAL(painted()), SLOT(update()));
377 disconnect(this, SIGNAL(painted()), this, SLOT(update()));
378 emit renderInThreadChanged();
384 void QSGCanvasItem::geometryChanged(const QRectF &newGeometry,
385 const QRectF &oldGeometry)
388 QSGItem::geometryChanged(newGeometry, oldGeometry);
390 const qreal w = newGeometry.width();
391 const qreal h = newGeometry.height();
393 if (!d->hasCanvasSize) {
394 d->canvasSize = QSizeF(w, h);
395 emit canvasSizeChanged();
398 if (!d->hasTileSize) {
399 d->tileSize = d->canvasSize.toSize();
400 emit tileSizeChanged();
403 if (!d->hasCanvasWindow) {
404 d->canvasWindow = newGeometry;
405 emit canvasWindowChanged();
412 void QSGCanvasItem::componentComplete()
415 QSGItem::componentComplete();
421 d->baseUrl = qmlEngine(this)->contextForObject(this)->baseUrl();
423 updatePolish(); //force update the canvas sizes to texture for the first time
425 d->componentCompleted = true;
428 void QSGCanvasItem::updatePolish()
431 QSGItem::updatePolish();
433 if (!d->renderInThread && d->dirtyRect.isValid())
434 _doPainting(d->dirtyRect);
436 d->texture->canvasChanged(d->canvasSize.toSize()
438 , d->canvasWindow.toAlignedRect()
439 , d->dirtyRect.toAlignedRect()
441 d->dirtyRect = QRectF();
445 QSGNode *QSGCanvasItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
448 QSGContext2DNode *node = static_cast<QSGContext2DNode *>(oldNode);
450 node = new QSGContext2DNode(this);
452 node->setTexture(d->texture);
453 node->setSize(d->canvasWindow.size());
458 void QSGCanvasItem::createTexture()
463 || d->texture->threadRendering() != d->renderInThread
464 || d->texture->renderTarget() != d->renderTarget) {
466 d->texture->deleteLater();
470 if (d->renderTarget == QSGCanvasItem::Image) {
471 d->texture = new QSGContext2DImageTexture(d->renderInThread);
472 } else if (d->renderTarget == QSGCanvasItem::FramebufferObject) {
473 d->texture = new QSGContext2DFBOTexture();
476 if (d->renderInThread && !d->texture->supportThreadRendering()) {
477 qWarning("Canvas: render target does not support thread rendering, force to non-thread rendering mode.");
478 d->renderInThread = false;
479 emit renderInThreadChanged();
482 if (d->renderInThread)
483 connect(d->texture, SIGNAL(textureChanged()), this, SLOT(update()));
485 d->texture->setItem(this);
489 void QSGCanvasItem::createContext()
495 d->context = new QSGContext2D(this);
497 QV8Engine *e = QDeclarativeEnginePrivate::getV8Engine(qmlEngine(this));
498 d->context->setV8Engine(e);
502 \qmlmethod object QtQuick2::Canvas::getContext(string contextId)
504 Currently, the canvas item only support the 2D context. If the \a contextId
505 parameter isn't provided or is "2d", then the QtQuick2::Context2D object is
506 returned, otherwise returns an invalid value.
508 QDeclarativeV8Handle QSGCanvasItem::getContext(const QString &contextId)
512 if (contextId.toLower() != QLatin1String("2d"))
513 return QDeclarativeV8Handle::fromHandle(v8::Undefined());
517 return QDeclarativeV8Handle::fromHandle(d->context->v8value());
521 \qmlmethod void QtQuick2::Canvas::markDirty(rect region)
523 Mark the given \a region as dirty, so that when this region is visible
524 the canvas render will redraw it. During the rendering process, the
525 canvas renderer may emit the canvas' "paint" signal so the actual painting
526 scripts can be putted into the canvas's "onPaint" signal handler function.
528 \sa QtQuick2::Canvas::paint QtQuick2::Canvas::requestPaint
530 void QSGCanvasItem::markDirty(const QRectF& region)
533 d->dirtyRect |= region;
534 if (d->componentCompleted)
541 \qmlmethod bool QtQuick2::Canvas::save(string filename)
543 Save the current canvas content into an image file \a filename.
544 The saved image format is automatically decided by the \a filename's
547 Note: calling this method will force painting the whole canvas, not the
548 current canvas visible window.
550 \sa canvasWindow canvasSize toDataURL
552 bool QSGCanvasItem::save(const QString &filename) const
554 Q_D(const QSGCanvasItem);
555 QUrl url = d->baseUrl.resolved(QUrl::fromLocalFile(filename));
556 return toImage().save(url.toLocalFile());
559 QImage QSGCanvasItem::loadedImage(const QUrl& url)
562 QUrl fullPathUrl = d->baseUrl.resolved(url);
563 if (!d->images.contains(fullPathUrl)) {
566 QDeclarativePixmap* pix = d->images.value(fullPathUrl);
567 if (pix->isLoading() || pix->isError()) {
570 return pix->pixmap().toImage();
574 \qmlmethod void QtQuick2::Canvas::loadImage(url image)
575 Loads the given \c image asynchronously, when the image is
576 ready, an imageLoaded signal will be emitted.
577 The loaded image can be unloaded by the \a QtQuick2::Canvas::unloadImage method.
579 Note: Only loaded images can be painted on the Canvas item.
580 \sa QtQuick2::Canvas::unloadImage QtQuick2::Canvas::imageLoaded QtQuick2::Canvas::isImageLoaded
581 \sa QtQuick2::Context2D::createImageData QtQuick2::Context2D::drawImage
583 void QSGCanvasItem::loadImage(const QUrl& url)
586 QUrl fullPathUrl = d->baseUrl.resolved(url);
587 if (!d->images.contains(fullPathUrl)) {
588 QDeclarativePixmap* pix = new QDeclarativePixmap();
589 d->images.insert(fullPathUrl, pix);
591 pix->load(qmlEngine(this)
593 , QDeclarativePixmap::Cache | QDeclarativePixmap::Asynchronous);
594 pix->connectFinished(this, SIGNAL(imageLoaded()));
598 \qmlmethod void QtQuick2::Canvas::loadImage(url image)
599 Unloads the \c image.
601 If the image is unloaded from the Canvas item, it can't be painted by the canvas context
602 until it's loaded again.
604 \sa QtQuick2::Canvas::loadImage QtQuick2::Canvas::imageLoaded QtQuick2::Canvas::isImageLoaded
605 \sa QtQuick2::Context2D::createImageData QtQuick2::Context2D::drawImage
607 void QSGCanvasItem::unloadImage(const QUrl& url)
610 QUrl removeThis = d->baseUrl.resolved(url);
611 if (d->images.contains(removeThis)) {
612 delete d->images.value(removeThis);
613 d->images.remove(removeThis);
618 \qmlmethod void QtQuick2::Canvas::isImageError(url image)
619 Returns true if the image can't be loaded because of error happens.
621 \sa QtQuick2::Canvas::loadImage
623 bool QSGCanvasItem::isImageError(const QUrl& url) const
625 Q_D(const QSGCanvasItem);
626 QUrl fullPathUrl = d->baseUrl.resolved(url);
627 return d->images.contains(fullPathUrl)
628 && d->images.value(fullPathUrl)->isError();
632 \qmlmethod void QtQuick2::Canvas::isImageLoading(url image)
633 Returns true if the Canvas item still is loading the \c image.
635 \sa QtQuick2::Canvas::loadImage
637 bool QSGCanvasItem::isImageLoading(const QUrl& url) const
639 Q_D(const QSGCanvasItem);
640 QUrl fullPathUrl = d->baseUrl.resolved(url);
641 return d->images.contains(fullPathUrl)
642 && d->images.value(fullPathUrl)->isLoading();
645 \qmlmethod void QtQuick2::Canvas::isImageLoaded(url image)
646 Returns true if the \c image is sucessfully loaded and ready to use.
648 \sa QtQuick2::Canvas::loadImage
650 bool QSGCanvasItem::isImageLoaded(const QUrl& url) const
652 Q_D(const QSGCanvasItem);
653 QUrl fullPathUrl = d->baseUrl.resolved(url);
654 return d->images.contains(fullPathUrl)
655 && d->images.value(fullPathUrl)->isReady();
658 QImage QSGCanvasItem::toImage(const QRectF& region) const
660 Q_D(const QSGCanvasItem);
662 if (region.isEmpty())
663 return d->texture->toImage(canvasWindow());
665 return d->texture->toImage(region);
671 \qmlmethod string QtQuick2::Canvas::toDataURL(string mimeType)
673 Returns a data: URL for the image in the canvas.
675 The default \a mimeType is "image/png".
677 \sa QtQuick2::Canvas::save
679 QString QSGCanvasItem::toDataURL(const QString& mimeType) const
681 QImage image = toImage();
683 if (!image.isNull()) {
686 buffer.open(QIODevice::WriteOnly);
687 QString mime = mimeType.toLower();
689 if (mime == QLatin1Literal("image/png")) {
690 type = QLatin1Literal("PNG");
691 } else if (mime == QLatin1Literal("image/bmp"))
692 type = QLatin1Literal("BMP");
693 else if (mime == QLatin1Literal("image/jpeg"))
694 type = QLatin1Literal("JPEG");
695 else if (mime == QLatin1Literal("image/x-portable-pixmap"))
696 type = QLatin1Literal("PPM");
697 else if (mime == QLatin1Literal("image/tiff"))
698 type = QLatin1Literal("TIFF");
699 else if (mime == QLatin1Literal("image/xpm"))
700 type = QLatin1Literal("XPM");
702 return QLatin1Literal("data:,");
704 image.save(&buffer, type.toAscii());
706 QString dataUrl = QLatin1Literal("data:%1;base64,%2");
707 return dataUrl.arg(mime).arg(QLatin1String(ba.toBase64().constData()));
709 return QLatin1Literal("data:,");
713 \qmlsignal QtQuick2::Canvas::onPaint(QtQuick2::Context2D context, rect region)
715 This handler is called before the given \c region needs to be rendered.
717 This signal can be triggered by QtQuick2::Canvas::markdirty, QtQuick2::Canvas::requestPaint
718 or by changing the current canvas window.
722 \qmlsignal QtQuick2::Canvas::onPainted()
724 This handler is called after all context painting commands are executed and
725 the Canvas is actually rendered.