qt drawing outlined text with QPainterPath - c++

i have to draw outlined striked out text on QImage like that:
I do it as follows:
QPainter painter(this);
QPainterPath path;
QFont font;
font.setPixelSize(95);
font.setStrikeOut(true);
font.setBold(true);
path.addText(10, 150, font, "lololo");
painter.setPen(Qt::blue);
painter.setBrush(Qt::red);
painter.drawPath(path);
and get this result:
As one can see the striking out line has zebra-like fill. How i can fill it completely with painter's brush?
I tried to change QPainter composition mode with no success. Also i tried to use QPainterPathStroker with the same result.
Sure i can draw striked out text with ordinary font (not striked out) plus rectangle, but it isn't a beautiful solution.

The solution is to perform operations between 2 paths with and without strike:
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QImage image(300, 200, QImage::Format_ARGB32);
image.fill(Qt::transparent);
QPoint p(30, 150);
QString text = "lololo";
QFont font;
font.setPixelSize(95);
font.setBold(true);
QPainterPath path1;
font.setStrikeOut(true);
path1.addText(p, font, text);
font.setStrikeOut(false);
QPainterPath path2;
path2.addText(p, font, text);
QPainterPath strike = (path1 + path2) - (path1 & path2);
// \---join---/ \-intersection-/
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::blue);
painter.setBrush(Qt::red);
painter.drawPath(path2);
painter.drawPath(strike);
painter.end();
QLabel w;
w.setPixmap(QPixmap::fromImage(image));
w.show();
return a.exec();
}

Related

Set the fill color (not the stroke color) for the QPainter in QT

How could I set the fill color (not the stroke color) for the QPainter in QT?
For example, I have a code which is responsible for filling the rectangle. It looks like:
painter.fillRect(fillRect, Qt::SolidPattern);
Where the type of painter is QPainter. Of course, I know that it is possible to specify the color in the case as a second parameter, but I have such a design in my program that it would be a lot better if I could set the painter fill color beforehand (by default the color is black).
I tried to use painter.setBackground(Qt::yellow);, but it did not help.
Hm. According to this we have:
Sets the painter's brush to the given brush.
The painter's brush defines how shapes are filled.
So, I would expect something like
QRect fillRect;
painter.setBrush(QBrush(Qt::yellow));
painter.fillRect(fillRect, Qt::SolidPattern);
to work. But it does not. What am I doing wrong?
After debugging it turns out that the setBrush method does not update the brush color at all:
The color rgb stays the same: (0, 0, 0).
fillRect() accepts a QBrush as a second parameter, so I could use it:
painter.fillRect(r, QBrush(Qt::yellow, Qt::SolidPattern));
Update:
#include <QApplication>
#include <QLabel>
#include <QPainter>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPixmap pixmap(128, 128);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
QRect r= pixmap.rect();
painter.setBrush(QBrush(Qt::yellow));
painter.fillRect(r, painter.brush());
painter.end();
QLabel w;
w.setPixmap(pixmap);
w.show();
return a.exec();
}

Hide area of QGraphicsItem that is out of boundary

I have a QGraphicsPixmap item in a QGraphicsScene. The item has flags set to ItemIsMovable, and ItemIsSelectable. How do I ensure that when the item is moved out of a certain boundary - it can be a QGraphicsScene or just a fixed frame size at fixed coordinates - the part becomes hidden?
Eg.
The left part of the basketball becomes hidden.
You have to use setClipPath().
In the following code I have created a class that inherits from QGraphicsPixmapItem (the same could do with other classes that inherit from QGraphicsItem) and I created the method setBoundaryPath() that receives a QPainterPath that indicates the visible area, for example in the code use:
QPainterPath path;
path.addRect(QRectF(100, 100, 400, 200));
That QPainterPath is a rectangle whose topleft is the point (100, 100) of the QGraphicsScene with size of 400 in width and 200 in height.
#include <QApplication>
#include <QGraphicsRectItem>
#include <QGraphicsView>
class GraphicsPixmapItem: public QGraphicsPixmapItem{
public:
GraphicsPixmapItem(const QPixmap & pixmap, QGraphicsItem *parent = 0):
QGraphicsPixmapItem(pixmap, parent)
{
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){
if(!m_boundaryPath.isEmpty()){
QPainterPath path = mapFromScene(m_boundaryPath);
if(!path.isEmpty())
painter->setClipPath(path);
}
QGraphicsPixmapItem::paint(painter, option, widget);
}
QPainterPath boundaryPath() const{
return m_boundaryPath;
}
void setBoundaryPath(const QPainterPath &boundaryPath){
m_boundaryPath = boundaryPath;
update();
}
private:
QPainterPath m_boundaryPath;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView view;
QGraphicsScene scene(0, 0, 600, 400);
view.setScene(&scene);
view.setBackgroundBrush(QBrush(Qt::gray));
GraphicsPixmapItem *p_item = new GraphicsPixmapItem(QPixmap(":/ball.png"));
p_item->setPos(100, 100);
// Define the area that will be visible
QPainterPath path;
path.addRect(QRectF(100, 100, 400, 200));
p_item->setBoundaryPath(path);
scene.addItem(p_item);
// the item is added to visualize the intersection
QGraphicsPathItem *path_item = scene.addPath(path, QPen(Qt::black), QBrush(Qt::white));
path_item->setZValue(-1);
view.show();
return a.exec();
}
You can find the example code in this link.

