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

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;
}

Related

Render QWidget which is larger than 32767 px

I try to render a widget whose size is 3000x50000.
#include <QApplication>
#include <QPainter>
class widget: public QWidget{
protected:
void paintEvent(QPaintEvent *event) override{
QPainter painter(this);
painter.setPen(QPen(Qt::black, 12, Qt::DashDotLine, Qt::RoundCap));
painter.drawLine(QPoint(0,0),rect().bottomRight());
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
widget wid;
wid.setGeometry(0,0,3000,500000);
QPixmap pixmap(3000,50000);
wid.render(&pixmap);
pixmap.save("PATH_TO_IMAGE");
exit(EXIT_SUCCESS);
return a.exec();
}
but in output I have this image result after 32767px image was not rendered.
Maybe you have any idea how to fix it?
The limitation comes from the internal raster paint engine (see qoutlinemapper_p.h: const int QT_RASTER_COORD_LIMIT = 32767;). Upgrade to Qt6 and use a 64bit build - then this limit is updated to 8388607 (2^23-1) pixels.
The better solution which I make is to render the widget to QImage step by step (step should be lower the 32767px) and then copy bits of QImage(usingQImage::bits()) into one buffer(QVector). And after all these steps you can create one QImage(using QImage(&buffer.first())

How to access the raw widget pixels rendered on the screen?

I am trying to extract the pixel data from every frame, but when I try to get the pixmap it returns null. I thought that the pixmap would return the actual pixel data of what was on the screen, similar to glReadPixels but I think I am mistaken. I think it's meant to access the pixel map of the result of setPixmap.
Is there a way to access the raw pixels rendered on the screen? With the below example, "Hello World" is rendered on the screen and I want the actual pixel data of that label.
QWidget window;
window.resize(1280, 720);
window.show();
window.setWindowTitle(QApplication::translate("toplevel", "Top-Level Widget"));
QLabel *label = new QLabel(QApplication::translate("label", "Hello World"), &window);
label->move(500, 500);
label->raise();
label->show();
QPixmap pixmapVal = label->pixmap(Qt::ReturnByValue);
Cause
QPixmap:
The QPixmap class is an off-screen image representation that can be used as a paint device
In other words, it is a drawing canvas and, as any other canvas, if you have not drawn on it, it would be empty.
On the other hand QLabel serves as a view for the pixmap, not as its content, and when you try to access the pixmap of the label without having set one, it returns null.
Solution
There is of course a way to make a widget, the label in your case, content of the pixmap and access the pixel data. My approach to the this problem would be like this:
Create an empty pixmap with the size of the widget whos pixel content you want to access, e.g.:
QPixmap pixmap(widget->size());
pixmap.fill(Qt::transparent);
Use QWidget::render to render the content of the widget onto the pixmap:
widget->render(&pixmap);
Convert the pixmap to QImage and use QImage::pixel or QImage::pixelColor to access the rgb data of the pixel at (pixelX, pixelY) like this:
pixmap.toImage().pixelColor(pixelX, pixelY);
Example
Here is an example I have prepared for you to demonstrate how the proposed solution could be implemented:
#include "MainWindow.h"
#include <QApplication>
#include <QLabel>
#include <QDebug>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
auto *label = new QLabel(QObject::tr("Hello World"), &w);
label->move(500, 500);
label->raise();
w.setWindowTitle(QObject::tr("Top-Level Widget"));
w.resize(1280, 720);
w.show();
QPixmap pixmap(label->size());
pixmap.fill(Qt::transparent);
label->render(&pixmap);
int pixelX = 10;
int pixelY = 5;
// Access image pixels
qDebug() << pixmap.toImage().pixelColor(pixelX, pixelY);
return a.exec();
}
Result
For the pixel at (10, 5) the example produces the following result:
QColor(ARGB 1, 0, 0, 0.156863)

only left border in textdocument

I am trying to draw only the left border in QTextDocument. Since afaik QTextFrame doesn't support selective border so I am trying with assigning a textured brush to the text frame. Something like the following -
#include <QPainter>
#include <QTextFrameFormat>
#include <QTextCursor>
#include <QTextFrame>
#include <QTextEdit>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QTextDocument doc;
QPixmap map(1024, 1024);
QPainter p;
p.begin(&map);
p.setBackground(QBrush(Qt::transparent));
p.drawRect(QRect(0,0, 4, map.height()));
p.end();
QTextFrameFormat frameFormat;
QBrush bruh(map);
bruh.setColor(Qt::transparent);
frameFormat.setBackground(bruh);
auto cur = new QTextCursor(&doc);
auto frame = cur->insertFrame(frameFormat);
auto curf = new QTextCursor(frame);
curf->insertText("Hello this is qt program!");
QTextEdit e;
e.setDocument(&doc);
e.show();
return a.exec();
}
But this prints a black background even though the background is set to be transparent(I need a transparent background with only left red border).
I can't figure out what is wrong. Also, can there be any other way to have only a left border to a QTextFrame?
Try this:
QTextDocument doc;
QPixmap map(1024, 1024);
map.fill(Qt::white);
QPainter p;
p.begin(&map);
p.fillRect(QRect(0,0, 4, map.height()),QBrush(Qt::red));
p.end();

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();
}

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