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
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 Schedules \a callback to be called before the Qt Quick scene is rendered,
856 and returns a handle that you can pass to cancelRequestAnimationFrame().
857
858 Use this function to animate the content of the canvas. A \l Timer fires
859 at a fixed interval, but the callback is synchronized with rendering, so
860 the canvas is redrawn once per frame. To update properties on every frame
861 without drawing, use \l FrameAnimation instead.
862
863 The canvas calls the callback once, during its next polish, and passes the
864 current time as its only argument. The time is in milliseconds since
865 1970-01-01T00:00:00 UTC. To keep animating, call requestAnimationFrame()
866 again from inside the callback. The canvas calls callbacks only while it's
867 visible. If it's hidden, pending callbacks wait until it's visible again.
868
869 Draw directly in the callback, using the context returned by getContext().
870 The drawing appears on the canvas in the same frame, so you don't need to
871 call requestPaint() or markDirty().
872
873 \note The canvas doesn't emit the \l paint signal in a frame where it calls
874 animation callbacks. While an animation keeps requesting frames, the
875 \c onPaint handler doesn't run, even if you call requestPaint(). Do all the
876 drawing for an animated canvas in the callback.
877
878 \qml
879 Canvas {
880 width: 200; height: 200
881 property real angle: 0
882
883 function drawFrame(timestamp) {
884 var ctx = getContext("2d");
885 ctx.reset();
886 ctx.translate(width / 2, height / 2);
887 ctx.rotate(angle);
888 ctx.fillStyle = "steelblue";
889 ctx.fillRect(-40, -40, 80, 80);
890 angle += 0.05;
891 requestAnimationFrame(drawFrame);
892 }
893
894 onAvailableChanged: if (available) requestAnimationFrame(drawFrame)
895 }
896 \endqml
897
898 \sa cancelRequestAnimationFrame(), FrameAnimation
899*/
900
901void QQuickCanvasItem::requestAnimationFrame(QQmlV4FunctionPtr args)
902{
903 QV4::Scope scope(args->v4engine());
904 QV4::ScopedFunctionObject f(scope, (*args)[0]);
905 if (!f) {
906 qmlWarning(this) << "requestAnimationFrame should be called with an animation callback function";
907 args->setReturnValue(QV4::Encode::null());
908 return;
909 }
910
911 Q_D(QQuickCanvasItem);
912
913 static int id = 0;
914
915 d->animationCallbacks.insert(++id, QV4::PersistentValue(scope.engine, f->asReturnedValue()));
916
917 // QTBUG-55778: Calling polish directly here can lead to a polish loop
918 if (isVisible())
919 schedulePolish();
920
921 args->setReturnValue(QV4::Encode(id));
922}
923
924/*!
925 \qmlmethod void QtQuick::Canvas::cancelRequestAnimationFrame(int handle)
926
927 This function will cancel the animation callback referenced by \a handle.
928*/
929
931{
932 QV4::Scope scope(args->v4engine());
933 QV4::ScopedValue v(scope, (*args)[0]);
934 if (!v->isInteger()) {
935 qmlWarning(this) << "cancelRequestAnimationFrame should be called with an animation callback id";
936 args->setReturnValue(QV4::Encode::null());
937 return;
938 }
939
940 d_func()->animationCallbacks.remove(v->integerValue());
941}
942
943
944/*!
945 \qmlmethod void QtQuick::Canvas::requestPaint()
946
947 Request the entire visible region be re-drawn.
948
949 \sa markDirty()
950*/
951
953{
954 markDirty(d_func()->canvasWindow);
955}
956
957/*!
958 \qmlmethod void QtQuick::Canvas::markDirty(rect area)
959
960 Marks the given \a area as dirty, so that when this area is visible the
961 canvas renderer will redraw it. This will trigger the \c paint signal.
962
963 \sa paint, requestPaint()
964*/
965
966void QQuickCanvasItem::markDirty(const QRectF& rect)
967{
968 Q_D(QQuickCanvasItem);
969 if (!d->available)
970 return;
971
972 d->dirtyRect |= rect;
973
974 polish();
975}
976
977void QQuickCanvasItem::checkAnimationCallbacks()
978{
979 if (d_func()->animationCallbacks.size() > 0 && isVisible())
980 polish();
981}
982
983/*!
984 \qmlmethod bool QtQuick::Canvas::save(string filename, size imageSize = undefined)
985
986 Saves the current canvas content into an image file \a filename.
987 The saved image format is automatically decided by the \a filename's suffix.
988 Returns \c true on success. If \a imageSize is specified, the resulting
989 image will have this size, and will have a devicePixelRatio of \c 1.0.
990 Otherwise, the \l {QQuickWindow::}{devicePixelRatio()} of the window in
991 which the canvas is displayed is applied to the saved image.
992
993 \note Calling this method will force painting the whole canvas, not just the
994 current canvas visible window.
995
996 \sa canvasWindow, canvasSize, toDataURL()
997*/
998bool QQuickCanvasItem::save(const QString &filename, const QSizeF &imageSize) const
999{
1000 Q_D(const QQuickCanvasItem);
1001 QString localFile = filename;
1002 if (QDir::isRelativePath(filename)) {
1003 QUrl url;
1004 url.setPath(filename); // `filename` may contain # or % characters
1005 localFile = d->baseUrl.resolved(url).toLocalFile();
1006 }
1007 return toImage(QRectF(QPointF(0, 0), imageSize)).save(localFile);
1008}
1009
1011{
1012 Q_D(QQuickCanvasItem);
1013 QUrl fullPathUrl = d->baseUrl.resolved(url);
1014 if (!d->pixmaps.contains(fullPathUrl)) {
1015 loadImage(url, sourceSize);
1016 }
1017 return d->pixmaps.value(fullPathUrl);
1018}
1019
1020/*!
1021 \qmlsignal QtQuick::Canvas::imageLoaded()
1022
1023 This signal is emitted when an image has been loaded.
1024
1025 \sa loadImage()
1026*/
1027
1028/*!
1029 \qmlmethod void QtQuick::Canvas::loadImage(url image, size sourceSize = undefined)
1030
1031 Loads the given \a image asynchronously.
1032
1033 Once the image is ready, imageLoaded() signal will be emitted.
1034 The loaded image can be unloaded with the unloadImage() method.
1035
1036 \note Only loaded images can be painted on the Canvas item.
1037
1038 If \a sourceSize is specified, the image will be scaled to that size during loading. This is
1039 useful for loading scalable (vector) images (eg. SVGs) at their intended display size. This
1040 parameter was introduced in Qt 6.7.
1041
1042 \sa unloadImage(), imageLoaded(), isImageLoaded(),
1043 Context2D::createImageData(), Context2D::drawImage()
1044*/
1045void QQuickCanvasItem::loadImage(const QUrl& url, QSizeF sourceSize)
1046{
1047 Q_D(QQuickCanvasItem);
1048 QUrl fullPathUrl = d->baseUrl.resolved(url);
1049 if (!d->pixmaps.contains(fullPathUrl)) {
1050 QQuickPixmap* pix = new QQuickPixmap();
1051 QQmlRefPointer<QQuickCanvasPixmap> canvasPix;
1052 canvasPix.adopt(new QQuickCanvasPixmap(pix));
1053 d->pixmaps.insert(fullPathUrl, canvasPix);
1054
1055 pix->load(qmlEngine(this)
1056 , fullPathUrl
1057 , QRect()
1058 , sourceSize.toSize()
1059 , QQuickPixmap::Cache | QQuickPixmap::Asynchronous);
1060 if (pix->isLoading())
1061 pix->connectFinished(this, SIGNAL(imageLoaded()));
1062 }
1063}
1064/*!
1065 \qmlmethod void QtQuick::Canvas::unloadImage(url image)
1066
1067 Unloads the \a image.
1068
1069 Once an image is unloaded, it cannot be painted by the canvas context
1070 unless it is loaded again.
1071
1072 \sa loadImage(), imageLoaded(), isImageLoaded(),
1073 Context2D::createImageData(), Context2D::drawImage
1074*/
1075void QQuickCanvasItem::unloadImage(const QUrl& url)
1076{
1077 Q_D(QQuickCanvasItem);
1078 d->pixmaps.remove(d->baseUrl.resolved(url));
1079}
1080
1081/*!
1082 \qmlmethod bool QtQuick::Canvas::isImageError(url image)
1083
1084 Returns \c true if the \a image failed to load, \c false otherwise.
1085
1086 \sa loadImage()
1087*/
1088bool QQuickCanvasItem::isImageError(const QUrl& url) const
1089{
1090 Q_D(const QQuickCanvasItem);
1091 QUrl fullPathUrl = d->baseUrl.resolved(url);
1092 return d->pixmaps.contains(fullPathUrl)
1093 && d->pixmaps.value(fullPathUrl)->pixmap()->isError();
1094}
1095
1096/*!
1097 \qmlmethod bool QtQuick::Canvas::isImageLoading(url image)
1098 Returns \c true if the \a image is currently loading.
1099
1100 \sa loadImage()
1101*/
1102bool QQuickCanvasItem::isImageLoading(const QUrl& url) const
1103{
1104 Q_D(const QQuickCanvasItem);
1105 QUrl fullPathUrl = d->baseUrl.resolved(url);
1106 return d->pixmaps.contains(fullPathUrl)
1107 && d->pixmaps.value(fullPathUrl)->pixmap()->isLoading();
1108}
1109/*!
1110 \qmlmethod bool QtQuick::Canvas::isImageLoaded(url image)
1111 Returns \c true if the \a image is successfully loaded and ready to use.
1112
1113 \sa loadImage()
1114*/
1115bool QQuickCanvasItem::isImageLoaded(const QUrl& url) const
1116{
1117 Q_D(const QQuickCanvasItem);
1118 QUrl fullPathUrl = d->baseUrl.resolved(url);
1119 return d->pixmaps.contains(fullPathUrl)
1120 && d->pixmaps.value(fullPathUrl)->pixmap()->isReady();
1121}
1122
1123/*!
1124 \internal
1125 Returns a QImage representing the requested \a rect which is in device independent pixels of the item.
1126 If \a rect is empty, then it will use the whole item's rect by default.
1127*/
1128
1129QImage QQuickCanvasItem::toImage(const QRectF& rect) const
1130{
1131 Q_D(const QQuickCanvasItem);
1132
1133 if (!d->context)
1134 return QImage();
1135
1136 const QRectF &rectSource = rect.isEmpty() ? canvasWindow() : rect;
1137 const qreal dpr = window() && rect.isEmpty() ? window()->effectiveDevicePixelRatio() : qreal(1);
1138 const QRectF rectScaled(rectSource.topLeft() * dpr, rectSource.size() * dpr);
1139
1140 QImage image = d->context->toImage(rectScaled);
1141 image.setDevicePixelRatio(dpr);
1142 return image;
1143}
1144
1145static const char* mimeToType(const QString &mime)
1146{
1147 const QLatin1String imagePrefix("image/");
1148 if (!mime.startsWith(imagePrefix))
1149 return nullptr;
1150 const QStringView mimeExt = QStringView{mime}.mid(imagePrefix.size());
1151 if (mimeExt == QLatin1String("png"))
1152 return "png";
1153 else if (mimeExt == QLatin1String("bmp"))
1154 return "bmp";
1155 else if (mimeExt == QLatin1String("jpeg"))
1156 return "jpeg";
1157 else if (mimeExt == QLatin1String("x-portable-pixmap"))
1158 return "ppm";
1159 else if (mimeExt == QLatin1String("tiff"))
1160 return "tiff";
1161 else if (mimeExt == QLatin1String("xpm"))
1162 return "xpm";
1163 return nullptr;
1164}
1165
1166/*!
1167 \qmlmethod string QtQuick::Canvas::toDataURL(string mimeType)
1168
1169 Returns a data URL for the image in the canvas.
1170
1171 The default \a mimeType is "image/png".
1172
1173 \sa save()
1174*/
1175QString QQuickCanvasItem::toDataURL(const QString& mimeType) const
1176{
1177 QImage image = toImage();
1178
1179 if (!image.isNull()) {
1180 QByteArray ba;
1181 QBuffer buffer(&ba);
1182 buffer.open(QIODevice::WriteOnly);
1183 const QString mime = mimeType.toLower();
1184 const char* type = mimeToType(mime);
1185 if (!type)
1186 return QStringLiteral("data:,");
1187
1188 image.save(&buffer, type);
1189 buffer.close();
1190 return QLatin1String("data:") + mime + QLatin1String(";base64,") + QLatin1String(ba.toBase64().constData());
1191 }
1192 return QStringLiteral("data:,");
1193}
1194
1195void QQuickCanvasItem::delayedCreate()
1196{
1197 Q_D(QQuickCanvasItem);
1198
1199 if (!d->context && !d->contextType.isNull())
1200 createContext(d->contextType);
1201
1202 requestPaint();
1203}
1204
1205bool QQuickCanvasItem::createContext(const QString &contextType)
1206{
1207 Q_D(QQuickCanvasItem);
1208
1209 if (!window())
1210 return false;
1211
1212 if (contextType == QLatin1String("2d")) {
1213 if (d->contextType.compare(QLatin1String("2d"), Qt::CaseInsensitive) != 0) {
1214 d->contextType = QLatin1String("2d");
1215 emit contextTypeChanged(); // XXX: can't be in setContextType()
1216 }
1217 initializeContext(new QQuickContext2D(this));
1218 return true;
1219 }
1220
1221 return false;
1222}
1223
1224void QQuickCanvasItem::initializeContext(QQuickCanvasContext *context, const QVariantMap &args)
1225{
1226 Q_D(QQuickCanvasItem);
1227
1228 d->context = context;
1229 d->context->init(this, args);
1230 d->context->setV4Engine(qmlEngine(this)->handle());
1231 connect(d->context, SIGNAL(textureChanged()), SLOT(update()));
1232 connect(d->context, SIGNAL(textureChanged()), SIGNAL(painted()));
1233 emit contextChanged();
1234}
1235
1236QRect QQuickCanvasItem::tiledRect(const QRectF &window, const QSize &tileSize)
1237{
1238 if (window.isEmpty())
1239 return QRect();
1240
1241 const int tw = tileSize.width();
1242 const int th = tileSize.height();
1243 const int h1 = window.left() / tw;
1244 const int v1 = window.top() / th;
1245
1246 const int htiles = ((window.right() - h1 * tw) + tw - 1)/tw;
1247 const int vtiles = ((window.bottom() - v1 * th) + th - 1)/th;
1248
1249 return QRect(h1 * tw, v1 * th, htiles * tw, vtiles * th);
1250}
1251
1252/*!
1253 \qmlsignal QtQuick::Canvas::paint(rect region)
1254
1255 This signal is emitted when the \a region needs to be rendered. If a context
1256 is active it can be referenced from the context property.
1257
1258 This signal can be triggered by markDirty(), requestPaint() or by changing
1259 the current canvas window.
1260*/
1261
1262/*!
1263 \qmlsignal QtQuick::Canvas::painted()
1264
1265 This signal is emitted after all context painting commands are executed and
1266 the Canvas has been rendered.
1267*/
1268
1269QT_END_NAMESPACE
1270
1271#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 &)
void setRenderStrategy(RenderStrategy strategy)
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)