Back to Blog home

Fast transformed pixmap/image drawing

Published on Saturday May 13, 2006 by Andreas Aardal Hanssen in Qt KDE Qtopia | Comments

From time to time I hear about one particular annoyance people have with Qt's painting architecture - that drawing transformed pixmaps is horribly slow. Scaled pixmap drawing, for example; something like this:

We all know the use case: some image editor that needs to zoom in on an image to plot pixels, or perhaps someone is storing all their icons in one huge image file, and only want to draw a small part zoomed up. Well here's the typical, yet surprisingly troublesome, code for it:

void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.scale(1000, 1000); // or more!
painter.drawPixmap(x, y, pix);
}

(I don't recommend running this unless you are very prepared to hit Ctrl-C!)

Now, despite the fact that you've asked QPainter to draw a really really humongous pixmap, the widget is typically small, so, it feels a bit wrong that your app suddenly eats up all those 2 gigs of RAM and your machine starts swapping like crazy. What's Qt using all that memory for? QPainter could just clip away the parts it doesn't need, right? Or - it could be smart enough to read a small chunk from the source pixmap, scale it, and then draw only the visible part? Well it doesn't, but there's a pretty good reason for it [*]. The good news is, you can easily get around this limitation by fixing the above code a bit.

void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.scale(1000, 1000); // or more!

QRect exposedRect = painter.matrix().inverted()
.mapRect(event->rect())
.adjusted(-1, -1, 1, 1);
// the adjust is to account for half pixels along edges
painter.drawPixmap(exposedRect, pix, exposedRect);
}

Don't we all love matrices? (matrixes?..) ;-)

We take the exposed rect from the event (that gives us scroll/expose optimizations for free - no need to draw the whole pixmap if your widget is only partially exposed), and reverse map it with the painter matrix. That gives us the part of the pixmap that has actually been exposed. And for a 1600x1200 widget displaying a 1600x1200 pixmap at 1000x1000 scale, the exposed rect is... well... typically (0, 0, 2, 1). That's 2 pixels. QPainter doesn't need 1600000x1200000 pixels of image data to draw 2 pixels. We know that. And QPainter is glad that we've told it that. ;-) Passing the exposed rect to QPainter actually allows drawing in near-instantaneous time.

Isn't it also cool that this simple stuff actually works for any painter transformation? QGraphicsView uses this for its pixmap items, which makes them very snappy at high transformation levels:

One pixmap item at 1:1 scale. The same zoomed in, no difference in speed.

[*] Yes, QPainter does have a clip rect set on the widget's rect. And when it is transformed, that clip rect is also transformed. In fact, it has all the information available to determine exactly how much (or little) it needs to do. But when it comes down to the basic pixel plotting (or whatever we need to do to accomodate limitations in the underlying rendering system), it's pretty slow to determine what will eventually become visible in advance, before QPainter starts its painting. QPainter's clip can, after all, be arbitrarily complex (even a QPainterPath in Qt 4), so masking the original pixmap before the transformation is done is very likely to make all other drawing slow.

Until next time, bye!

Subscribe to Our Blog

Stay up to date with the latest marketing, sales and service tips and news.

The blog comment system has been migrated to a new platform. If you face any issues, please let us know via feedback@qt.io.