Fast transformed pixmap/image drawing

Published Saturday May 13th, 2006
1 Comment on Fast transformed pixmap/image drawing
Posted in KDE, Qt, Qtopia

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 1600×1200 widget displaying a 1600×1200 pixmap at 1000×1000 scale, the exposed rect is… well… typically (0, 0, 2, 1). That’s 2 pixels. QPainter doesn’t need 1600000×1200000 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!

Do you like this? Share it
Share on LinkedInGoogle+Share on FacebookTweet about this on Twitter

Posted in KDE, Qt, Qtopia

One comment

Nicolas says:

Thank you for this tip ! I really enjoy it, and I need it πŸ˜‰

In Qt 3, we have made an image viewer with unlimited zoom, and we had to caculate and extract the part of the image exposed, and scale it before painting it on the widget.

I don’t know if it’s working for Qt 3, but I have to port this code during summer for Qt 4, and it’s perfect for me.

Thank you (I hope to read more like that).

Commenting closed.

Get started today with Qt Download now