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
63QT_BEGIN_NAMESPACE
64
65class QQuickCanvasTextureProvider : public QSGTextureProvider
66{
67public:
68 QSGTexture *tex;
69 QSGTexture *texture() const override { return tex; }
70 void fireTextureChanged() { emit textureChanged(); }
71};
72
73QQuickCanvasPixmap::QQuickCanvasPixmap(const QImage& image)
74 : m_pixmap(nullptr)
75 , m_image(image)
76{
77
78}
79
80QQuickCanvasPixmap::QQuickCanvasPixmap(QQuickPixmap *pixmap)
81 : m_pixmap(pixmap)
82{
83
84}
85
86QQuickCanvasPixmap::~QQuickCanvasPixmap()
87{
88 delete m_pixmap;
89}
90
91qreal QQuickCanvasPixmap::width() const
92{
93 if (m_pixmap)
94 return m_pixmap->width();
95
96 return m_image.width();
97}
98
99qreal QQuickCanvasPixmap::height() const
100{
101 if (m_pixmap)
102 return m_pixmap->height();
103
104 return m_image.height();
105}
106
107bool QQuickCanvasPixmap::isValid() const
108{
109 if (m_pixmap)
110 return m_pixmap->isReady();
111 return !m_image.isNull();
112}
113
114QImage QQuickCanvasPixmap::image()
115{
116 if (m_image.isNull() && m_pixmap)
117 m_image = m_pixmap->image();
118
119 return m_image;
120}
121
122QHash<QQmlEngine *,QQuickContext2DRenderThread*> QQuickContext2DRenderThread::renderThreads;
123QMutex QQuickContext2DRenderThread::renderThreadsMutex;
124
125QQuickContext2DRenderThread::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(thread: this);
131 connect(asender: m_eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), atype: Qt::DirectConnection);
132 start(QThread::IdlePriority);
133}
134
135QQuickContext2DRenderThread::~QQuickContext2DRenderThread()
136{
137 renderThreadsMutex.lock();
138 renderThreads.remove(key: m_engine);
139 renderThreadsMutex.unlock();
140
141 m_eventLoopQuitHack->deleteLater();
142 wait();
143}
144
145QQuickContext2DRenderThread *QQuickContext2DRenderThread::instance(QQmlEngine *engine)
146{
147 QQuickContext2DRenderThread *thread = nullptr;
148 renderThreadsMutex.lock();
149 if (renderThreads.contains(key: engine))
150 thread = renderThreads.value(key: engine);
151 else {
152 thread = new QQuickContext2DRenderThread(engine);
153 renderThreads.insert(key: engine, value: thread);
154 }
155 renderThreadsMutex.unlock();
156 return thread;
157}
158
159class QQuickCanvasItemPrivate : public QQuickItemPrivate
160{
161public:
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
184QQuickCanvasItemPrivate::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
202QQuickCanvasItemPrivate::~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
307QQuickCanvasItem::QQuickCanvasItem(QQuickItem *parent)
308 : QQuickItem(*(new QQuickCanvasItemPrivate), parent)
309{
310 setFlag(flag: ItemHasContents);
311}
312
313QQuickCanvasItem::~QQuickCanvasItem()
314{
315 Q_D(QQuickCanvasItem);
316 delete d->context;
317 if (d->textureProvider)
318 QQuickWindowQObjectCleanupJob::schedule(window: window(), object: 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
327bool 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
347QString QQuickCanvasItem::contextType() const
348{
349 return d_func()->contextType;
350}
351
352void QQuickCanvasItem::setContextType(const QString &contextType)
353{
354 Q_D(QQuickCanvasItem);
355
356 if (contextType.compare(s: d->contextType, cs: Qt::CaseInsensitive) == 0)
357 return;
358
359 if (d->context) {
360 qmlWarning(me: 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
381QJSValue 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*/
401QSizeF QQuickCanvasItem::canvasSize() const
402{
403 Q_D(const QQuickCanvasItem);
404 return d->canvasSize;
405}
406
407void 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*/
437QSize QQuickCanvasItem::tileSize() const
438{
439 Q_D(const QQuickCanvasItem);
440 return d->tileSize;
441}
442
443void 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*/
472QRectF QQuickCanvasItem::canvasWindow() const
473{
474 Q_D(const QQuickCanvasItem);
475 return d->canvasWindow;
476}
477
478void 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*/
509QQuickCanvasItem::RenderTarget QQuickCanvasItem::renderTarget() const
510{
511 Q_D(const QQuickCanvasItem);
512 return d->renderTarget;
513}
514
515void QQuickCanvasItem::setRenderTarget(QQuickCanvasItem::RenderTarget target)
516{
517 Q_D(QQuickCanvasItem);
518 if (d->renderTarget != target) {
519 if (d->context) {
520 qmlWarning(me: 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
554QQuickCanvasItem::RenderStrategy QQuickCanvasItem::renderStrategy() const
555{
556 return d_func()->renderStrategy;
557}
558
559void QQuickCanvasItem::setRenderStrategy(QQuickCanvasItem::RenderStrategy strategy)
560{
561 Q_D(QQuickCanvasItem);
562 if (d->renderStrategy != strategy) {
563 if (d->context) {
564 qmlWarning(me: this) << "Canvas:renderStrategy not changeable once context is active.";
565 return;
566 }
567 d->renderStrategy = strategy;
568 emit renderStrategyChanged();
569 }
570}
571
572QQuickCanvasContext* QQuickCanvasItem::rawContext() const
573{
574 return d_func()->context;
575}
576
577bool QQuickCanvasItem::isPaintConnected()
578{
579 IS_SIGNAL_CONNECTED(this, QQuickCanvasItem, paint, (const QRect &));
580}
581
582void QQuickCanvasItem::sceneGraphInitialized()
583{
584 Q_D(QQuickCanvasItem);
585
586 d->available = true;
587 connect(asender: this, SIGNAL(visibleChanged()), SLOT(checkAnimationCallbacks()));
588 QMetaObject::invokeMethod(obj: this, member: "availableChanged", type: Qt::QueuedConnection);
589
590 if (!d->contextType.isNull())
591 QMetaObject::invokeMethod(obj: this, member: "delayedCreate", type: Qt::QueuedConnection);
592 else if (isPaintConnected())
593 QMetaObject::invokeMethod(obj: this, member: "requestPaint", type: Qt::QueuedConnection);
594}
595
596void 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
628void 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: window(), object: d->textureProvider);
639 d->textureProvider = nullptr;
640 }
641 if (d->nodeTexture) {
642 QQuickWindowQObjectCleanupJob::schedule(window: window(), object: d->nodeTexture);
643 d->nodeTexture = nullptr;
644 }
645}
646
647bool 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
658void 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
671void QQuickCanvasItem::schedulePolish()
672{
673 auto polishRequestEvent = new QEvent(QEvent::PolishRequest);
674 QCoreApplication::postEvent(receiver: this, event: polishRequestEvent);
675}
676
677void QQuickCanvasItem::componentComplete()
678{
679 QQuickItem::componentComplete();
680
681 Q_D(QQuickCanvasItem);
682 d->baseUrl = qmlEngine(this)->contextForObject(this)->baseUrl();
683}
684
685void 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(c: 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(obj: this, member: "sceneGraphInitialized", type: Qt::QueuedConnection);
710 } else {
711 connect(asender: d->window, SIGNAL(sceneGraphInitialized()), SLOT(sceneGraphInitialized()));
712 }
713}
714
715void QQuickCanvasItem::updatePolish()
716{
717 QQuickItem::updatePolish();
718
719 Q_D(QQuickCanvasItem);
720 if (d->context && d->renderStrategy != QQuickCanvasItem::Cooperative)
721 d->context->prepare(canvasSize: d->canvasSize.toSize(), tileSize: d->tileSize, canvasWindow: d->canvasWindow.toRect(), dirtyRect: d->dirtyRect.toRect(), smooth: d->smooth, antialiasing: 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(engine: v4, object: 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(i: QDateTime::currentMSecsSinceEpoch());
736 function->call(data: jsCall);
737 }
738 }
739 else {
740 if (d->dirtyRect.isValid()) {
741 if (d->hasTileSize && d->hasCanvasWindow)
742 emit paint(region: tiledRect(window: d->canvasWindow.intersected(r: d->dirtyRect.toAlignedRect()), tileSize: d->tileSize));
743 else
744 emit paint(region: 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
757QSGNode *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(c: window())->context;
773 node = rc->sceneGraphContext()->createInternalImageNode(renderContext: 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(canvasSize: d->canvasSize.toSize(), tileSize: d->tileSize, canvasWindow: d->canvasWindow.toRect(), dirtyRect: d->dirtyRect.toRect(), smooth: d->smooth, antialiasing: antialiasing());
785 d->context->flush();
786 }
787
788 QQuickContext2D *ctx = qobject_cast<QQuickContext2D *>(object: d->context);
789 QQuickContext2DTexture *factory = ctx->texture();
790 QSGTexture *texture = factory->textureForNextFrame(lastFrame: d->nodeTexture, window: 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
815bool QQuickCanvasItem::isTextureProvider() const
816{
817 return true;
818}
819
820QSGTextureProvider *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(c: w)->context->thread()) {
833 qWarning(msg: "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
862void 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(me: 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(me: 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(str: contextId, cs: Qt::CaseInsensitive)) {
884 args->setReturnValue(d->context->v4value());
885 return;
886 }
887
888 qmlWarning(me: this) << "Canvas already initialized with a different context type";
889 args->setReturnValue(QV4::Encode::null());
890 return;
891 }
892
893 if (createContext(contextType: 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
906void QQuickCanvasItem::requestAnimationFrame(QQmlV4Function *args)
907{
908 QV4::Scope scope(args->v4engine());
909 QV4::ScopedFunctionObject f(scope, (*args)[0]);
910 if (!f) {
911 qmlWarning(me: 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(key: ++id, value: 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
935void QQuickCanvasItem::cancelRequestAnimationFrame(QQmlV4Function *args)
936{
937 QV4::Scope scope(args->v4engine());
938 QV4::ScopedValue v(scope, (*args)[0]);
939 if (!v->isInteger()) {
940 qmlWarning(me: 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(key: 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
957void QQuickCanvasItem::requestPaint()
958{
959 markDirty(dirtyRect: 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
971void 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
982void 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*/
1000bool QQuickCanvasItem::save(const QString &filename) const
1001{
1002 Q_D(const QQuickCanvasItem);
1003 QUrl url = d->baseUrl.resolved(relative: QUrl::fromLocalFile(localfile: filename));
1004 return toImage().save(fileName: url.toLocalFile());
1005}
1006
1007QQmlRefPointer<QQuickCanvasPixmap> QQuickCanvasItem::loadedPixmap(const QUrl& url)
1008{
1009 Q_D(QQuickCanvasItem);
1010 QUrl fullPathUrl = d->baseUrl.resolved(relative: url);
1011 if (!d->pixmaps.contains(key: fullPathUrl)) {
1012 loadImage(url);
1013 }
1014 return d->pixmaps.value(key: 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*/
1038void QQuickCanvasItem::loadImage(const QUrl& url)
1039{
1040 Q_D(QQuickCanvasItem);
1041 QUrl fullPathUrl = d->baseUrl.resolved(relative: url);
1042 if (!d->pixmaps.contains(key: fullPathUrl)) {
1043 QQuickPixmap* pix = new QQuickPixmap();
1044 QQmlRefPointer<QQuickCanvasPixmap> canvasPix;
1045 canvasPix.adopt(new QQuickCanvasPixmap(pix));
1046 d->pixmaps.insert(key: fullPathUrl, value: canvasPix);
1047
1048 pix->load(qmlEngine(this)
1049 , fullPathUrl
1050 , options: 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*/
1066void QQuickCanvasItem::unloadImage(const QUrl& url)
1067{
1068 Q_D(QQuickCanvasItem);
1069 d->pixmaps.remove(key: d->baseUrl.resolved(relative: 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*/
1079bool QQuickCanvasItem::isImageError(const QUrl& url) const
1080{
1081 Q_D(const QQuickCanvasItem);
1082 QUrl fullPathUrl = d->baseUrl.resolved(relative: url);
1083 return d->pixmaps.contains(key: fullPathUrl)
1084 && d->pixmaps.value(key: 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*/
1093bool QQuickCanvasItem::isImageLoading(const QUrl& url) const
1094{
1095 Q_D(const QQuickCanvasItem);
1096 QUrl fullPathUrl = d->baseUrl.resolved(relative: url);
1097 return d->pixmaps.contains(key: fullPathUrl)
1098 && d->pixmaps.value(key: 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*/
1106bool QQuickCanvasItem::isImageLoaded(const QUrl& url) const
1107{
1108 Q_D(const QQuickCanvasItem);
1109 QUrl fullPathUrl = d->baseUrl.resolved(relative: url);
1110 return d->pixmaps.contains(key: fullPathUrl)
1111 && d->pixmaps.value(key: fullPathUrl)->pixmap()->isReady();
1112}
1113
1114QImage 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(bounds: rectScaled);
1126 image.setDevicePixelRatio(dpr);
1127 return image;
1128}
1129
1130static const char* mimeToType(const QString &mime)
1131{
1132 const QLatin1String imagePrefix("image/");
1133 if (!mime.startsWith(s: imagePrefix))
1134 return nullptr;
1135 const QStringRef mimeExt = mime.midRef(position: 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*/
1160QString 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(openMode: 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(device: &buffer, format: type);
1174 buffer.close();
1175 return QLatin1String("data:") + mime + QLatin1String(";base64,") + QLatin1String(ba.toBase64().constData());
1176 }
1177 return QStringLiteral("data:,");
1178}
1179
1180void QQuickCanvasItem::delayedCreate()
1181{
1182 Q_D(QQuickCanvasItem);
1183
1184 if (!d->context && !d->contextType.isNull())
1185 createContext(contextType: d->contextType);
1186
1187 requestPaint();
1188}
1189
1190bool 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(other: QLatin1String("2d"), cs: Qt::CaseInsensitive) != 0) {
1199 d->contextType = QLatin1String("2d");
1200 emit contextTypeChanged(); // XXX: can't be in setContextType()
1201 }
1202 initializeContext(context: new QQuickContext2D(this));
1203 return true;
1204 }
1205
1206 return false;
1207}
1208
1209void QQuickCanvasItem::initializeContext(QQuickCanvasContext *context, const QVariantMap &args)
1210{
1211 Q_D(QQuickCanvasItem);
1212
1213 d->context = context;
1214 d->context->init(canvasItem: this, args);
1215 d->context->setV4Engine(qmlEngine(this)->handle());
1216 connect(asender: d->context, SIGNAL(textureChanged()), SLOT(update()));
1217 connect(asender: d->context, SIGNAL(textureChanged()), SIGNAL(painted()));
1218 emit contextChanged();
1219}
1220
1221QRect 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
1254QT_END_NAMESPACE
1255
1256#include "moc_qquickcanvasitem_p.cpp"
1257

source code of qtdeclarative/src/quick/items/context2d/qquickcanvasitem.cpp