Qt - is there a way to transform a graphic item into a pixmap?

I want to create a pixmap from a graphicObject, so i can set the pixmap as an icon
I have a Block class derived from QGraphicsPathItem and i tried using:
Block *block = new Block();
QRect rect = block->boundingRect().toRect();
QPixmap pixmapItem;
pixmapItem.copy(rect);
QListWidgetItem *item = new QListWidgetItem;
item->setIcon(QPixmap(pixmapItem));
but the pixmap appears to be empty.
Is there a way to get an image/icon out of a graphicObject or graphicItem?
You have to use the paint() method of QGraphicsItem to get the rendering:
static QPixmap QPixmapFromItem(QGraphicsItem *item){
QPixmap pixmap(item->boundingRect().size().toSize());
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
QStyleOptionGraphicsItem opt;
item->paint(&painter, &opt);
return pixmap;
}
Example:
#include <QApplication>
#include <QGraphicsPathItem>
#include <QGraphicsView>
#include <QHBoxLayout>
#include <QListWidget>
static QPixmap QPixmapFromItem(QGraphicsItem *item){
QPixmap pixmap(item->boundingRect().size().toSize());
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
QStyleOptionGraphicsItem opt;
item->paint(&painter, &opt);
return pixmap;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QHBoxLayout lay(&w);
QListWidget listWidget;
QGraphicsView view;
QGraphicsScene scene;
view.setScene(&scene);
int counter = 0;
for(const QColor & color : {QColor("red"), QColor("blue"), QColor("green")}){
QPainterPath p;
p.addRoundedRect(0, 0, 150, 50, 2, 2);
QGraphicsPathItem *block = scene.addPath(p);
block->setBrush(QBrush(color));
block->setFlag(QGraphicsItem::ItemIsMovable);
block->setFlag(QGraphicsItem::ItemIsSelectable);
block->setPos(counter*QPointF(10, 10));
// get QPixmap from item
QPixmap pixmap = QPixmapFromItem(block);
QListWidgetItem *l_item = new QListWidgetItem(color.name());
listWidget.addItem(l_item);
l_item->setIcon(QIcon(pixmap));
counter++;
}
lay.addWidget(&listWidget);
lay.addWidget(&view);
w.show();
return a.exec();
}
It should be possible to use QGraphicsItem::paint for this:
QSize itemSize = item->boundingRect()
QPixmap targetPixmap(item->boundingRect().size().toSize());
QPainter pixmapPainter(&targetPixmap);
QStyleOptionGraphicsItem styleOption;
item->paint(&pixmapPainter, &styleOption);
This is because a bounding rectangle only contains coordinate and size information about your QGraphicsItem but no further information about how to draw it.
You could try something similar to the following: Create a QImage of your block's size and use it to initialize a QPainter. The QPainter can then be used by the block to draw on the image. This is achieved using the paint method which Block inherits from QGraphicsItem
Block *block = new Block();
QSize size = block->boundingRect().toRect().toSize();
QImage* image = new QImage(size, QImage::Format_RGB32);
QPainter* painter = new QPainter(image);
block->paint(painter, new StyleOptionGraphicsItem());
Then your block should have be painted to image.
Thanks for the nice example by #eyllanesc. In addition a translate command also mandatory in some cases.
static QPixmap QPixmapFromItem(QGraphicsItem *item){
QPixmap pixmap(item->boundingRect().size().toSize());
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
QStyleOptionGraphicsItem opt;
painter.translate (-1 * (item->boundingRect ().topLeft ()));//THIS IS MANDATORY IN SOME CASES.
item->paint(&painter, &opt);
return pixmap;
}
Although this post is long time ago and solved, I just want to contribute the "translation" of the function in Python and PyQt5:
def convert( self, item: QGraphicsItem ):
pixmap = QPixmap( item.boundingRect().size().toSize() )
pixmap.fill( Qt.transparent )
painter = QPainter( pixmap )
# this line seems to be needed for all items except of a LineItem...
painter.translate( -item.boundingRect().x(), -item.boundingRect().y())
painter.setRenderHint( QPainter.Antialiasing, True )
opt = QStyleOptionGraphicsItem()
item.paint( painter, opt , self) # here in some cases the self is needed
return pixmap
# The "some cases": Placing this function in a QGraphicsScene-Subclass was ok without the "self", but placing it in QWidget-Subclass needed this self.
Thanks for all answers here :-)
...but I have to admit, that I don't really understand what is happening in the line item.paint( painter, opt , self)
I would be happy about any explanation how this works, but I am also happy that it works ;-)

