1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQuick module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qquickpainteditem.h"
41 #include <private/qquickpainteditem_p.h>
42 
43 #include <QtQuick/private/qsgdefaultpainternode_p.h>
44 #include <QtQuick/private/qsgcontext_p.h>
45 #include <private/qsgadaptationlayer_p.h>
46 #include <qsgtextureprovider.h>
47 
48 #include <qmath.h>
49 
50 QT_BEGIN_NAMESPACE
51 
52 class QQuickPaintedItemTextureProvider : public QSGTextureProvider
53 {
54 public:
55     QSGPainterNode *node;
texture() const56     QSGTexture *texture() const override { return node ? node->texture() : nullptr; }
fireTextureChanged()57     void fireTextureChanged() { emit textureChanged(); }
58 };
59 
60 /*!
61     \class QQuickPaintedItem
62     \brief The QQuickPaintedItem class provides a way to use the QPainter API in the
63     QML Scene Graph.
64 
65     \inmodule QtQuick
66 
67     The QQuickPaintedItem makes it possible to use the QPainter API with the
68     QML Scene Graph. It sets up a textured rectangle in the Scene Graph and
69     uses a QPainter to paint onto the texture. The render target can be either
70     a QImage or, when OpenGL is in use, a QOpenGLFramebufferObject. When the
71     render target is a QImage, QPainter first renders into the image then the
72     content is uploaded to the texture. When a QOpenGLFramebufferObject is
73     used, QPainter paints directly onto the texture. Call update() to trigger a
74     repaint.
75 
76     To enable QPainter to do anti-aliased rendering, use setAntialiasing().
77 
78     To write your own painted item, you first create a subclass of
79     QQuickPaintedItem, and then start by implementing its only pure virtual
80     public function: paint(), which implements the actual painting. The
81     painting will be inside the rectangle spanning from 0,0 to
82     width(),height().
83 
84     \note It important to understand the performance implications such items
85     can incur. See QQuickPaintedItem::RenderTarget and
86     QQuickPaintedItem::renderTarget.
87 */
88 
89 /*!
90     \enum QQuickPaintedItem::RenderTarget
91 
92     This enum describes QQuickPaintedItem's render targets. The render target is the
93     surface QPainter paints onto before the item is rendered on screen.
94 
95     \value Image The default; QPainter paints into a QImage using the raster paint engine.
96     The image's content needs to be uploaded to graphics memory afterward, this operation
97     can potentially be slow if the item is large. This render target allows high quality
98     anti-aliasing and fast item resizing.
99 
100     \value FramebufferObject QPainter paints into a QOpenGLFramebufferObject using the GL
101     paint engine. Painting can be faster as no texture upload is required, but anti-aliasing
102     quality is not as good as if using an image. This render target allows faster rendering
103     in some cases, but you should avoid using it if the item is resized often.
104 
105     \value InvertedYFramebufferObject Exactly as for FramebufferObject above, except once
106     the painting is done, prior to rendering the painted image is flipped about the
107     x-axis so that the top-most pixels are now at the bottom.  Since this is done with the
108     OpenGL texture coordinates it is a much faster way to achieve this effect than using a
109     painter transform.
110 
111     \sa setRenderTarget()
112 */
113 
114 /*!
115     \enum QQuickPaintedItem::PerformanceHint
116 
117     This enum describes flags that you can enable to improve rendering
118     performance in QQuickPaintedItem. By default, none of these flags are set.
119 
120     \value FastFBOResizing Resizing an FBO can be a costly operation on a few
121     OpenGL driver implementations. To work around this, one can set this flag
122     to let the QQuickPaintedItem allocate one large framebuffer object and
123     instead draw into a subregion of it. This saves the resize at the cost of
124     using more memory. Please note that this is not a common problem.
125 
126 */
127 
128 /*!
129     \internal
130 */
QQuickPaintedItemPrivate()131 QQuickPaintedItemPrivate::QQuickPaintedItemPrivate()
132     : QQuickItemPrivate()
133     , contentsScale(1.0)
134     , fillColor(Qt::transparent)
135     , renderTarget(QQuickPaintedItem::Image)
136     , opaquePainting(false)
137     , antialiasing(false)
138     , mipmap(false)
139     , textureProvider(nullptr)
140     , node(nullptr)
141 {
142 }
143 
144 /*!
145     Constructs a QQuickPaintedItem with the given \a parent item.
146  */
QQuickPaintedItem(QQuickItem * parent)147 QQuickPaintedItem::QQuickPaintedItem(QQuickItem *parent)
148     : QQuickItem(*(new QQuickPaintedItemPrivate), parent)
149 {
150     setFlag(ItemHasContents);
151 }
152 
153 /*!
154     \internal
155 */
QQuickPaintedItem(QQuickPaintedItemPrivate & dd,QQuickItem * parent)156 QQuickPaintedItem::QQuickPaintedItem(QQuickPaintedItemPrivate &dd, QQuickItem *parent)
157     : QQuickItem(dd, parent)
158 {
159     setFlag(ItemHasContents);
160 }
161 
162 /*!
163     Destroys the QQuickPaintedItem.
164 */
~QQuickPaintedItem()165 QQuickPaintedItem::~QQuickPaintedItem()
166 {
167     Q_D(QQuickPaintedItem);
168     if (d->textureProvider)
169         QQuickWindowQObjectCleanupJob::schedule(window(), d->textureProvider);
170 }
171 
172 /*!
173     Schedules a redraw of the area covered by \a rect in this item. You can call this function
174     whenever your item needs to be redrawn, such as if it changes appearance or size.
175 
176     This function does not cause an immediate paint; instead it schedules a paint request that
177     is processed by the QML Scene Graph when the next frame is rendered. The item will only be
178     redrawn if it is visible.
179 
180     \sa paint()
181 */
update(const QRect & rect)182 void QQuickPaintedItem::update(const QRect &rect)
183 {
184     Q_D(QQuickPaintedItem);
185 
186     if (rect.isNull() && !d->dirtyRect.isNull())
187         d->dirtyRect = contentsBoundingRect().toAlignedRect();
188     else
189         d->dirtyRect |= (contentsBoundingRect() & rect).toAlignedRect();
190     QQuickItem::update();
191 }
192 
193 /*!
194     Returns true if this item is opaque; otherwise, false is returned.
195 
196     By default, painted items are not opaque.
197 
198     \sa setOpaquePainting()
199 */
opaquePainting() const200 bool QQuickPaintedItem::opaquePainting() const
201 {
202     Q_D(const QQuickPaintedItem);
203     return d->opaquePainting;
204 }
205 
206 /*!
207     If \a opaque is true, the item is opaque; otherwise, it is considered as translucent.
208 
209     Opaque items are not blended with the rest of the scene, you should set this to true
210     if the content of the item is opaque to speed up rendering.
211 
212     By default, painted items are not opaque.
213 
214     \sa opaquePainting()
215 */
setOpaquePainting(bool opaque)216 void QQuickPaintedItem::setOpaquePainting(bool opaque)
217 {
218     Q_D(QQuickPaintedItem);
219 
220     if (d->opaquePainting == opaque)
221         return;
222 
223     d->opaquePainting = opaque;
224     QQuickItem::update();
225 }
226 
227 /*!
228     Returns true if antialiased painting is enabled; otherwise, false is returned.
229 
230     By default, antialiasing is not enabled.
231 
232     \sa setAntialiasing()
233 */
antialiasing() const234 bool QQuickPaintedItem::antialiasing() const
235 {
236     Q_D(const QQuickPaintedItem);
237     return d->antialiasing;
238 }
239 
240 /*!
241     If \a enable is true, antialiased painting is enabled.
242 
243     By default, antialiasing is not enabled.
244 
245     \sa antialiasing()
246 */
setAntialiasing(bool enable)247 void QQuickPaintedItem::setAntialiasing(bool enable)
248 {
249     Q_D(QQuickPaintedItem);
250 
251     if (d->antialiasing == enable)
252         return;
253 
254     d->antialiasing = enable;
255     update();
256 }
257 
258 /*!
259     Returns true if mipmaps are enabled; otherwise, false is returned.
260 
261     By default, mipmapping is not enabled.
262 
263     \sa setMipmap()
264 */
mipmap() const265 bool QQuickPaintedItem::mipmap() const
266 {
267     Q_D(const QQuickPaintedItem);
268     return d->mipmap;
269 }
270 
271 /*!
272     If \a enable is true, mipmapping is enabled on the associated texture.
273 
274     Mipmapping increases rendering speed and reduces aliasing artifacts when the item is
275     scaled down.
276 
277     By default, mipmapping is not enabled.
278 
279     \sa mipmap()
280 */
setMipmap(bool enable)281 void QQuickPaintedItem::setMipmap(bool enable)
282 {
283     Q_D(QQuickPaintedItem);
284 
285     if (d->mipmap == enable)
286         return;
287 
288     d->mipmap = enable;
289     update();
290 }
291 
292 /*!
293     Returns the performance hints.
294 
295     By default, no performance hint is enabled.
296 
297     \sa setPerformanceHint(), setPerformanceHints()
298 */
performanceHints() const299 QQuickPaintedItem::PerformanceHints QQuickPaintedItem::performanceHints() const
300 {
301     Q_D(const QQuickPaintedItem);
302     return d->performanceHints;
303 }
304 
305 /*!
306     Sets the given performance \a hint on the item if \a enabled is true;
307     otherwise clears the performance hint.
308 
309     By default, no performance hint is enabled/
310 
311     \sa setPerformanceHints(), performanceHints()
312 */
setPerformanceHint(QQuickPaintedItem::PerformanceHint hint,bool enabled)313 void QQuickPaintedItem::setPerformanceHint(QQuickPaintedItem::PerformanceHint hint, bool enabled)
314 {
315     Q_D(QQuickPaintedItem);
316     PerformanceHints oldHints = d->performanceHints;
317     if (enabled)
318         d->performanceHints |= hint;
319     else
320         d->performanceHints &= ~hint;
321     if (oldHints != d->performanceHints)
322        update();
323 }
324 
325 /*!
326     Sets the performance hints to \a hints
327 
328     By default, no performance hint is enabled/
329 
330     \sa setPerformanceHint(), performanceHints()
331 */
setPerformanceHints(QQuickPaintedItem::PerformanceHints hints)332 void QQuickPaintedItem::setPerformanceHints(QQuickPaintedItem::PerformanceHints hints)
333 {
334     Q_D(QQuickPaintedItem);
335     if (d->performanceHints == hints)
336         return;
337     d->performanceHints = hints;
338     update();
339 }
340 
textureSize() const341 QSize QQuickPaintedItem::textureSize() const
342 {
343     Q_D(const QQuickPaintedItem);
344     return d->textureSize;
345 }
346 
347 /*!
348     \property QQuickPaintedItem::textureSize
349 
350     \brief Defines the size of the texture.
351 
352     Changing the texture's size does not affect the coordinate system used in
353     paint(). A scale factor is instead applied so painting should still happen
354     inside 0,0 to width(),height().
355 
356     By default, the texture size will have the same size as this item.
357 
358     \note If the item is on a window with a device pixel ratio different from
359     1, this scale factor will be implicitly applied to the texture size.
360 
361  */
setTextureSize(const QSize & size)362 void QQuickPaintedItem::setTextureSize(const QSize &size)
363 {
364     Q_D(QQuickPaintedItem);
365     if (d->textureSize == size)
366         return;
367     d->textureSize = size;
368     emit textureSizeChanged();
369 }
370 
371 #if QT_VERSION >= 0x060000
372 #warning "Remove: QQuickPaintedItem::contentsBoundingRect, contentsScale, contentsSize. Also remove them from qsgadaptationlayer_p.h and qsgdefaultpainternode.h/cpp."
373 #endif
374 
375 /*!
376     \obsolete
377 
378     This function is provided for compatibility, use size in combination
379     with textureSize to decide the size of what you are drawing.
380 
381     \sa width(), height(), textureSize()
382 */
contentsBoundingRect() const383 QRectF QQuickPaintedItem::contentsBoundingRect() const
384 {
385     Q_D(const QQuickPaintedItem);
386 
387     qreal w = d->width;
388     QSizeF sz = d->contentsSize * d->contentsScale;
389     if (w < sz.width())
390         w = sz.width();
391     qreal h = d->height;
392     if (h < sz.height())
393         h = sz.height();
394 
395     return QRectF(0, 0, w, h);
396 }
397 
398 /*!
399     \property QQuickPaintedItem::contentsSize
400     \brief Obsolete method for setting the contents size.
401     \obsolete
402 
403     This function is provided for compatibility, use size in combination
404     with textureSize to decide the size of what you are drawing.
405 
406     \sa width(), height(), textureSize()
407 */
contentsSize() const408 QSize QQuickPaintedItem::contentsSize() const
409 {
410     Q_D(const QQuickPaintedItem);
411     return d->contentsSize;
412 }
413 
setContentsSize(const QSize & size)414 void QQuickPaintedItem::setContentsSize(const QSize &size)
415 {
416     Q_D(QQuickPaintedItem);
417 
418     if (d->contentsSize == size)
419         return;
420 
421     d->contentsSize = size;
422     update();
423 
424     emit contentsSizeChanged();
425 }
426 
427 /*!
428     \obsolete
429     This convenience function is equivalent to calling setContentsSize(QSize()).
430 */
resetContentsSize()431 void QQuickPaintedItem::resetContentsSize()
432 {
433     setContentsSize(QSize());
434 }
435 
436 /*!
437     \property QQuickPaintedItem::contentsScale
438     \brief Obsolete method for scaling the contents.
439     \obsolete
440 
441     This function is provided for compatibility, use size() in combination
442     with textureSize() to decide the size of what you are drawing.
443 
444     \sa width(), height(), textureSize()
445 */
contentsScale() const446 qreal QQuickPaintedItem::contentsScale() const
447 {
448     Q_D(const QQuickPaintedItem);
449     return d->contentsScale;
450 }
451 
setContentsScale(qreal scale)452 void QQuickPaintedItem::setContentsScale(qreal scale)
453 {
454     Q_D(QQuickPaintedItem);
455 
456     if (d->contentsScale == scale)
457         return;
458 
459     d->contentsScale = scale;
460     update();
461 
462     emit contentsScaleChanged();
463 }
464 
465 /*!
466     \property QQuickPaintedItem::fillColor
467     \brief The item's background fill color.
468 
469     By default, the fill color is set to Qt::transparent.
470 */
fillColor() const471 QColor QQuickPaintedItem::fillColor() const
472 {
473     Q_D(const QQuickPaintedItem);
474     return d->fillColor;
475 }
476 
setFillColor(const QColor & c)477 void QQuickPaintedItem::setFillColor(const QColor &c)
478 {
479     Q_D(QQuickPaintedItem);
480 
481     if (d->fillColor == c)
482         return;
483 
484     d->fillColor = c;
485     update();
486 
487     emit fillColorChanged();
488 }
489 
490 /*!
491     \property QQuickPaintedItem::renderTarget
492     \brief The item's render target.
493 
494     This property defines which render target the QPainter renders into, it can be either
495     QQuickPaintedItem::Image, QQuickPaintedItem::FramebufferObject or QQuickPaintedItem::InvertedYFramebufferObject.
496 
497     Each has certain benefits, typically performance versus quality. Using a framebuffer
498     object avoids a costly upload of the image contents to the texture in graphics memory,
499     while using an image enables high quality anti-aliasing.
500 
501     \warning Resizing a framebuffer object is a costly operation, avoid using
502     the QQuickPaintedItem::FramebufferObject render target if the item gets resized often.
503 
504     By default, the render target is QQuickPaintedItem::Image.
505 
506     \note Some Qt Quick backends may not support all render target options. For
507     example, it is likely that non-OpenGL backends will lack support for
508     QQuickPaintedItem::FramebufferObject and
509     QQuickPaintedItem::InvertedYFramebufferObject. Requesting these will then
510     be ignored.
511 */
renderTarget() const512 QQuickPaintedItem::RenderTarget QQuickPaintedItem::renderTarget() const
513 {
514     Q_D(const QQuickPaintedItem);
515     return d->renderTarget;
516 }
517 
setRenderTarget(RenderTarget target)518 void QQuickPaintedItem::setRenderTarget(RenderTarget target)
519 {
520     Q_D(QQuickPaintedItem);
521 
522     if (d->renderTarget == target)
523         return;
524 
525     d->renderTarget = target;
526     update();
527 
528     emit renderTargetChanged();
529 }
530 
531 /*!
532     \fn virtual void QQuickPaintedItem::paint(QPainter *painter) = 0
533 
534     This function, which is usually called by the QML Scene Graph, paints the
535     contents of an item in local coordinates.
536 
537     The underlying texture will have a size defined by textureSize when set,
538     or the item's size, multiplied by the window's device pixel ratio.
539 
540     The function is called after the item has been filled with the fillColor.
541 
542     Reimplement this function in a QQuickPaintedItem subclass to provide the
543     item's painting implementation, using \a painter.
544 
545     \note The QML Scene Graph uses two separate threads, the main thread does things such as
546     processing events or updating animations while a second thread does the actual OpenGL rendering.
547     As a consequence, paint() is not called from the main GUI thread but from the GL enabled
548     renderer thread. At the moment paint() is called, the GUI thread is blocked and this is
549     therefore thread-safe.
550 
551     \warning Extreme caution must be used when creating QObjects, emitting signals, starting
552     timers and similar inside this function as these will have affinity to the rendering thread.
553 
554     \sa width(), height(), textureSize
555 */
556 
557 /*!
558     \reimp
559 */
updatePaintNode(QSGNode * oldNode,UpdatePaintNodeData * data)560 QSGNode *QQuickPaintedItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
561 {
562     Q_UNUSED(data);
563     Q_D(QQuickPaintedItem);
564 
565     if (width() <= 0 || height() <= 0) {
566         delete oldNode;
567         if (d->textureProvider) {
568             d->textureProvider->node = nullptr;
569             d->textureProvider->fireTextureChanged();
570         }
571         return nullptr;
572     }
573 
574     QSGPainterNode *node = static_cast<QSGPainterNode *>(oldNode);
575     if (!node) {
576         node = d->sceneGraphContext()->createPainterNode(this);
577         d->node = node;
578     }
579 
580     bool hasTextureSize = d->textureSize.width() > 0 && d->textureSize.height() > 0;
581 
582     // Use the compat mode if any of the compat things are set and
583     // textureSize is 0x0.
584     if (!hasTextureSize
585         && (d->contentsScale != 1
586             || (d->contentsSize.width() > 0 && d->contentsSize.height() > 0))) {
587         QRectF br = contentsBoundingRect();
588         node->setContentsScale(d->contentsScale);
589         QSize size = QSize(qRound(br.width()), qRound(br.height()));
590         node->setSize(size);
591         node->setTextureSize(size);
592     } else {
593         // The default, use textureSize or item's size * device pixel ratio
594         node->setContentsScale(1);
595         QSize itemSize(qRound(width()), qRound(height()));
596         node->setSize(itemSize);
597         QSize textureSize = (hasTextureSize ? d->textureSize : itemSize)
598                             * window()->effectiveDevicePixelRatio();
599         node->setTextureSize(textureSize);
600     }
601 
602     node->setPreferredRenderTarget(d->renderTarget);
603     node->setFastFBOResizing(d->performanceHints & FastFBOResizing);
604     node->setSmoothPainting(d->antialiasing);
605     node->setLinearFiltering(d->smooth);
606     node->setMipmapping(d->mipmap);
607     node->setOpaquePainting(d->opaquePainting);
608     node->setFillColor(d->fillColor);
609     node->setDirty(d->dirtyRect);
610     node->update();
611 
612     d->dirtyRect = QRect();
613 
614     if (d->textureProvider) {
615         d->textureProvider->node = node;
616         d->textureProvider->fireTextureChanged();
617     }
618 
619     return node;
620 }
621 
622 /*!
623    \reimp
624 */
releaseResources()625 void QQuickPaintedItem::releaseResources()
626 {
627     Q_D(QQuickPaintedItem);
628     if (d->textureProvider) {
629         QQuickWindowQObjectCleanupJob::schedule(window(), d->textureProvider);
630         d->textureProvider = nullptr;
631     }
632     d->node = nullptr; // Managed by the scene graph, just clear the pointer.
633 }
634 
invalidateSceneGraph()635 void QQuickPaintedItem::invalidateSceneGraph()
636 {
637     Q_D(QQuickPaintedItem);
638     delete d->textureProvider;
639     d->textureProvider = nullptr;
640     d->node = nullptr; // Managed by the scene graph, just clear the pointer
641 }
642 
643 /*!
644    \reimp
645 */
isTextureProvider() const646 bool QQuickPaintedItem::isTextureProvider() const
647 {
648     return true;
649 }
650 
651 /*!
652    \reimp
653 */
textureProvider() const654 QSGTextureProvider *QQuickPaintedItem::textureProvider() const
655 {
656     // When Item::layer::enabled == true, QQuickItem will be a texture
657     // provider. In this case we should prefer to return the layer rather
658     // than the image itself. The layer will include any children and any
659     // the image's wrap and fill mode.
660     if (QQuickItem::isTextureProvider())
661         return QQuickItem::textureProvider();
662 
663     Q_D(const QQuickPaintedItem);
664 #if QT_CONFIG(opengl)
665     QQuickWindow *w = window();
666     if (!w || !w->openglContext() || QThread::currentThread() != w->openglContext()->thread()) {
667         qWarning("QQuickPaintedItem::textureProvider: can only be queried on the rendering thread of an exposed window");
668         return nullptr;
669     }
670 #endif
671     if (!d->textureProvider)
672         d->textureProvider = new QQuickPaintedItemTextureProvider();
673     d->textureProvider->node = d->node;
674     return d->textureProvider;
675 }
676 
677 
678 /*!
679    \reimp
680 */
itemChange(ItemChange change,const ItemChangeData & value)681 void QQuickPaintedItem::itemChange(ItemChange change, const ItemChangeData &value)
682 {
683     if (change == ItemDevicePixelRatioHasChanged)
684         update();
685     QQuickItem::itemChange(change, value);
686 }
687 
688 QT_END_NAMESPACE
689 
690 #include "moc_qquickpainteditem.cpp"
691