QML type to draw with QPainter - c++

In QML documentation I found an example of custom type (defined from C++) to draw on it with QPainter:
Header:
#include <QtQuick/QQuickPaintedItem>
class PieChart : public QQuickPaintedItem
{
...
public:
void paint(QPainter *painter);
...
};
Source:
void PieChart::paint(QPainter *painter)
{
QPen pen(m_color, 2);
painter->setPen(pen);
painter->setRenderHints(QPainter::Antialiasing, true);
painter->drawPie(boundingRect().adjusted(1, 1, -1, -1), 90 * 16, 290 * 16);
}
How can I derive a type to draw (e.g. a line) on it asynchronously with QPainter?
Thanks!

You have multiple ways do draw asynchronously:
1) Draw your content into a QImage at some point (maybe even in a seperate thread), and in QQuickPaintedItem::paint(), simply draw that image.
2) Use the QtQuick Canvas. Note that this is drawing in JavaScript, not in C++, but under the hood it is actually QPainter commands. The Canvas supports various render strategies, among others doing the drawing in a dedicated thread or in the render thread

Related

How to draw svg graphics with painter class

I'm trying to learn how to use the drawing functions. Till now i have been able to use the QPainter class to draw some circles on a QWidget like this:
in MainWindow.h i added:
virtual void paintEvents(QPaintEvent *event);
Then in MainWindow.cpp i added:
void MainWindow::paintEvents(QPaintEvent *event) {
QPainter painter(this);
painter.drawEllipse( 305, 55, 475, 475 );
painter.drawEllipse( 320, 70, 445, 445 );
painter.end();
}
This draws me some circles in pixels, but i need svg. So how can i use the QPainter class to draw svg circles instead?
You can use Qt SVG module. If you are using qmake, add QT += svg to your .pro file and then you will be able to use SVG classes.
Then, you can utilize QSvgRenderer to draw svg documents with your QPainter instance like:
#include <QSvgRenderer>
...
void MainWindow::paintEvents(QPaintEvent *event) {
QPainter painter(this);
QSvgRenderer svgr("/path/to/img.svg");
svgr.render(&painter);
painter.end();
}
As per documentation render method has two other overloads which will give you control over where and what to render.
You may also want to load your svg content from QByteArray or XML stream which have their appropriate constructors or load methods.

QPixmap paint performance issue

I need to recreate the Windows 7 theme where the application window header is transparent and displays blurred screen contents. My idea was to capture the screen contents and display them blurred in the header. For that reason I extended QQuickPaintedItem.
Here is the header:
class DesktopImage : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(int desktopX READ desktopX WRITE setDesktopX NOTIFY desktopXChanged)
Q_PROPERTY(int desktopY READ desktopY WRITE setDesktopY NOTIFY desktopYChanged)
public:
explicit DesktopImage(QQuickItem *parent = nullptr);
void paint(QPainter *painter) override;
int desktopX() const;
void setDesktopX(int desktopX);
int desktopY() const;
void setDesktopY(int desktopY);
signals:
void desktopXChanged();
void desktopYChanged();
private:
void grabScreensContent();
private:
QPixmap mScreensContent;
int mDesktopX;
int mDesktopY;
};
the grabScreensContent() method do as the name suggest. The paint() method is implemented as follows:
void DesktopImage::paint(QPainter *painter)
{
QRectF target(0, 0, width(), height());
QRectF source(mDesktopX, mDesktopY, width(), height());
painter->drawPixmap(target, mScreensContent, source);
}
on the QML side, I use the type as follows:
DesktopContent {
id: desktop
desktopX: window.x
desktopY: window.y
width: parent.width
height: parent.height
}
as you can see the desktopX (desktopY) properties are bound to the window x (window y) properties so that when the user moves the window the portion of the background that needs to be drawn is properly fetched. However the drawing is not as fluid as one might expect. Here is the result:
Can someone suggest an performance improvement?
Take care of renderTarget by setting it to FramebufferObject. This basically should make it as efficient as normal QML rendering yet you can use QPainter which is sometimes conveninent.
DesktopImage::DesktopImage(QQuickItem *parent)
{
// this setting is not default
this->setRenderTarget(QQuickPaintedItem::FramebufferObject);
}
Also, only operate with the limited screen area if you don't need the whole screen. This may or may not help depending on the platform / implementation but I always limit the scope on the target first and then position the source within it. The low level painting is not necessarily very intelligent and may expect many changes across the whole area. So we should rather specify the minimum target area and just alter it (this case works like that).
void DesktopImage::paint(QPainter *painter)
{
QRectF target(mDesktopX, mDesktopY, width(), height()); // now limited
QRectF source(0, 0, width(), height()); // now within smaller target
painter->drawPixmap(target, mScreensContent, source);
}

