Trolltech | Documentation | Qt Quarterly | « Far Reaching QFtp and QHttp | Glimpsing the Third Dimension »

Fast and Flicker-Free
by Reginald Stadlbauer
Many applications are written using Qt's standard widgets. But sometimes none of the standard widgets is suitable, and it is necessary to write a custom widget. It is not hard to create a custom widget, especially when you start from a QWidget, QFrame or QScrollView subclass and reimplement paintEvent() or drawContents(). But one common problem that can affect custom widgets is flicker. In this article we will explain why flicker occurs and how to avoid it.

Painting by Candlelight

Why does flicker occur? Let's look at what happens when we paint text on a widget:

The human eye sees a flicker, because there is a tiny interval of time between when the background is visible (after erasing), and when the text is painted on top of it. When this happens multiple times it leads to visible flicker.

Before we can address the problem, we must know when the erasing and painting takes place. One obvious time is when a paint event is generated by the windowing system, for example, when an overlapping window is moved and the newly exposed area must be repainted. But there are other possibilities too. For example, when the widget is resized or scrolled, or when update() or repaint() are called programmatically.

Now that we know how and when flicker is caused we are ready to deal with it. In the following sections we will look at some techniques for reducing and eliminating flicker.

Static Content versus Dynamic Content

A paint event is generated for the entire widget every time it is resized. This means that the whole background is erased and the entire content is painted again. With modern window managers, where the window is resized "live" (not using a rubber band), this causes a lot of flicker.

But do we really need to repaint the whole widget when it is resized? If we have a text editor widget that formats its text using the widget's width as the page width, the text will be reformatted every time the widget changes size and the entire content will need to be repainted. Widgets like this are said to have dynamic content. But if the text editor doesn't use the widget's width as its page width, but uses a fixed page width (as is common with word-processors), we don't need to repaint the visible part of the widget when it is resized, since the visible area will not change. Widgets like this are said to have static content.

Visible Area

We can tell Qt that a widget has static content (so it will only repaint newly revealed rectangles) using the WStaticContents flag:

    MyWidget::MyWidget(QWidget *parent, const char *name,
                       WFlags flags)
        : QWidget(parent, name, flags|WStaticContents)
    {
        // etc.
    }
    

Who's Got the Background?

Regardless of the content type, in many cases we must repaint at least some parts of the widget, and this repainting can still cause flicker. The problem remains: The windowing system fills the background with the widget's background color, and then the widget processes the paint event to paint the content. If the paint event is "slow," for example because it must do some calculations first (e.g. reformatting text), the delay between painting the background and painting the contents can be long enough for the human eye to notice flicker, and the longer the delay the worse the flicker will be.

One solution is for us to take over responsibility for erasing the background ourselves, and to minimize the time between erasing and painting. This does require extra code, but it gives us complete control over painting.

We can tell Qt that we want full control of painting by passing two flags to the QWidget constructor:

Once we use these flags we must paint the background ourselves, for example, using QPainter::drawRect() or QPainter::fillRect(). (Qt 3.2 will have a new flag that combines the two flags mentioned above: WNoAutoErase.)

Earning Our Stripes

So far we have seen how to minimize the amount of painting (if the widget has static content), and to take over background erasing. Doing our own erasing allows us to perform any calculations we need before erasing and painting, rather than between erasing and painting, so flicker will be reduced. But this still leaves us with a problem:

paint the entire background
paint line 1
paint line 2
...
paint line n

The delay between painting the background and painting the first line is very short (1  t, where t is the time needed to paint one line). This means there will be almost no flicker when the first line is painted. But the delay between painting the background and painting the last line is n  t, which can be significantly larger than 1  t depending on the size of n and t.

We can solve this problem by painting the background in stripes rather than all in one go:

paint the background of line 1
paint line 1
paint the background of line 2
paint line 2
...
paint the background of line n
paint line n
paint any remaining background

Notice that we must paint the "remaining" background separately. This is because the lines we paint (and their backgrounds) probably don't cover the entire widget, so we must take care of this separately.

Painting Stripes

The remaining area is rarely a single rectangle, usually it is a collection of many rectangles, which we call a region. At first sight it doesn't look very easy to calculate and paint the region. But Qt simplifies the problem thanks to the QRegion class that can represent combinations of rectangles, ellipses and polygons.

To demonstrate how this works in practice, we'll write a paintEvent() that paints a vector of lines. We begin by initializing a region with the whole unpainted rectangle that needs to be repainted:

    void MyWidget::paintEvent(QPaintEvent *evt)
    {
        // any initialization
        QPainter painter(this);
        QRegion unpainted(evt->clipRegion());

	for (int i = 0; i < lines.count(); ++i) {
            Line *line = lines[i];
            painter.fillRect(line->boundingRect(), backgroundColor);
            painter.translate(0, line->boundingRect().y());
            line->draw(&painter);
            painter.translate(0, -line->boundingRect().y());
            unpainted -= line->boundingRect();
        }
    

For each line, we fill the line's bounding rectangle, translate the painter to make position (0, 0) the line's origin, and then paint the line itself. We then subtract the rectangle we've painted from the unpainted region. Once we've processed all the lines, the unpainted background only contains the region that requires painting. Qt doesn't provide a fill function that takes a QRegion; but that's no problem, we simply set the painter's clip region to our region and fill it:

	painter.setClipRegion(unpainted);
	painter.fillRect(unpainted.boundingRect(), backgroundColor);
    }
    

