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

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