How to draw svg graphics with painter class - c++

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.

Related

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.

QT5: Drawing rect on top of another rect using drawPixMap()

am I using drawPixmap() correctly?
Essentially my goal is to take an tileset image, and replace an individual tile with a custom tile image.
I'm able to get both images to load on the screen, but when I call drawPixmap(), then original image doesn't change at all.
Thanks in advance.
void replaceCustomTile(QPixmap custom, QPixmap target, int whichTile) {
QRect rect(0, 0 + (squareTileSize * whichTile), squareTileSize, squareTileSize);
QRect customRect = custom.rect();
QPainter painter(this);
painter.drawPixmap(rect, target, customRect);
painter.end();
}
EDIT:
This is how replaceCustomTile is called:
QPixmap terrainTiles(":/static/Terrain.png");
QPixmap customTile(":/static/Smiles.png");
replaceCustomTile(customTile, terrainTiles, 0);
To intialize QPainter by this it must be called from the widget paintEvent(QPaintEvent *) if you want to draw it on some widget. So, replaceCustomTile() should be called from the event handler in that case.
To draw some pixmap on top of another pixmap QPainter should be initialized by the target pixmap using QPainter::begin():
QPainter painter;
painter.begin(&target);
painter.drawPixmap(rect, custom);
painter.end();
The above code draws QPixmap custom into given QRect rect over QPixmap target. The target is modified.

QML type to draw with QPainter

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

Qt drawRect in background

I want to paint the background of a slider. I tried this but the color covers up the whole slider. This is in an inherited class of QSlider
void paintEvent(QPaintEvent *e) {
QPainter painter(this);
painter.begin(this);
painter.setBrush(/*not important*/);
// This covers up the control. How do I make it so the color is in
// the background and the control is still visible?
painter.drawRect(rect());
painter.end();
}
To set the background of a widget you could set the style sheet:
theSlider->setStyleSheet("QSlider { background-color: green; }");
The following will set the background of the widget, allowing you to do more:
void paintEvent(QPaintEvent *event) {
QPainter painter;
painter.begin(this);
painter.fillRect(rect(), /* brush, brush style or color */);
painter.end();
// This is very important if you don't want to handle _every_
// detail about painting this particular widget. Without this
// the control would just be red, if that was the brush used,
// for instance.
QSlider::paintEvent(event);
}
And btw. the following two lines of your sample code will yield a warning:
QPainter painter(this);
painter.begin(this);
Namely this one using GCC:
QPainter::begin: A paint device can only be painted by one painter at
a time.
So make sure, as I do in my example, that you either do QPainter painter(this) or painter.begin(this).

QPixmap of a QGraphicsTextItem

How do you convert/paint a QGraphicsTextItem into a QPixmap?
You can add it to a QGraphicsScene (if it's not already inside one) and then render() the scene to a QPixmap using a QPainter
QPixmap pix(100, 100);
QPainter paint(&pix);
scene.render(&paint);
Or, you can save yourself the trouble and just use QPainter::drawText() after changing the current font of the painter. it should provide the same capabilities.
Maybe something like this-
QPixmap pix(100, 100);
QPainter paint(&pix);
paint.drawText(0, 0, "Hello World");
The QGraphicsTextItem::document() function is the back door you're looking for:
// pItem is a QGraphicsTextItem *
QPixmap srcPixmap(pItem->boundingRect().size().toSize());
QPainter tmpPainter(&srcPixmap);
pItem->document()->drawContents(&tmpPainter);
tmpPainter.end()