Written by Bo Thorsen
2016/08/30
In very rare occasions, we want to draw individual pixels all over a widget.
Custom painted widgets are much less common. But we can often get away with much easier painting than doing individual pixels. We draw these using lines, rectangles, or text, and fill with patterns or gradients. All of these operations on QPainter are very fast.
But if you try painting the widget using QPainters drawPoint method, you hit the wall almost immediately. Only for the smallest widgets is it possible to use this way of drawing, because it’s painfully slow.
Pixel based painting
I remember three widgets that I have written over the years, that had to do the pixel based drawing. One was a widget that show Ogg Vorbis based video decoded directly in the widget, the other two were different types of drawing widgets where the user could zoom in and out.
For an example of pixel based painting, take a look at this simple piece of code:
#include
#include
#include
#include
#include
const int loop = 25;
const int windowWidth = 400;
const int windowHeight = 300;
class PainterWindow : public QWidget {
void paintEvent(QPaintEvent*) {
QTime time;
time.start();
for (int i = 0; i < ::loop; ++i) {
QPainter painter(this);
for (int x = 0; x < width(); ++x) {
for (int y = 0; y < height(); ++y) {
const QColor color(static_cast(i+x+y));
painter.setPen(color);
painter.drawPoint(x, y);
}
}
}
qDebug() << "drawPoint time:" << time.elapsed();
close();
}
};
class ImageWindow : public QWidget {
void paintEvent(QPaintEvent*) {
QRgb* pixels = new QRgb[width()*height()];
QTime time;
time.start();
for (int i = 0; i < ::loop; ++i) {
QPainter painter(this);
QImage image((uchar*)pixels, width(), height(), QImage::Format_ARGB32);
for (int x = 0; x < width(); ++x) {
for (int y = 0; y < height(); ++y) {
pixels[x + y * height()] = static_cast(i+x+y);
}
}
painter.drawImage(0, 0, image);
}
qDebug() << "drawImage time:" << time.elapsed();
close();
delete[] pixels;
}
};
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
PainterWindow w;
w.resize(::windowWidth, ::windowHeight);
w.show();
a.exec();
ImageWindow imageWindow;
imageWindow.resize(::windowWidth, ::windowHeight);
imageWindow.show();
a.exec();
}
This is a pretty silly example, that just does some painting of colors. What I wanted to achieve was something that draws a lot of pixels on the screen. The colors are irrelevant, as long as they change. If you build and run this, you won’t actually see the painting being done, because it just paints all of the pixels 25 times and closes the window.
So how much faster do you think the image based painting is?
When I run this, I get drawing times for the drawPoint based painting at around 9,200 ms.
The image based painting is around 70 ms. This is less than 1% of what drawPoint uses!
Optimising the code
The code could be optimised a bit. For example, you can reuse the same image in all paint events and only recreate it on resizeEvents. Or you can check if color changes before you call setPen. But those are micro optimizations that won’t change the picture. And both examples should of course be buffered and only redrawn on changes, when this makes sense for the application.
I have tried other window sizes and different loop numbers. The speed difference is close to linear, when changing the widget size. For 40×30 windows, I get 94ms and 1ms drawing times. For 1000×500 I get 37,795 ms and 307 ms.
It should be noted that I run this on a fast Windows 8.1 desktop machine with a good graphics card. It’s highly likely that the numbers will be different for other types of machines. embedded Linux would be an interesting thing to try it on, for example. But no matter what you run this on, you will still see the same picture. That the speed of image based drawing crushes the slow drawPoint.
There is actually another option: To use an OpenGL based approach. I haven’t done this here, but it’s a valid approach. If a reader has numbers for this, please comment on this story.