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
4#include <private/qsgadaptationlayer_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/qquickpixmap_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
29
31{
32public:
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(this);
96 connect(m_eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), Qt::DirectConnection);
97 start(QThread::IdlePriority);
98}
99
101{
102 renderThreadsMutex.lock();
103 renderThreads.remove(m_engine);
104 renderThreadsMutex.unlock();
105
106 m_eventLoopQuitHack->deleteLater();
107 wait();
108}
109
111{
112 QQuickContext2DRenderThread *thread = nullptr;
113 renderThreadsMutex.lock();
114 if (renderThreads.contains(engine))
115 thread = renderThreads.value(engine);
116 else {
117 thread = new QQuickContext2DRenderThread(engine);
118 renderThreads.insert(engine, thread);
119 }
120 renderThreadsMutex.unlock();
121 return thread;
122}
123
148
151 , context(nullptr)
152 , canvasSize(1, 1)
153 , tileSize(1, 1)
154 , hasCanvasSize(false)
155 , hasTileSize(false)
156 , hasCanvasWindow(false)
157 , available(false)
160 , textureProvider(nullptr)
161 , node(nullptr)
162 , nodeTexture(nullptr)
163{
164 implicitAntialiasing = true;
165}
166
168{
169 pixmaps.clear();
170}
171
172
173/*!
174 \qmltype Canvas
175 \nativetype 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(ItemHasContents);
262}
263
265{
266 Q_D(QQuickCanvasItem);
267 delete d->context;
268 if (d->textureProvider)
269 QQuickWindowQObjectCleanupJob::schedule(window(), 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
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
299{
300 return d_func()->contextType;
301}
302
303void QQuickCanvasItem::setContextType(const QString &contextType)
304{
305 Q_D(QQuickCanvasItem);
306
307 if (contextType.compare(d->contextType, Qt::CaseInsensitive) == 0)
308 return;
309
310 if (d->context) {
311 qmlWarning(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
333{
334 Q_D(const QQuickCanvasItem);
335 return d->context ? QJSValuePrivate::fromReturnedValue(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*/
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*/
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*/
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*/
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(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
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(this) << "Canvas:renderStrategy not changeable once context is active.";
512 return;
513 }
514 d->renderStrategy = strategy;
515 emit renderStrategyChanged();
516 }
517}
518
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(this, SIGNAL(visibleChanged()), SLOT(checkAnimationCallbacks()));
535 QMetaObject::invokeMethod(this, "availableChanged", Qt::QueuedConnection);
536
537 if (!d->contextType.isNull())
538 QMetaObject::invokeMethod(this, "delayedCreate", Qt::QueuedConnection);
539 else if (isPaintConnected())
540 QMetaObject::invokeMethod(this, "requestPaint", 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
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(), d->textureProvider);
586 d->textureProvider = nullptr;
587 }
588 if (d->nodeTexture) {
589 QQuickWindowQObjectCleanupJob::schedule(window(), 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(this, polishRequestEvent);
626}
627
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(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(this, "sceneGraphInitialized", Qt::QueuedConnection);
661 } else {
662 connect(d->window, SIGNAL(sceneGraphInitialized()), SLOT(sceneGraphInitialized()));
663 }
664}
665
667{
668 QQuickItem::updatePolish();
669
670 Q_D(QQuickCanvasItem);
671 if (d->context && d->renderStrategy != QQuickCanvasItem::Cooperative)
672 d->context->prepare(d->canvasSize.toSize(), d->tileSize, d->canvasWindow.toRect(), d->dirtyRect.toRect(), d->smooth, 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(v4, 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(QDateTime::currentMSecsSinceEpoch());
687 function->call(jsCall);
688 }
689 }
690 else {
691 if (d->dirtyRect.isValid()) {
692 if (d->hasTileSize && d->hasCanvasWindow)
693 emit paint(tiledRect(d->canvasWindow.intersected(d->dirtyRect.toAlignedRect()), d->tileSize));
694 else
695 emit paint(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(window())->context;
724 node = rc->sceneGraphContext()->createInternalImageNode(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(d->canvasSize.toSize(), d->tileSize, d->canvasWindow.toRect(), d->dirtyRect.toRect(), d->smooth, antialiasing());
736 d->context->flush();
737 }
738
739 QQuickContext2D *ctx = qobject_cast<QQuickContext2D *>(d->context);
740 QQuickContext2DTexture *factory = ctx->texture();
741 QSGTexture *texture = factory->textureForNextFrame(d->nodeTexture, 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
767{
768 return true;
769}
770
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(w)->context->thread()) {
784 qWarning("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(QQmlV4FunctionPtr 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(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(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(contextId, Qt::CaseInsensitive)) {
835 args->setReturnValue(d->context->v4value());
836 return;
837 }
838
839 qmlWarning(this) << "Canvas already initialized with a different context type";
840 args->setReturnValue(QV4::Encode::null());
841 return;
842 }
843
844 if (createContext(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(QQmlV4FunctionPtr args)
858{
859 QV4::Scope scope(args->v4engine());
860 QV4::ScopedFunctionObject f(scope, (*args)[0]);
861 if (!f) {
862 qmlWarning(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(++id, 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
887{
888 QV4::Scope scope(args->v4engine());
889 QV4::ScopedValue v(scope, (*args)[0]);
890 if (!v->isInteger()) {
891 qmlWarning(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(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
909{
910 markDirty(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(QUrl::fromLocalFile(filename));
958 return toImage(QRectF(QPointF(0, 0), imageSize)).save(url.toLocalFile());
959}
960
962{
963 Q_D(QQuickCanvasItem);
964 QUrl fullPathUrl = d->baseUrl.resolved(url);
965 if (!d->pixmaps.contains(fullPathUrl)) {
966 loadImage(url, sourceSize);
967 }
968 return d->pixmaps.value(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, size sourceSize = undefined)
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 If \a sourceSize is specified, the image will be scaled to that size during loading. This is
990 useful for loading scalable (vector) images (eg. SVGs) at their intended display size. This
991 parameter was introduced in Qt 6.7.
992
993 \sa unloadImage(), imageLoaded(), isImageLoaded(),
994 Context2D::createImageData(), Context2D::drawImage()
995*/
996void QQuickCanvasItem::loadImage(const QUrl& url, QSizeF sourceSize)
997{
998 Q_D(QQuickCanvasItem);
999 QUrl fullPathUrl = d->baseUrl.resolved(url);
1000 if (!d->pixmaps.contains(fullPathUrl)) {
1001 QQuickPixmap* pix = new QQuickPixmap();
1002 QQmlRefPointer<QQuickCanvasPixmap> canvasPix;
1003 canvasPix.adopt(new QQuickCanvasPixmap(pix));
1004 d->pixmaps.insert(fullPathUrl, canvasPix);
1005
1006 pix->load(qmlEngine(this)
1007 , fullPathUrl
1008 , QRect()
1009 , sourceSize.toSize()
1010 , QQuickPixmap::Cache | QQuickPixmap::Asynchronous);
1011 if (pix->isLoading())
1012 pix->connectFinished(this, SIGNAL(imageLoaded()));
1013 }
1014}
1015/*!
1016 \qmlmethod QtQuick::Canvas::unloadImage(url image)
1017
1018 Unloads the \a image.
1019
1020 Once an image is unloaded, it cannot be painted by the canvas context
1021 unless it is loaded again.
1022
1023 \sa loadImage(), imageLoaded(), isImageLoaded(),
1024 Context2D::createImageData(), Context2D::drawImage
1025*/
1026void QQuickCanvasItem::unloadImage(const QUrl& url)
1027{
1028 Q_D(QQuickCanvasItem);
1029 d->pixmaps.remove(d->baseUrl.resolved(url));
1030}
1031
1032/*!
1033 \qmlmethod QtQuick::Canvas::isImageError(url image)
1034
1035 Returns \c true if the \a image failed to load, \c false otherwise.
1036
1037 \sa loadImage()
1038*/
1039bool QQuickCanvasItem::isImageError(const QUrl& url) const
1040{
1041 Q_D(const QQuickCanvasItem);
1042 QUrl fullPathUrl = d->baseUrl.resolved(url);
1043 return d->pixmaps.contains(fullPathUrl)
1044 && d->pixmaps.value(fullPathUrl)->pixmap()->isError();
1045}
1046
1047/*!
1048 \qmlmethod QtQuick::Canvas::isImageLoading(url image)
1049 Returns true if the \a image is currently loading.
1050
1051 \sa loadImage()
1052*/
1053bool QQuickCanvasItem::isImageLoading(const QUrl& url) const
1054{
1055 Q_D(const QQuickCanvasItem);
1056 QUrl fullPathUrl = d->baseUrl.resolved(url);
1057 return d->pixmaps.contains(fullPathUrl)
1058 && d->pixmaps.value(fullPathUrl)->pixmap()->isLoading();
1059}
1060/*!
1061 \qmlmethod QtQuick::Canvas::isImageLoaded(url image)
1062 Returns true if the \a image is successfully loaded and ready to use.
1063
1064 \sa loadImage()
1065*/
1066bool QQuickCanvasItem::isImageLoaded(const QUrl& url) const
1067{
1068 Q_D(const QQuickCanvasItem);
1069 QUrl fullPathUrl = d->baseUrl.resolved(url);
1070 return d->pixmaps.contains(fullPathUrl)
1071 && d->pixmaps.value(fullPathUrl)->pixmap()->isReady();
1072}
1073
1074/*!
1075 \internal
1076 Returns a QImage representing the requested \a rect which is in device independent pixels of the item.
1077 If \a rect is empty, then it will use the whole item's rect by default.
1078*/
1079
1080QImage QQuickCanvasItem::toImage(const QRectF& rect) const
1081{
1082 Q_D(const QQuickCanvasItem);
1083
1084 if (!d->context)
1085 return QImage();
1086
1087 const QRectF &rectSource = rect.isEmpty() ? canvasWindow() : rect;
1088 const qreal dpr = window() && rect.isEmpty() ? window()->effectiveDevicePixelRatio() : qreal(1);
1089 const QRectF rectScaled(rectSource.topLeft() * dpr, rectSource.size() * dpr);
1090
1091 QImage image = d->context->toImage(rectScaled);
1092 image.setDevicePixelRatio(dpr);
1093 return image;
1094}
1095
1096static const char* mimeToType(const QString &mime)
1097{
1098 const QLatin1String imagePrefix("image/");
1099 if (!mime.startsWith(imagePrefix))
1100 return nullptr;
1101 const QStringView mimeExt = QStringView{mime}.mid(imagePrefix.size());
1102 if (mimeExt == QLatin1String("png"))
1103 return "png";
1104 else if (mimeExt == QLatin1String("bmp"))
1105 return "bmp";
1106 else if (mimeExt == QLatin1String("jpeg"))
1107 return "jpeg";
1108 else if (mimeExt == QLatin1String("x-portable-pixmap"))
1109 return "ppm";
1110 else if (mimeExt == QLatin1String("tiff"))
1111 return "tiff";
1112 else if (mimeExt == QLatin1String("xpm"))
1113 return "xpm";
1114 return nullptr;
1115}
1116
1117/*!
1118 \qmlmethod string QtQuick::Canvas::toDataURL(string mimeType)
1119
1120 Returns a data URL for the image in the canvas.
1121
1122 The default \a mimeType is "image/png".
1123
1124 \sa save()
1125*/
1126QString QQuickCanvasItem::toDataURL(const QString& mimeType) const
1127{
1128 QImage image = toImage();
1129
1130 if (!image.isNull()) {
1131 QByteArray ba;
1132 QBuffer buffer(&ba);
1133 buffer.open(QIODevice::WriteOnly);
1134 const QString mime = mimeType.toLower();
1135 const char* type = mimeToType(mime);
1136 if (!type)
1137 return QStringLiteral("data:,");
1138
1139 image.save(&buffer, type);
1140 buffer.close();
1141 return QLatin1String("data:") + mime + QLatin1String(";base64,") + QLatin1String(ba.toBase64().constData());
1142 }
1143 return QStringLiteral("data:,");
1144}
1145
1146void QQuickCanvasItem::delayedCreate()
1147{
1148 Q_D(QQuickCanvasItem);
1149
1150 if (!d->context && !d->contextType.isNull())
1151 createContext(d->contextType);
1152
1153 requestPaint();
1154}
1155
1156bool QQuickCanvasItem::createContext(const QString &contextType)
1157{
1158 Q_D(QQuickCanvasItem);
1159
1160 if (!window())
1161 return false;
1162
1163 if (contextType == QLatin1String("2d")) {
1164 if (d->contextType.compare(QLatin1String("2d"), Qt::CaseInsensitive) != 0) {
1165 d->contextType = QLatin1String("2d");
1166 emit contextTypeChanged(); // XXX: can't be in setContextType()
1167 }
1168 initializeContext(new QQuickContext2D(this));
1169 return true;
1170 }
1171
1172 return false;
1173}
1174
1175void QQuickCanvasItem::initializeContext(QQuickCanvasContext *context, const QVariantMap &args)
1176{
1177 Q_D(QQuickCanvasItem);
1178
1179 d->context = context;
1180 d->context->init(this, args);
1181 d->context->setV4Engine(qmlEngine(this)->handle());
1182 connect(d->context, SIGNAL(textureChanged()), SLOT(update()));
1183 connect(d->context, SIGNAL(textureChanged()), SIGNAL(painted()));
1184 emit contextChanged();
1185}
1186
1187QRect QQuickCanvasItem::tiledRect(const QRectF &window, const QSize &tileSize)
1188{
1189 if (window.isEmpty())
1190 return QRect();
1191
1192 const int tw = tileSize.width();
1193 const int th = tileSize.height();
1194 const int h1 = window.left() / tw;
1195 const int v1 = window.top() / th;
1196
1197 const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
1198 const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
1199
1200 return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
1201}
1202
1203/*!
1204 \qmlsignal QtQuick::Canvas::paint(rect region)
1205
1206 This signal is emitted when the \a region needs to be rendered. If a context
1207 is active it can be referenced from the context property.
1208
1209 This signal can be triggered by markDirty(), requestPaint() or by changing
1210 the current canvas window.
1211*/
1212
1213/*!
1214 \qmlsignal QtQuick::Canvas::painted()
1215
1216 This signal is emitted after all context painting commands are executed and
1217 the Canvas has been rendered.
1218*/
1219
1220QT_END_NAMESPACE
1221
1222#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)
Combined button and popup list for selecting options.
static const char * mimeToType(const QString &mime)