1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQuick module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include <private/qsgadaptationlayer_p.h>
41 #include "qquickcanvasitem_p.h"
42 #include <private/qquickitem_p.h>
43 #include <private/qquickcanvascontext_p.h>
44 #include <private/qquickcontext2d_p.h>
45 #include <private/qquickcontext2dtexture_p.h>
46 #include <private/qsgadaptationlayer_p.h>
47 #include <qsgtextureprovider.h>
48 #include <QtQuick/private/qquickpixmapcache_p.h>
49 #include <QtGui/QGuiApplication>
50 #include <qsgtextureprovider.h>
51 
52 #include <qqmlinfo.h>
53 #include <private/qqmlengine_p.h>
54 #include <QtCore/QBuffer>
55 #include <QtCore/qdatetime.h>
56 
57 #include <private/qv4value_p.h>
58 #include <private/qv4functionobject_p.h>
59 #include <private/qv4scopedvalue_p.h>
60 #include <private/qv4jscall_p.h>
61 #include <private/qv4qobjectwrapper_p.h>
62 
63 QT_BEGIN_NAMESPACE
64 
65 class QQuickCanvasTextureProvider : public QSGTextureProvider
66 {
67 public:
68     QSGTexture *tex;
texture() const69     QSGTexture *texture() const override { return tex; }
fireTextureChanged()70     void fireTextureChanged() { emit textureChanged(); }
71 };
72 
QQuickCanvasPixmap(const QImage & image)73 QQuickCanvasPixmap::QQuickCanvasPixmap(const QImage& image)
74     : m_pixmap(nullptr)
75     , m_image(image)
76 {
77 
78 }
79 
QQuickCanvasPixmap(QQuickPixmap * pixmap)80 QQuickCanvasPixmap::QQuickCanvasPixmap(QQuickPixmap *pixmap)
81     : m_pixmap(pixmap)
82 {
83 
84 }
85 
~QQuickCanvasPixmap()86 QQuickCanvasPixmap::~QQuickCanvasPixmap()
87 {
88     delete m_pixmap;
89 }
90 
width() const91 qreal QQuickCanvasPixmap::width() const
92 {
93     if (m_pixmap)
94         return m_pixmap->width();
95 
96     return m_image.width();
97 }
98 
height() const99 qreal QQuickCanvasPixmap::height() const
100 {
101     if (m_pixmap)
102         return m_pixmap->height();
103 
104     return m_image.height();
105 }
106 
isValid() const107 bool QQuickCanvasPixmap::isValid() const
108 {
109     if (m_pixmap)
110         return m_pixmap->isReady();
111     return !m_image.isNull();
112 }
113 
image()114 QImage QQuickCanvasPixmap::image()
115 {
116     if (m_image.isNull() && m_pixmap)
117         m_image = m_pixmap->image();
118 
119     return m_image;
120 }
121 
122 QHash<QQmlEngine *,QQuickContext2DRenderThread*> QQuickContext2DRenderThread::renderThreads;
123 QMutex QQuickContext2DRenderThread::renderThreadsMutex;
124 
QQuickContext2DRenderThread(QQmlEngine * eng)125 QQuickContext2DRenderThread::QQuickContext2DRenderThread(QQmlEngine *eng)
126     : QThread(eng), m_engine(eng), m_eventLoopQuitHack(nullptr)
127 {
128     Q_ASSERT(eng);
129     m_eventLoopQuitHack = new QObject;
130     m_eventLoopQuitHack->moveToThread(this);
131     connect(m_eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), Qt::DirectConnection);
132     start(QThread::IdlePriority);
133 }
134 
~QQuickContext2DRenderThread()135 QQuickContext2DRenderThread::~QQuickContext2DRenderThread()
136 {
137     renderThreadsMutex.lock();
138     renderThreads.remove(m_engine);
139     renderThreadsMutex.unlock();
140 
141     m_eventLoopQuitHack->deleteLater();
142     wait();
143 }
144 
instance(QQmlEngine * engine)145 QQuickContext2DRenderThread *QQuickContext2DRenderThread::instance(QQmlEngine *engine)
146 {
147     QQuickContext2DRenderThread *thread = nullptr;
148     renderThreadsMutex.lock();
149     if (renderThreads.contains(engine))
150         thread = renderThreads.value(engine);
151     else {
152         thread = new QQuickContext2DRenderThread(engine);
153         renderThreads.insert(engine, thread);
154     }
155     renderThreadsMutex.unlock();
156     return thread;
157 }
158 
159 class QQuickCanvasItemPrivate : public QQuickItemPrivate
160 {
161 public:
162     QQuickCanvasItemPrivate();
163     ~QQuickCanvasItemPrivate();
164     QQuickCanvasContext *context;
165     QSizeF canvasSize;
166     QSize tileSize;
167     QRectF canvasWindow;
168     QRectF dirtyRect;
169     uint hasCanvasSize :1;
170     uint hasTileSize :1;
171     uint hasCanvasWindow :1;
172     uint available :1;
173     QQuickCanvasItem::RenderTarget renderTarget;
174     QQuickCanvasItem::RenderStrategy renderStrategy;
175     QString contextType;
176     QHash<QUrl, QQmlRefPointer<QQuickCanvasPixmap> > pixmaps;
177     QUrl baseUrl;
178     QMap<int, QV4::PersistentValue> animationCallbacks;
179     mutable QQuickCanvasTextureProvider *textureProvider;
180     QSGInternalImageNode *node;
181     QSGTexture *nodeTexture;
182 };
183 
QQuickCanvasItemPrivate()184 QQuickCanvasItemPrivate::QQuickCanvasItemPrivate()
185     : QQuickItemPrivate()
186     , context(nullptr)
187     , canvasSize(1, 1)
188     , tileSize(1, 1)
189     , hasCanvasSize(false)
190     , hasTileSize(false)
191     , hasCanvasWindow(false)
192     , available(false)
193     , renderTarget(QQuickCanvasItem::Image)
194     , renderStrategy(QQuickCanvasItem::Immediate)
195     , textureProvider(nullptr)
196     , node(nullptr)
197     , nodeTexture(nullptr)
198 {
199     implicitAntialiasing = true;
200 }
201 
~QQuickCanvasItemPrivate()202 QQuickCanvasItemPrivate::~QQuickCanvasItemPrivate()
203 {
204     pixmaps.clear();
205 }
206 
207 
208 /*!
209     \qmltype Canvas
210     \instantiates QQuickCanvasItem
211     \inqmlmodule QtQuick
212     \since 5.0
213     \inherits Item
214     \ingroup qtquick-canvas
215     \ingroup qtquick-visual
216     \brief Provides a 2D canvas item enabling drawing via JavaScript.
217 
218     The Canvas item allows drawing of straight and curved lines, simple and
219     complex shapes, graphs, and referenced graphic images.  It can also add
220     text, colors, shadows, gradients, and patterns, and do low level pixel
221     operations. The Canvas output may be saved as an image file or serialized
222     to a URL.
223 
224     Rendering to the Canvas is done using a Context2D object, usually as a
225     result of the \l paint signal.
226 
227     To define a drawing area in the Canvas item set the \c width and \c height
228     properties.  For example, the following code creates a Canvas item which
229     has a drawing area with a height of 100 pixels and width of 200 pixels:
230     \qml
231     import QtQuick 2.0
232     Canvas {
233         id: mycanvas
234         width: 100
235         height: 200
236         onPaint: {
237             var ctx = getContext("2d");
238             ctx.fillStyle = Qt.rgba(1, 0, 0, 1);
239             ctx.fillRect(0, 0, width, height);
240         }
241     }
242     \endqml
243 
244     Currently the Canvas item only supports the two-dimensional rendering context.
245 
246     \section1 Threaded Rendering and Render Target
247 
248     The Canvas item supports two render targets: \c Canvas.Image and
249     \c Canvas.FramebufferObject.
250 
251     The \c Canvas.Image render target is a \a QImage object. This render target
252     supports background thread rendering, allowing complex or long running
253     painting to be executed without blocking the UI. This is the only render
254     target that is supported by all Qt Quick backends.
255 
256     The Canvas.FramebufferObject render target utilizes OpenGL hardware
257     acceleration rather than rendering into system memory, which in many cases
258     results in faster rendering. Canvas.FramebufferObject relies on the OpenGL
259     extensions \c GL_EXT_framebuffer_multisample and \c GL_EXT_framebuffer_blit
260     for antialiasing. It will also use more graphics memory when rendering
261     strategy is anything other than Canvas.Cooperative. Framebuffer objects may
262     not be available with Qt Quick backends other than OpenGL.
263 
264     The default render target is Canvas.Image and the default renderStrategy is
265     Canvas.Immediate.
266 
267     \section1 Pixel Operations
268     All HTML5 2D context pixel operations are supported. In order to ensure
269     improved pixel reading/writing performance the \a Canvas.Image render
270     target should be chosen. The \a Canvas.FramebufferObject render target
271     requires the pixel data to be exchanged between the system memory and the
272     graphic card, which is significantly more expensive.  Rendering may also be
273     synchronized with the V-sync signal (to avoid
274     \l{http://en.wikipedia.org/wiki/Screen_tearing}{screen tearing}) which will further
275     impact pixel operations with \c Canvas.FrambufferObject render target.
276 
277     \section1 Tips for Porting Existing HTML5 Canvas Applications
278 
279     Although the Canvas item provides an HTML5-like API, HTML5 canvas
280     applications need to be modified to run in the Canvas item:
281     \list
282     \li Replace all DOM API calls with QML property bindings or Canvas item methods.
283     \li Replace all HTML event handlers with the MouseArea item.
284     \li Change setInterval/setTimeout function calls with the \l Timer item or
285        the use of requestAnimationFrame().
286     \li Place painting code into the \c onPaint handler and trigger
287        painting by calling the markDirty() or requestPaint() methods.
288     \li To draw images, load them by calling the Canvas's loadImage() method and then request to paint
289        them in the \c onImageLoaded handler.
290     \endlist
291 
292     Starting Qt 5.4, the Canvas is a
293     \l{QSGTextureProvider}{texture provider}
294     and can be used directly in \l {ShaderEffect}{ShaderEffects} and other
295     classes that consume texture providers.
296 
297     \note In general large canvases, frequent updates, and animation should be
298     avoided with the Canvas.Image render target. This is because with
299     accelerated graphics APIs each update will lead to a texture upload. Also,
300     if possible, prefer QQuickPaintedItem and implement drawing in C++ via
301     QPainter instead of the more expensive and likely less performing
302     JavaScript and Context2D approach.
303 
304     \sa Context2D QQuickPaintedItem
305 */
306 
QQuickCanvasItem(QQuickItem * parent)307 QQuickCanvasItem::QQuickCanvasItem(QQuickItem *parent)
308     : QQuickItem(*(new QQuickCanvasItemPrivate), parent)
309 {
310     setFlag(ItemHasContents);
311 }
312 
~QQuickCanvasItem()313 QQuickCanvasItem::~QQuickCanvasItem()
314 {
315     Q_D(QQuickCanvasItem);
316     delete d->context;
317     if (d->textureProvider)
318         QQuickWindowQObjectCleanupJob::schedule(window(), d->textureProvider);
319 }
320 
321 /*!
322     \qmlproperty bool QtQuick::Canvas::available
323 
324     Indicates when Canvas is able to provide a drawing context to operate on.
325 */
326 
isAvailable() const327 bool QQuickCanvasItem::isAvailable() const
328 {
329     return d_func()->available;
330 }
331 
332 /*!
333     \qmlproperty string QtQuick::Canvas::contextType
334     The type of drawing context to use.
335 
336     This property is set to the name of the active context type.
337 
338     If set explicitly the canvas will attempt to create a context of the
339     named type after becoming available.
340 
341     The type name is the same as used in the getContext() call, for the 2d
342     canvas the value will be "2d".
343 
344     \sa getContext(), available
345 */
346 
contextType() const347 QString QQuickCanvasItem::contextType() const
348 {
349     return d_func()->contextType;
350 }
351 
setContextType(const QString & contextType)352 void QQuickCanvasItem::setContextType(const QString &contextType)
353 {
354     Q_D(QQuickCanvasItem);
355 
356     if (contextType.compare(d->contextType, Qt::CaseInsensitive) == 0)
357         return;
358 
359     if (d->context) {
360         qmlWarning(this) << "Canvas already initialized with a different context type";
361         return;
362     }
363 
364     d->contextType = contextType;
365 
366     if (d->available)
367         createContext(contextType);
368 
369     emit contextTypeChanged();
370 }
371 
372 /*!
373     \qmlproperty object QtQuick::Canvas::context
374     Holds the active drawing context.
375 
376     If the canvas is ready and there has been a successful call to getContext()
377     or the contextType property has been set with a supported context type,
378     this property will contain the current drawing context, otherwise null.
379 */
380 
context() const381 QJSValue QQuickCanvasItem::context() const
382 {
383     Q_D(const QQuickCanvasItem);
384     return d->context ? QJSValue(d->context->v4Engine(), d->context->v4value()) : QJSValue();
385 }
386 
387 /*!
388     \qmlproperty size QtQuick::Canvas::canvasSize
389     Holds the logical canvas size that the context paints on.
390 
391     By default, the canvas size is the same size as the current canvas item
392     size.
393 
394     By setting the canvasSize, tileSize and canvasWindow, the Canvas item can
395     act as a large virtual canvas with many separately rendered tile rectangles.
396     Only those tiles within the current canvas window are painted by the Canvas
397     render engine.
398 
399     \sa tileSize, canvasWindow
400 */
canvasSize() const401 QSizeF QQuickCanvasItem::canvasSize() const
402 {
403     Q_D(const QQuickCanvasItem);
404     return d->canvasSize;
405 }
406 
setCanvasSize(const QSizeF & size)407 void QQuickCanvasItem::setCanvasSize(const QSizeF & size)
408 {
409     Q_D(QQuickCanvasItem);
410     if (d->canvasSize != size) {
411         d->hasCanvasSize = true;
412         d->canvasSize = size;
413         emit canvasSizeChanged();
414 
415         if (d->context)
416             polish();
417     }
418 }
419 
420 /*!
421     \qmlproperty size QtQuick::Canvas::tileSize
422     Holds the canvas rendering tile size.
423 
424     The Canvas item enters tiled mode by setting canvasSize, tileSize and the
425     canvasWindow. This can improve rendering performance by rendering and
426     caching tiles instead of rendering the whole canvas every time.
427 
428     Memory will be consumed only by those tiles within the current visible
429     region.
430 
431     By default the tileSize is the same as the canvasSize.
432 
433     \obsolete This feature is incomplete. For details, see QTBUG-33129.
434 
435     \sa canvasSize, canvasWindow
436 */
tileSize() const437 QSize QQuickCanvasItem::tileSize() const
438 {
439     Q_D(const QQuickCanvasItem);
440     return d->tileSize;
441 }
442 
setTileSize(const QSize & size)443 void QQuickCanvasItem::setTileSize(const QSize & size)
444 {
445     Q_D(QQuickCanvasItem);
446     if (d->tileSize != size) {
447         d->hasTileSize = true;
448         d->tileSize = size;
449 
450         emit tileSizeChanged();
451 
452         if (d->context)
453             polish();
454     }
455 }
456 
457 /*!
458     \qmlproperty rect QtQuick::Canvas::canvasWindow
459      Holds the current canvas visible window.
460 
461      By default the canvasWindow size is the same as the Canvas item size with
462      the top-left point as (0, 0).
463 
464      If the canvasSize is different to the Canvas item size, the Canvas item
465      can display different visible areas by changing the canvas windowSize
466      and/or position.
467 
468     \obsolete This feature is incomplete. For details, see QTBUG-33129
469 
470     \sa canvasSize, tileSize
471 */
canvasWindow() const472 QRectF QQuickCanvasItem::canvasWindow() const
473 {
474     Q_D(const QQuickCanvasItem);
475     return d->canvasWindow;
476 }
477 
setCanvasWindow(const QRectF & rect)478 void QQuickCanvasItem::setCanvasWindow(const QRectF& rect)
479 {
480     Q_D(QQuickCanvasItem);
481     if (d->canvasWindow != rect) {
482         d->canvasWindow = rect;
483 
484         d->hasCanvasWindow = true;
485         emit canvasWindowChanged();
486 
487         if (d->context)
488             polish();
489     }
490 }
491 
492 /*!
493     \qmlproperty enumeration QtQuick::Canvas::renderTarget
494     Holds the current canvas render target.
495 
496     \list
497     \li Canvas.Image  - render to an in memory image buffer.
498     \li Canvas.FramebufferObject - render to an OpenGL frame buffer
499     \endlist
500 
501     This hint is supplied along with renderStrategy to the graphics context to
502     determine the method of rendering. A renderStrategy, renderTarget or a
503     combination may not be supported by a graphics context, in which case the
504     context will choose appropriate options and Canvas will signal the change
505     to the properties.
506 
507     The default render target is \c Canvas.Image.
508 */
renderTarget() const509 QQuickCanvasItem::RenderTarget QQuickCanvasItem::renderTarget() const
510 {
511     Q_D(const QQuickCanvasItem);
512     return d->renderTarget;
513 }
514 
setRenderTarget(QQuickCanvasItem::RenderTarget target)515 void QQuickCanvasItem::setRenderTarget(QQuickCanvasItem::RenderTarget target)
516 {
517     Q_D(QQuickCanvasItem);
518     if (d->renderTarget != target) {
519         if (d->context) {
520             qmlWarning(this) << "Canvas:renderTarget not changeble once context is active.";
521             return;
522         }
523 
524         d->renderTarget = target;
525         emit renderTargetChanged();
526     }
527 }
528 
529 /*!
530     \qmlproperty enumeration QtQuick::Canvas::renderStrategy
531     Holds the current canvas rendering strategy.
532 
533     \list
534     \li Canvas.Immediate - context will perform graphics commands immediately in the main UI thread.
535     \li Canvas.Threaded - context will defer graphics commands to a private rendering thread.
536     \li Canvas.Cooperative - context will defer graphics commands to the applications global render thread.
537     \endlist
538 
539     This hint is supplied along with renderTarget to the graphics context to
540     determine the method of rendering. A renderStrategy, renderTarget or a
541     combination may not be supported by a graphics context, in which case the
542     context will choose appropriate options and Canvas will signal the change
543     to the properties.
544 
545     Configuration or runtime tests may cause the QML Scene Graph to render in
546     the GUI thread.  Selecting \c Canvas.Cooperative, does not guarantee
547     rendering will occur on a thread separate from the GUI thread.
548 
549     The default value is \c Canvas.Immediate.
550 
551     \sa renderTarget
552 */
553 
renderStrategy() const554 QQuickCanvasItem::RenderStrategy QQuickCanvasItem::renderStrategy() const
555 {
556     return d_func()->renderStrategy;
557 }
558 
setRenderStrategy(QQuickCanvasItem::RenderStrategy strategy)559 void QQuickCanvasItem::setRenderStrategy(QQuickCanvasItem::RenderStrategy strategy)
560 {
561     Q_D(QQuickCanvasItem);
562     if (d->renderStrategy != strategy) {
563         if (d->context) {
564             qmlWarning(this) << "Canvas:renderStrategy not changeable once context is active.";
565             return;
566         }
567         d->renderStrategy = strategy;
568         emit renderStrategyChanged();
569     }
570 }
571 
rawContext() const572 QQuickCanvasContext* QQuickCanvasItem::rawContext() const
573 {
574     return d_func()->context;
575 }
576 
isPaintConnected()577 bool QQuickCanvasItem::isPaintConnected()
578 {
579     IS_SIGNAL_CONNECTED(this, QQuickCanvasItem, paint, (const QRect &));
580 }
581 
sceneGraphInitialized()582 void QQuickCanvasItem::sceneGraphInitialized()
583 {
584     Q_D(QQuickCanvasItem);
585 
586     d->available = true;
587     connect(this, SIGNAL(visibleChanged()), SLOT(checkAnimationCallbacks()));
588     QMetaObject::invokeMethod(this, "availableChanged", Qt::QueuedConnection);
589 
590     if (!d->contextType.isNull())
591         QMetaObject::invokeMethod(this, "delayedCreate", Qt::QueuedConnection);
592     else if (isPaintConnected())
593         QMetaObject::invokeMethod(this, "requestPaint", Qt::QueuedConnection);
594 }
595 
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)596 void QQuickCanvasItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
597 {
598     Q_D(QQuickCanvasItem);
599 
600     QQuickItem::geometryChanged(newGeometry, oldGeometry);
601 
602     // Due to indirect recursion, newGeometry may be outdated
603     // after this call, so we use width and height instead.
604     QSizeF newSize = QSizeF(width(), height());
605     if (!d->hasCanvasSize && d->canvasSize != newSize) {
606         d->canvasSize = newSize;
607         emit canvasSizeChanged();
608     }
609 
610     if (!d->hasTileSize && d->tileSize != newSize) {
611         d->tileSize = newSize.toSize();
612         emit tileSizeChanged();
613     }
614 
615     const QRectF rect = QRectF(QPointF(0, 0), newSize);
616 
617     if (!d->hasCanvasWindow && d->canvasWindow != rect) {
618         d->canvasWindow = rect;
619         emit canvasWindowChanged();
620     }
621 
622     if (d->available && newSize != oldGeometry.size()) {
623         if (isVisible() || (d->extra.isAllocated() && d->extra->effectRefCount > 0))
624             requestPaint();
625     }
626 }
627 
releaseResources()628 void QQuickCanvasItem::releaseResources()
629 {
630     Q_D(QQuickCanvasItem);
631 
632     if (d->context) {
633         delete d->context;
634         d->context = nullptr;
635     }
636     d->node = nullptr; // managed by the scene graph, just reset the pointer
637     if (d->textureProvider) {
638         QQuickWindowQObjectCleanupJob::schedule(window(), d->textureProvider);
639         d->textureProvider = nullptr;
640     }
641     if (d->nodeTexture) {
642         QQuickWindowQObjectCleanupJob::schedule(window(), d->nodeTexture);
643         d->nodeTexture = nullptr;
644     }
645 }
646 
event(QEvent * event)647 bool QQuickCanvasItem::event(QEvent *event)
648 {
649     switch (event->type()) {
650     case QEvent::PolishRequest:
651         polish();
652         return true;
653     default:
654         return QQuickItem::event(event);
655     }
656 }
657 
invalidateSceneGraph()658 void QQuickCanvasItem::invalidateSceneGraph()
659 {
660     Q_D(QQuickCanvasItem);
661     if (d->context)
662         d->context->deleteLater();
663     d->context = nullptr;
664     d->node = nullptr; // managed by the scene graph, just reset the pointer
665     delete d->textureProvider;
666     d->textureProvider = nullptr;
667     delete d->nodeTexture;
668     d->nodeTexture = nullptr;
669 }
670 
schedulePolish()671 void QQuickCanvasItem::schedulePolish()
672 {
673     auto polishRequestEvent = new QEvent(QEvent::PolishRequest);
674     QCoreApplication::postEvent(this, polishRequestEvent);
675 }
676 
componentComplete()677 void QQuickCanvasItem::componentComplete()
678 {
679     QQuickItem::componentComplete();
680 
681     Q_D(QQuickCanvasItem);
682     d->baseUrl = qmlEngine(this)->contextForObject(this)->baseUrl();
683 }
684 
itemChange(QQuickItem::ItemChange change,const QQuickItem::ItemChangeData & value)685 void QQuickCanvasItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value)
686 {
687     QQuickItem::itemChange(change, value);
688     if (change != QQuickItem::ItemSceneChange)
689         return;
690 
691     Q_D(QQuickCanvasItem);
692     if (d->available) {
693         if (d->dirtyAttributes & QQuickItemPrivate::ContentUpdateMask)
694             requestPaint();
695         return;
696     }
697 
698     if (value.window== nullptr)
699         return;
700 
701     d->window = value.window;
702     QSGRenderContext *context = QQuickWindowPrivate::get(d->window)->context;
703 
704     // Rendering to FramebufferObject needs a valid OpenGL context.
705     if (context != nullptr && (d->renderTarget != FramebufferObject || context->isValid())) {
706         // Defer the call. In some (arguably incorrect) cases we get here due
707         // to ItemSceneChange with the user-supplied property values not yet
708         // set. Work this around by a deferred invoke. (QTBUG-49692)
709         QMetaObject::invokeMethod(this, "sceneGraphInitialized", Qt::QueuedConnection);
710     } else {
711         connect(d->window, SIGNAL(sceneGraphInitialized()), SLOT(sceneGraphInitialized()));
712     }
713 }
714 
updatePolish()715 void QQuickCanvasItem::updatePolish()
716 {
717     QQuickItem::updatePolish();
718 
719     Q_D(QQuickCanvasItem);
720     if (d->context && d->renderStrategy != QQuickCanvasItem::Cooperative)
721         d->context->prepare(d->canvasSize.toSize(), d->tileSize, d->canvasWindow.toRect(), d->dirtyRect.toRect(), d->smooth, antialiasing());
722 
723     if (d->animationCallbacks.size() > 0 && isVisible()) {
724         QMap<int, QV4::PersistentValue> animationCallbacks = d->animationCallbacks;
725         d->animationCallbacks.clear();
726 
727         QV4::ExecutionEngine *v4 = qmlEngine(this)->handle();
728         QV4::Scope scope(v4);
729         QV4::ScopedFunctionObject function(scope);
730         QV4::JSCallData jsCall(scope, 1);
731         *jsCall->thisObject = QV4::QObjectWrapper::wrap(v4, this);
732 
733         for (auto it = animationCallbacks.cbegin(), end = animationCallbacks.cend(); it != end; ++it) {
734             function = it.value().value();
735             jsCall->args[0] = QV4::Value::fromUInt32(QDateTime::currentMSecsSinceEpoch());
736             function->call(jsCall);
737         }
738     }
739     else {
740         if (d->dirtyRect.isValid()) {
741             if (d->hasTileSize && d->hasCanvasWindow)
742                 emit paint(tiledRect(d->canvasWindow.intersected(d->dirtyRect.toAlignedRect()), d->tileSize));
743             else
744                 emit paint(d->dirtyRect.toRect());
745             d->dirtyRect = QRectF();
746         }
747     }
748 
749     if (d->context) {
750         if (d->renderStrategy == QQuickCanvasItem::Cooperative)
751             update();
752         else
753             d->context->flush();
754     }
755 }
756 
updatePaintNode(QSGNode * oldNode,UpdatePaintNodeData *)757 QSGNode *QQuickCanvasItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
758 {
759     Q_D(QQuickCanvasItem);
760 
761     if (!d->context || d->canvasWindow.size().isEmpty()) {
762         if (d->textureProvider) {
763             d->textureProvider->tex = nullptr;
764             d->textureProvider->fireTextureChanged();
765         }
766         delete oldNode;
767         return nullptr;
768     }
769 
770     QSGInternalImageNode *node = static_cast<QSGInternalImageNode *>(oldNode);
771     if (!node) {
772         QSGRenderContext *rc = QQuickWindowPrivate::get(window())->context;
773         node = rc->sceneGraphContext()->createInternalImageNode(rc);
774         d->node = node;
775     }
776 
777 
778     if (d->smooth)
779         node->setFiltering(QSGTexture::Linear);
780     else
781         node->setFiltering(QSGTexture::Nearest);
782 
783     if (d->renderStrategy == QQuickCanvasItem::Cooperative) {
784         d->context->prepare(d->canvasSize.toSize(), d->tileSize, d->canvasWindow.toRect(), d->dirtyRect.toRect(), d->smooth, antialiasing());
785         d->context->flush();
786     }
787 
788     QQuickContext2D *ctx = qobject_cast<QQuickContext2D *>(d->context);
789     QQuickContext2DTexture *factory = ctx->texture();
790     QSGTexture *texture = factory->textureForNextFrame(d->nodeTexture, window());
791     if (!texture) {
792         delete node;
793         d->node = nullptr;
794         d->nodeTexture = nullptr;
795         if (d->textureProvider) {
796             d->textureProvider->tex = nullptr;
797             d->textureProvider->fireTextureChanged();
798         }
799         return nullptr;
800     }
801 
802     d->nodeTexture = texture;
803     node->setTexture(texture);
804     node->setTargetRect(QRectF(QPoint(0, 0), d->canvasWindow.size()));
805     node->setInnerTargetRect(QRectF(QPoint(0, 0), d->canvasWindow.size()));
806     node->update();
807 
808     if (d->textureProvider) {
809         d->textureProvider->tex = d->nodeTexture;
810         d->textureProvider->fireTextureChanged();
811     }
812     return node;
813 }
814 
isTextureProvider() const815 bool QQuickCanvasItem::isTextureProvider() const
816 {
817     return true;
818 }
819 
textureProvider() const820 QSGTextureProvider *QQuickCanvasItem::textureProvider() const
821 {
822     // When Item::layer::enabled == true, QQuickItem will be a texture
823     // provider. In this case we should prefer to return the layer rather
824     // than the canvas itself.
825     if (QQuickItem::isTextureProvider())
826         return QQuickItem::textureProvider();
827 
828     Q_D(const QQuickCanvasItem);
829 #if QT_CONFIG(opengl)
830     QQuickWindow *w = window();
831     if (!w || !w->isSceneGraphInitialized()
832             || QThread::currentThread() != QQuickWindowPrivate::get(w)->context->thread()) {
833         qWarning("QQuickCanvasItem::textureProvider: can only be queried on the rendering thread of an exposed window");
834         return nullptr;
835     }
836 #endif
837     if (!d->textureProvider)
838         d->textureProvider = new QQuickCanvasTextureProvider;
839     d->textureProvider->tex = d->nodeTexture;
840     return d->textureProvider;
841 }
842 
843 /*!
844     \qmlmethod object QtQuick::Canvas::getContext(string contextId, ... args)
845 
846     Returns a drawing context, or \c null if no context is available.
847 
848     The \a contextId parameter names the required context. The Canvas item
849     will return a context that implements the required drawing mode. After the
850     first call to getContext, any subsequent call to getContext with the same
851     contextId will return the same context object. Any additional arguments
852     (\a args) are currently ignored.
853 
854     If the context type is not supported or the canvas has previously been
855     requested to provide a different and incompatible context type, \c null
856     will be returned.
857 
858     Canvas only supports a 2d context.
859 
860 */
861 
getContext(QQmlV4Function * args)862 void QQuickCanvasItem::getContext(QQmlV4Function *args)
863 {
864     Q_D(QQuickCanvasItem);
865 
866     QV4::Scope scope(args->v4engine());
867     QV4::ScopedString str(scope, (*args)[0]);
868     if (!str) {
869         qmlWarning(this) << "getContext should be called with a string naming the required context type";
870         args->setReturnValue(QV4::Encode::null());
871         return;
872     }
873 
874     if (!d->available) {
875         qmlWarning(this) << "Unable to use getContext() at this time, please wait for available: true";
876         args->setReturnValue(QV4::Encode::null());
877         return;
878     }
879 
880     QString contextId = str->toQString();
881 
882     if (d->context != nullptr) {
883         if (d->context->contextNames().contains(contextId, Qt::CaseInsensitive)) {
884             args->setReturnValue(d->context->v4value());
885             return;
886         }
887 
888         qmlWarning(this) << "Canvas already initialized with a different context type";
889         args->setReturnValue(QV4::Encode::null());
890         return;
891     }
892 
893     if (createContext(contextId))
894         args->setReturnValue(d->context->v4value());
895     else
896         args->setReturnValue(QV4::Encode::null());
897 }
898 
899 /*!
900     \qmlmethod int QtQuick::Canvas::requestAnimationFrame(callback)
901 
902     This function schedules \a callback to be invoked before composing the Qt Quick
903     scene.
904 */
905 
requestAnimationFrame(QQmlV4Function * args)906 void QQuickCanvasItem::requestAnimationFrame(QQmlV4Function *args)
907 {
908     QV4::Scope scope(args->v4engine());
909     QV4::ScopedFunctionObject f(scope, (*args)[0]);
910     if (!f) {
911         qmlWarning(this) << "requestAnimationFrame should be called with an animation callback function";
912         args->setReturnValue(QV4::Encode::null());
913         return;
914     }
915 
916     Q_D(QQuickCanvasItem);
917 
918     static int id = 0;
919 
920     d->animationCallbacks.insert(++id, QV4::PersistentValue(scope.engine, f->asReturnedValue()));
921 
922     // QTBUG-55778: Calling polish directly here can lead to a polish loop
923     if (isVisible())
924         schedulePolish();
925 
926     args->setReturnValue(QV4::Encode(id));
927 }
928 
929 /*!
930     \qmlmethod QtQuick::Canvas::cancelRequestAnimationFrame(int handle)
931 
932     This function will cancel the animation callback referenced by \a handle.
933 */
934 
cancelRequestAnimationFrame(QQmlV4Function * args)935 void QQuickCanvasItem::cancelRequestAnimationFrame(QQmlV4Function *args)
936 {
937     QV4::Scope scope(args->v4engine());
938     QV4::ScopedValue v(scope, (*args)[0]);
939     if (!v->isInteger()) {
940         qmlWarning(this) << "cancelRequestAnimationFrame should be called with an animation callback id";
941         args->setReturnValue(QV4::Encode::null());
942         return;
943     }
944 
945     d_func()->animationCallbacks.remove(v->integerValue());
946 }
947 
948 
949 /*!
950     \qmlmethod QtQuick::Canvas::requestPaint()
951 
952     Request the entire visible region be re-drawn.
953 
954     \sa markDirty()
955 */
956 
requestPaint()957 void QQuickCanvasItem::requestPaint()
958 {
959     markDirty(d_func()->canvasWindow);
960 }
961 
962 /*!
963     \qmlmethod QtQuick::Canvas::markDirty(rect area)
964 
965     Marks the given \a area as dirty, so that when this area is visible the
966     canvas renderer will redraw it. This will trigger the \c paint signal.
967 
968     \sa paint, requestPaint()
969 */
970 
markDirty(const QRectF & rect)971 void QQuickCanvasItem::markDirty(const QRectF& rect)
972 {
973     Q_D(QQuickCanvasItem);
974     if (!d->available)
975         return;
976 
977     d->dirtyRect |= rect;
978 
979     polish();
980 }
981 
checkAnimationCallbacks()982 void QQuickCanvasItem::checkAnimationCallbacks()
983 {
984     if (d_func()->animationCallbacks.size() > 0 && isVisible())
985         polish();
986 }
987 
988 /*!
989     \qmlmethod bool QtQuick::Canvas::save(string filename)
990 
991     Saves the current canvas content into an image file \a filename.
992     The saved image format is automatically decided by the \a filename's
993     suffix. Returns \c true on success.
994 
995     \note Calling this method will force painting the whole canvas, not just the
996     current canvas visible window.
997 
998     \sa canvasWindow, canvasSize, toDataURL()
999 */
save(const QString & filename) const1000 bool QQuickCanvasItem::save(const QString &filename) const
1001 {
1002     Q_D(const QQuickCanvasItem);
1003     QUrl url = d->baseUrl.resolved(QUrl::fromLocalFile(filename));
1004     return toImage().save(url.toLocalFile());
1005 }
1006 
loadedPixmap(const QUrl & url)1007 QQmlRefPointer<QQuickCanvasPixmap> QQuickCanvasItem::loadedPixmap(const QUrl& url)
1008 {
1009     Q_D(QQuickCanvasItem);
1010     QUrl fullPathUrl = d->baseUrl.resolved(url);
1011     if (!d->pixmaps.contains(fullPathUrl)) {
1012         loadImage(url);
1013     }
1014     return d->pixmaps.value(fullPathUrl);
1015 }
1016 
1017 /*!
1018     \qmlsignal QtQuick::Canvas::imageLoaded()
1019 
1020     This signal is emitted when an image has been loaded.
1021 
1022     \sa loadImage()
1023 */
1024 
1025 /*!
1026     \qmlmethod QtQuick::Canvas::loadImage(url image)
1027 
1028     Loads the given \a image asynchronously.
1029 
1030     Once the image is ready, imageLoaded() signal will be emitted.
1031     The loaded image can be unloaded with the unloadImage() method.
1032 
1033     \note Only loaded images can be painted on the Canvas item.
1034 
1035     \sa unloadImage(), imageLoaded(), isImageLoaded(),
1036         Context2D::createImageData(), Context2D::drawImage()
1037 */
loadImage(const QUrl & url)1038 void QQuickCanvasItem::loadImage(const QUrl& url)
1039 {
1040     Q_D(QQuickCanvasItem);
1041     QUrl fullPathUrl = d->baseUrl.resolved(url);
1042     if (!d->pixmaps.contains(fullPathUrl)) {
1043         QQuickPixmap* pix = new QQuickPixmap();
1044         QQmlRefPointer<QQuickCanvasPixmap> canvasPix;
1045         canvasPix.adopt(new QQuickCanvasPixmap(pix));
1046         d->pixmaps.insert(fullPathUrl, canvasPix);
1047 
1048         pix->load(qmlEngine(this)
1049                 , fullPathUrl
1050                 , QQuickPixmap::Cache | QQuickPixmap::Asynchronous);
1051         if (pix->isLoading())
1052             pix->connectFinished(this, SIGNAL(imageLoaded()));
1053     }
1054 }
1055 /*!
1056     \qmlmethod QtQuick::Canvas::unloadImage(url image)
1057 
1058     Unloads the \a image.
1059 
1060     Once an image is unloaded, it cannot be painted by the canvas context
1061     unless it is loaded again.
1062 
1063     \sa loadImage(), imageLoaded(), isImageLoaded(),
1064         Context2D::createImageData(), Context2D::drawImage
1065 */
unloadImage(const QUrl & url)1066 void QQuickCanvasItem::unloadImage(const QUrl& url)
1067 {
1068     Q_D(QQuickCanvasItem);
1069     d->pixmaps.remove(d->baseUrl.resolved(url));
1070 }
1071 
1072 /*!
1073     \qmlmethod QtQuick::Canvas::isImageError(url image)
1074 
1075     Returns \c true if the \a image failed to load, \c false otherwise.
1076 
1077     \sa loadImage()
1078 */
isImageError(const QUrl & url) const1079 bool QQuickCanvasItem::isImageError(const QUrl& url) const
1080 {
1081     Q_D(const QQuickCanvasItem);
1082     QUrl fullPathUrl = d->baseUrl.resolved(url);
1083     return d->pixmaps.contains(fullPathUrl)
1084         && d->pixmaps.value(fullPathUrl)->pixmap()->isError();
1085 }
1086 
1087 /*!
1088   \qmlmethod QtQuick::Canvas::isImageLoading(url image)
1089   Returns true if the \a image is currently loading.
1090 
1091   \sa loadImage()
1092 */
isImageLoading(const QUrl & url) const1093 bool QQuickCanvasItem::isImageLoading(const QUrl& url) const
1094 {
1095     Q_D(const QQuickCanvasItem);
1096     QUrl fullPathUrl = d->baseUrl.resolved(url);
1097     return d->pixmaps.contains(fullPathUrl)
1098         && d->pixmaps.value(fullPathUrl)->pixmap()->isLoading();
1099 }
1100 /*!
1101   \qmlmethod QtQuick::Canvas::isImageLoaded(url image)
1102   Returns true if the \a image is successfully loaded and ready to use.
1103 
1104   \sa loadImage()
1105 */
isImageLoaded(const QUrl & url) const1106 bool QQuickCanvasItem::isImageLoaded(const QUrl& url) const
1107 {
1108     Q_D(const QQuickCanvasItem);
1109     QUrl fullPathUrl = d->baseUrl.resolved(url);
1110     return d->pixmaps.contains(fullPathUrl)
1111         && d->pixmaps.value(fullPathUrl)->pixmap()->isReady();
1112 }
1113 
toImage(const QRectF & rect) const1114 QImage QQuickCanvasItem::toImage(const QRectF& rect) const
1115 {
1116     Q_D(const QQuickCanvasItem);
1117 
1118     if (!d->context)
1119         return QImage();
1120 
1121     const QRectF &rectSource = rect.isEmpty() ? canvasWindow() : rect;
1122     const qreal dpr = window() ? window()->effectiveDevicePixelRatio() : qreal(1);
1123     const QRectF rectScaled(rectSource.topLeft() * dpr, rectSource.size() * dpr);
1124 
1125     QImage image = d->context->toImage(rectScaled);
1126     image.setDevicePixelRatio(dpr);
1127     return image;
1128 }
1129 
mimeToType(const QString & mime)1130 static const char* mimeToType(const QString &mime)
1131 {
1132     const QLatin1String imagePrefix("image/");
1133     if (!mime.startsWith(imagePrefix))
1134         return nullptr;
1135     const QStringRef mimeExt = mime.midRef(imagePrefix.size());
1136     if (mimeExt == QLatin1String("png"))
1137         return "png";
1138     else if (mimeExt == QLatin1String("bmp"))
1139         return "bmp";
1140     else if (mimeExt == QLatin1String("jpeg"))
1141         return "jpeg";
1142     else if (mimeExt == QLatin1String("x-portable-pixmap"))
1143         return "ppm";
1144     else if (mimeExt == QLatin1String("tiff"))
1145         return "tiff";
1146     else if (mimeExt == QLatin1String("xpm"))
1147         return "xpm";
1148     return nullptr;
1149 }
1150 
1151 /*!
1152   \qmlmethod string QtQuick::Canvas::toDataURL(string mimeType)
1153 
1154    Returns a data URL for the image in the canvas.
1155 
1156    The default \a mimeType is "image/png".
1157 
1158    \sa save()
1159 */
toDataURL(const QString & mimeType) const1160 QString QQuickCanvasItem::toDataURL(const QString& mimeType) const
1161 {
1162     QImage image = toImage();
1163 
1164     if (!image.isNull()) {
1165         QByteArray ba;
1166         QBuffer buffer(&ba);
1167         buffer.open(QIODevice::WriteOnly);
1168         const QString mime = mimeType.toLower();
1169         const char* type = mimeToType(mime);
1170         if (!type)
1171             return QStringLiteral("data:,");
1172 
1173         image.save(&buffer, type);
1174         buffer.close();
1175         return QLatin1String("data:") + mime + QLatin1String(";base64,") + QLatin1String(ba.toBase64().constData());
1176     }
1177     return QStringLiteral("data:,");
1178 }
1179 
delayedCreate()1180 void QQuickCanvasItem::delayedCreate()
1181 {
1182     Q_D(QQuickCanvasItem);
1183 
1184     if (!d->context && !d->contextType.isNull())
1185         createContext(d->contextType);
1186 
1187     requestPaint();
1188 }
1189 
createContext(const QString & contextType)1190 bool QQuickCanvasItem::createContext(const QString &contextType)
1191 {
1192     Q_D(QQuickCanvasItem);
1193 
1194     if (!window())
1195         return false;
1196 
1197     if (contextType == QLatin1String("2d")) {
1198         if (d->contextType.compare(QLatin1String("2d"), Qt::CaseInsensitive) != 0)  {
1199             d->contextType = QLatin1String("2d");
1200             emit contextTypeChanged(); // XXX: can't be in setContextType()
1201         }
1202         initializeContext(new QQuickContext2D(this));
1203         return true;
1204     }
1205 
1206     return false;
1207 }
1208 
initializeContext(QQuickCanvasContext * context,const QVariantMap & args)1209 void QQuickCanvasItem::initializeContext(QQuickCanvasContext *context, const QVariantMap &args)
1210 {
1211     Q_D(QQuickCanvasItem);
1212 
1213     d->context = context;
1214     d->context->init(this, args);
1215     d->context->setV4Engine(qmlEngine(this)->handle());
1216     connect(d->context, SIGNAL(textureChanged()), SLOT(update()));
1217     connect(d->context, SIGNAL(textureChanged()), SIGNAL(painted()));
1218     emit contextChanged();
1219 }
1220 
tiledRect(const QRectF & window,const QSize & tileSize)1221 QRect QQuickCanvasItem::tiledRect(const QRectF &window, const QSize &tileSize)
1222 {
1223     if (window.isEmpty())
1224         return QRect();
1225 
1226     const int tw = tileSize.width();
1227     const int th = tileSize.height();
1228     const int h1 = window.left() / tw;
1229     const int v1 = window.top() / th;
1230 
1231     const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
1232     const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
1233 
1234     return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
1235 }
1236 
1237 /*!
1238     \qmlsignal QtQuick::Canvas::paint(rect region)
1239 
1240     This signal is emitted when the \a region needs to be rendered. If a context
1241     is active it can be referenced from the context property.
1242 
1243     This signal can be triggered by markdirty(), requestPaint() or by changing
1244     the current canvas window.
1245 */
1246 
1247 /*!
1248     \qmlsignal QtQuick::Canvas::painted()
1249 
1250     This signal is emitted after all context painting commands are executed and
1251     the Canvas has been rendered.
1252 */
1253 
1254 QT_END_NAMESPACE
1255 
1256 #include "moc_qquickcanvasitem_p.cpp"
1257