AUG
30
2008

Beware QPainter::eraseRect() with a QBrush::texturedImage()!

Funny little story... I was profiling with valgrind recently and was shocked to discover a huge hiccup in the painting performance of my QImage raster based app. There was this repeated call to QPainter::drawTiledPixmap() that was soaking up CPU cycles. It was a true mystery as I had thought the canvas of my app was completely QImage based. Not so.

You see, I had created a QBrush with a QImage texture and called QPainter::eraseRect() with it in a fairly important path in my code. What I didn't know was that QPainter was silently turning my QImage based textured brush into a QPixmap with QPixmap::fromImage() and using that to operate on my QImage based source device.

I have no idea why QPainter would decide to do this other than the mistaken assumption that QPixmap is always faster to draw. And maybe that is true in some general case, but surely not when the device you are painting to is already itself a QImage. And not in many other cases too. In fact, I don't even know why the QBrush(const QImage &) constructor exists if QPainter is going to insist on turning it into a QPixmap anyway.

Interestingly, a comment in the QPainter sources for the eraseRect shows another problem with this: it isn't thread-safe. Qt 4.4 touts itself as thread-safe when painting to a QImage. But that isn't true here. If you happen to try painting to a QImage in another thread and you have this use case you're in for some crashy, crashy. I wonder that the comment doesn't recognize the performance implications of this...

So beware of QPainter::eraseRect() and a textured image. I really hope Qt 4.5 can solve some of these problems.