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