How to create custom 3D looking button in Qt?

I'm trying to create the following custom button:
To do this I've create the class and overrided paintEvent:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QPen pen(Qt::darkGray, 7, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
painter.setPen(pen);
painter.drawEllipse(QPointF(width()/2, height()/2), width()/2.1,height()/2.1);
QPen pen2(Qt::lightGray, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
painter.setPen(pen2);
painter.drawEllipse(QPointF(width()/2, height()/2), width()/2.15, height()/2.15);
QPen pen1(Qt::gray, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
painter.setPen(pen1);
painter.drawEllipse(QPointF(width()/2, height()/2), width()/3.5, height()/3.5);
}
But, I'm not able to create the button like below with appropriate gradients and picture in the center.
Can you please help me?
The key thing here is to make the gradients. And you cannot make gradients on strokes, only on fills. Meaning that you will have to implement the outline as a fill.
There is the button's components digested, listed in the order you should draw them:
Mind you that only the first component is a solid color, everything else is gradients. It is those gradients which get the effect.
In order to get gradients for the inner ring you will have to use QPainterPath, fist add the outer circle, and then add a slightly smaller inner circle, which will effectively punch a hole in the first circle, giving you something that looks like an outline but is actually a fill, a fill you can use a gradient on.
As mentioned in the comments - this involves a lot of operations and is not ideal. It would be preferable to have such buttons as images instead of painting them with QPainter.
Update: here is some more help, to show you how to draw a gradient outline in order to achieve some 3D illusion:
class Test : public QWidget {
Q_OBJECT
public:
Test() { resize(200, 200); }
void paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.fillRect(rect(), Qt::gray);
QPainterPath p;
p.addEllipse(rect().center(), 50, 50);
p.addEllipse(rect().center(), 45, 45);
QLinearGradient g(QPointF(0, 50), QPointF(0, 150));
g.setColorAt(0, Qt::white);
g.setColorAt(1, Qt::black);
QBrush b(g);
painter.fillPath(p, b);
QPainterPath p2;
p2.addEllipse(rect().center(), 45, 45);
p2.addEllipse(rect().center(), 40, 40);
QLinearGradient g1(QPointF(0, 50), QPointF(0, 150));
g1.setColorAt(0, Qt::black);
g1.setColorAt(1, Qt::white);
QBrush b1(g1);
painter.fillPath(p2, b1);
QPainterPath p3;
p3.addEllipse(rect().center(), 40, 40);
painter.fillPath(p3, b);
}
};
And the result:
Considering the question reworded as:
How to use QPainter to generate the given picture?
Using QPainter:
Get a vectorial image of the button, ideally in SVG.
Look the SVG code to extract colors, gradients, sizes, etc.
Reproduce them with QPainter, you will see that for each SVG command, you have an nearly equal equivalent in QPainter. QPainterPath will help.
Using QPixmap:
Get an SVG image of your button
Use QSvgRenderer to generate a QImage of the given size, and then convert it to a QPixmap.
Paint the QPixmap when required.
Benefits of each options:
QPixmap is simpler, and allows you to switch the button aesthetic easily.
QSvgRenderer allows you to manage hover easily
QPainter allows you to override colors with those of the OS
QPainter give you more flexibility for effects, animations, etc.

QPaintedTextureImage in Qt3D (Qt 5.8)

