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