This technique reduces the delay between erasing the background and painting the content to a fixed constant time t. Providing we do very little calculation in the loop, for example by pre-calculating the data before we start painting, the constant will be so small that there will be no visible flicker.

But what happens if the minimum delay we achieve is still too long? We could take the striping technique to the extreme, and paint the background and content one character at a time. Although feasible, this approach could be tedious to implement. A popular alternative strategy that sacrifices some memory for the sake of fast and flicker-free painting is presented in the next section.

Double Buffering

Flicker can be completely avoided if there is no delay between painting the background and painting the content. This can be achieved by rendering the background into a pixmap, then painting the content onto that pixmap, and finally painting the pixmap onto the widget. This technique is known as double buffering.

The quick and easy way to do this is to create a pixmap that's the same size as the rectangle that needs to be repainted. Then paint everything into the pixmap and finally paint the pixmap onto the widget. While doing this is completely flicker-free, it means paying a price in memory consumption and processing. For example, you would need to allocate and deallocate a pixmap of width height color-depth bits in every paint event, and this is a lot of memory if the widget gets maximized on a 32-bpp (bits per pixel) 1280 1024 screen. It is possible to reuse the same pixmap to save reallocations, but it would need to be the maximum possible size of the widget, so it will still consume a big chunk of memory.

A neat solution is to combine double buffering with stripes. Instead of storing the whole widget's rectangle in a pixmap, we can store one line instead. We then paint the background and content of a single line at a time and copy the pixmap back to the widget for each line that's painted. This requires a pixmap that's only line-width line-height color-depth bits in size; a lot less than the whole widget. We can also reuse the same pixmap, resizing it to be larger if necessary.

Here's another version of the paintEvent() we wrote earlier, this time using double buffering, reusing the pixmap, and painting in stripes.

    void MyWidget::paintEvent(QPaintEvent *evt)
    {
        // any initialization
        QPainter painter(this);
        QRegion unpainted(evt->clipRegion());
        static QPixmap *doubleBuffer = 0;
        if (!doubleBuffer)
            doubleBuffer = new QPixmap;
        QPainter dbPainter(doubleBuffer);

	for (int i = 0; i < lines.count(); i++) {
            Line *line = lines[i];
            doubleBuffer->resize(QMAX(doubleBuffer->width(), line->boundingRect().width()),
				 QMAX(doubleBuffer->height(), line->boundingRect().height()));
            doubleBuffer->fill(backgroundColor);
            line->draw(&dbPainter);
            painter.drawPixmap(0, line->boundingRect().y(), *doubleBuffer, 0, 0,
                               line->boundingRect().width(), line->boundingRect().height());
            unpainted -= line->boundingRect();
        }
        painter.setClipRegion(unpainted);
        painter.fillRect(unpainted.boundingRect(), backgroundColor);
    }
    

We reuse the pixmap by declaring it as a static pointer and allocating it if it is 0 (i.e. the first time we paint). Instead of painting directly on the widget, we paint on the pixmap. We must ensure that the pixmap is large enough each time we use it, resizing it if necessary. The painting code is almost the same as before because Qt provides a device-independent painting API. We just have to copy the pixmap to the widget after painting, using drawPixmap(). (We could have used bitBlt(), but drawPixmap() is faster if the painter is already open.)

This technique completely eliminates flicker, without consuming too much memory. Qt uses this approach in many standard widgets, including QTextEdit, QListView and QMenuBar. In fact, Qt applies a further memory optimization. Instead of allocating a double buffer pixmap for every single widget, Qt allocates a global pixmap for every type of widget, no matter how many instances of that widget are used.

Painting without Tears

Using stripes assumes that the widget content can be broken down into single items, for example, lines in an editor, or items in a list box. This assumption is often valid, but even in cases where it isn't, using stripes may still be possible.

The stripe technique can be applied by separating the area that needs to be repainted into small stripes (e.g. 100-200 pixels high). For each of these stripes in turn, paint the background, then paint all the items that intersect the stripe (either directly or using double buffering).

This approach to using stripes is especially useful in situations where items can overlap, as they can, for example in QIconView. If the first stripe approach we presented is used in this situation, items can be overpainted.

Overlapping Items

For example, if item "beta" overlaps item "alpha", what happens with our original stripe technique is this: Item alpha's background is painted, then item alpha is painted, then item beta's background is painted -- and this overpaints some or all of item alpha, then item beta is painted.

This problem is avoided by using this second stripe approach: The background of the stripes that contain items are painted, then item alpha is painted, then item beta is painted. Item beta may (correctly) obscure some or all of item alpha, but no (incorrect) background overpainting occurs.

There is a caveat to this second approach: It is only effective if you have a fast way of determining which parts of the content intersect with a given stripe.

Conclusion

We have presented several techniques to reduce or eliminate flicker from a custom widget. The right approach in any particular case will depend on the widget's content and data. If you bear in mind the techniques we've shown when creating custom widgets you can leave the door open to apply the best technique to ensure your widgets are fast and flicker-free.


This document is licensed under the Creative Commons Attribution-Share Alike 2.5 license.

Copyright © 2003 Trolltech. Trademarks Glimpsing the Third Dimension »