I want to create an entity with Qt3D that has a custom image as texture. I came across the QPaintedTextureImage (link leads to Qt 5.9 version for details. Here ist doc for 5.8), which can be written with a QPainter but I don't understand how.
First, this is how I imagine the entity could look like:
[EDIT]: code is edited and works now!
planeEntity = new Qt3DCore::QEntity(rootEntity);
planeMesh = new Qt3DExtras::QPlaneMesh;
planeMesh->setWidth(2);
planeMesh->setHeight(2);
image = new TextureImage; //see below
image->setSize(QSize(100,100));
painter = new QPainter;
image->paint(painter)
planeMaterial = new Qt3DExtras::QDiffuseMapMaterial;
planeMaterial->diffuse()->addTextureImage(image);
planeEntity->addComponent(planeMesh);
planeEntity->addComponent(planeMaterial);
TextureImage is the subclassed QPaintedTextureImage with paint function:
class TextureImage : public Qt3DRender::QPaintedTextureImage
{
public:
void paint(QPainter* painter);
};
What does the QPainter, passed to paint function, need to do in the implementation of paint if I just want to draw a big circle to the planeEntity?
[Edit] Implementation:
void TextureImage::paint(QPainter* painter)
{
//hardcoded values because there was no device()->width/heigth
painter->fillRect(0, 0, 100, 100, QColor(255, 255, 255));
/* Set pen and brush to whatever you want. */
painter->setPen(QPen(QBrush(QColor(255, 0, 255)) ,10));
painter->setBrush(QColor(0, 0, 255));
/*
* Draw a circle (or an ellipse -- the outcome depends very much on
* the aspect ratio of the bounding rectangle amongst other things).
*/
painter->drawEllipse(0, 0, 100, 100);
}
The short answer is... use QPainter exactly the same way you would normally.
void TextureImage::paint (QPainter* painter)
{
int w = painter->device()->width();
int h = painter->device()->height();
/* Clear to white. */
painter->fillRect(0, 0, w, h, QColor(255, 255, 255));
/* Set pen and brush to whatever you want. */
painter->setPen(QPen(QBrush(QColor(0, 0, 0)) ,10));
painter->setBrush(QColor(0, 0, 255));
/*
* Draw a circle (or an ellipse -- the outcome depends very much on
* the aspect ratio of the bounding rectangle amongst other things).
*/
painter->drawEllipse(0, 0, w, h);
}
However, note that you really shouldn't invoke the paint method directly. Instead use update which will cause Qt to schedule a repaint, initialize a QPainter and invoke your overridden paint method with a pointer to that painter.
It might be simpler to dynamically load the image you need in QML.
I had to do it not so long ago and opened a question on SO for it:
Qt3D dynamic texture

Qt: Overlapping semitransparent QgraphicsItem

I've been working with QGraphicsView for a while and I am facing a requisite that I am not sure if it can be fulfilled by using this framework.
Putting it as simple as possible, I have 2 overlapping RectItem with a semitransparent QBrush (the same one for both). Is it possible to prevent the overlapping area from becoming more opaque? I just want the whole area to have the same color (this will occur only if both rects are fully opaque, but sometimes that is not the case)
I know it might seem a weird requisite, but the old graphics engine my colleagues used allowed it.
Any ideas?
Qt provides various blend (composition) modes for the QPainter. Deriving your RectItem class from QGraphicsItem or QGraphicsObject, allows you to customise the painting and using the composition modes, create various effects, as demonstrated in the Qt Example.
If you want two semi-transparent items overlapping without changing the colour (assuming their colour is the same), either the QPainter::CompositionMode_Difference mode, or CompositionMode_Exclusion will do this. Here's example code of such an object: -
Header
#ifndef RECTITEM_H
#define RECTITEM_H
#include <QGraphicsItem>
#include <QColor>
class RectItem : public QGraphicsItem
{
public:
RectItem(int width, int height, QColor colour);
~RectItem();
QRectF boundingRect() const;
private:
QRectF m_boundingRect;
QColor m_colour;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
};
#endif // RECTITEM_H
Implementation
#include "rectitem.h"
#include <QPainter>
RectItem::RectItem(int width, int height, QColor colour)
: QGraphicsItem(), m_boundingRect(-width/2, -height/2, width, height), m_colour(colour)
{
setFlag(QGraphicsItem::ItemIsSelectable);
setFlag(QGraphicsItem::ItemIsMovable);
}
RectItem::~RectItem()
{
}
QRectF RectItem::boundingRect() const
{
return m_boundingRect;
}
void RectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
painter->setCompositionMode(QPainter::CompositionMode_Difference);
painter->setBrush(m_colour);
painter->drawRect(m_boundingRect);
}
You can now create two RectItem objects of the same semi-transparent colour and add them to the scene
// assuming the scene and view are setup and m_pScene is a pointer to the scene
RectItem* pItem = new RectItem(50, 50, QColor(255, 0, 0, 128));
pItem->setPos(10, 10);
m_pScene->addItem(pItem);
pItem = new RectItem(50, 50, QColor(255, 0, 0, 128));
pItem->setPos(80, 80);
m_pScene->addItem(pItem);