Formatted outline text on QImage

How to draw outline text on QImage with word wrap, alignment etc.?
I tried QPainterPath::addText() method, but it does not support word wrap and alignment.
This is how I draw text now:
path.addText(rect.bottomLeft(), font, text);
p.strokePath(path, pen);
p.drawText(rect, flag, text);
where
p - QPainter
rect - rectangle that contains the text
flag - Qt::AlignRight, Qt::TextWordWrap
but it looks like this
QPainter does not support this and neither does QPainterPath.
What you can do is use a QGraphicsSceneinstead. If you really have to create a QImage, you can always do that using its render() function.
To achieve this effect in a graphics scene you need a QGraphicsTextItem and a QTextCharFormat.
The outline is painted on top of the actual text, which looks like complete crap for any outline thicker than 1. Therefore you have to add another QGraphicsTextItem without outline on top of the first one.
Here is a full running example. Note that we can specify the width of the text, but not the height, you wold probably do that by clipping.
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsTextItem>
#include <QLabel>
#include <QPainter>
#include <QTextCharFormat>
#include <QTextCursor>
#include <QTextDocument>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QString text = "lorem ipsum dolor sit amet";
QFont font("Arial", 30);
QRect rect(100, 100, 300, 0);
QImage img(500, 500, QImage::Format_RGB32);
img.fill(0x00ffffff);
{
QPainter p(&img);
QGraphicsScene scene(img.rect());
QTextCharFormat charFormat;
charFormat.setFont(font);
charFormat.setTextOutline(QPen(Qt::red, 5, Qt::SolidLine));
QTextDocument document;
QTextCursor cursor = QTextCursor(&document);
cursor.insertText(text, charFormat);
QGraphicsTextItem outlineTextItem;
outlineTextItem.setPos(rect.topLeft());
outlineTextItem.setDocument(&document);
outlineTextItem.setTextWidth(rect.width());
scene.addItem(&outlineTextItem);
QGraphicsTextItem textItem(text);
textItem.setPos(rect.topLeft());
textItem.setTextWidth(rect.width());
textItem.setFont(font);
scene.addItem(&textItem);
scene.render(&p);
}
QLabel label;
label.setPixmap(QPixmap::fromImage(std::move(img)));
label.show();
return a.exec();
}
I found this solution: I draw the text in the buffer (using QPainter.drawText), calculate the SDF, and make outline.
https://en.wikipedia.org/wiki/Signed_distance_function
https://blog.mapbox.com/drawing-text-with-signed-distance-fields-in-mapbox-gl-b0933af6f817

How to paint on pixmaps inside a QList<QPixmap>?

I'm trying to generate pixmaps with the following code
QList<QPixmap> pixmapList;
for (int i=0;i<50;++i){
QPixmap pixmap = QPixmap(10050,10050);
pixmap.fill(Qt::transparent);
pixmapList<<pixmap;
}
The above part works find. And I would like to paint on those pixmaps later, e.g.
QPixmap pixmap = pixmapList[10];
QPainter painter(&pixmap);
painter.drawPixmap(....); // this pixmap is 10*10
pixmapList[10]=pixmap;
or
QPainter painter(&pixmapList[10]);
painter.drawPixmap(....); // this pixmap is 10*10
but they both gave me "QPainter::begin: Paint device returned engine == 0, type: 2". May I ask the right way to paint on pixmaps in the pixmapList? Thanks very much!
Your code is OK, except that the pixmaps are too big (they occupy ~400MBytes each).
After making the pixmaps smaller, it works fine (shown for Qt 5):
#include <QGuiApplication>
#include <QPixmap>
#include <QPainter>
int main(int argc, char *argv[])
{
QGuiApplication a(argc, argv);
QList<QPixmap> pixmapList;
for (int i=0;i<50;++i){
QPixmap pixmap = QPixmap(1000,1000);
pixmap.fill(Qt::transparent);
pixmapList<<pixmap;
}
QPainter painter(&pixmapList[10]);
painter.drawLine(0, 0, 100, 100);
return 0;
}