Transparent Backgrounds in Qt 4.1
Материал из Wiki.crossplatform.ru
by Andreas Aardal Hanssen
Qt 4.1 radically improves widget drawing through its new "backing store", allowing semi-transparent (alpha-blended) child widgets and faster widget painting, as well as solving long-standing issues with nonrectangular widgets.
As a custom widget or custom style writer, Qt 4.1 should make your life easier, especially if you want to draw non-rectangular widgets. When doing the transition to the backing store in preparation for the Qt 4.1 release, we discovered that we could both simplify and improve our widget and style code. The new features have also enabled us to address issues concerning the Plastique style which in its initial release did not adequately support textures, gradients, and "dark colored backgrounds".
To better understand what has changed in Qt 4.1, we will start by reviewing how backgrounds work in Qt 3.x and 4.0 and how to obtain child widget transparency. Much of this is still applicable in Qt 4.1, so read on!
The System's Background
A widget represents a distinct region of desktop real estate in which we can paint shapes and colors. Widgets can be stacked on top of each other, and the topmost widget covers the widget underneath it. If a child widget covers half of its parent widget, the parent widget can only draw onto its own exposed areas (available as QPaintEvent::region()). Painting operations on the child widgets are clipped away.
On all desktop platforms, a Qt widget is also an independent object known to the window system. By default, the system provides a background based on the widget's background role and palette, which is used whenever a new area of the widget is shown for the first time.
For example, the background role of a QLabel is QPalette::Background, which traditionally corresponds to a gray color in the palette; for a QLineEdit, the background role is QPalette::Base, which is usually white.
If we call update() or repaint() to repaint a widget, Qt automatically erases the widget using the background color, relieving us from painting all the widget's pixels. This is very convenient, but it comes at a price:
- If we paint all the pixels in the paint event (for example, we draw a texture or a gradient that covers the entire background), the CPU cycles spent erasing the widget are pure waste.
- In Qt 3, erasing the widget is a source of flicker because some pixels will be painted twice in a short time. [1]
Let's suppose we want to draw a graphing widget on a Base background. We'd start by adding this call to the constructor:
setBackgroundRole(QPalette::Base);
In paintEvent(), we could now get away with drawing only the foreground. If we don't draw anything in paintEvent(), the widget will be totally white (or whatever color or brush Base stands for). Without having to draw anything, we can alter the widget's background color by changing the palette or setting a different background role.
If paintEvent() paints all the widget's pixels, we can tell Qt not to erase the widget (or the pixmap) with the background color by setting the Qt::WNoAutoErase (Qt 3) or Qt::WA_NoBackground (Qt 4) flag. In Qt 3, this is a common anti-flicker technique; in Qt 4, this is a minor speed optimization.
As a further optimization, we can also disable the window system background by calling setBackgroundMode(Qt::NoBackground) (Qt 3) or setAttribute(Qt::NoSystemBackground) (Qt 4). Now, the widget is truly uninitialized, and if we don't paint all the pixels in paintEvent(), we can see some interesting "artwork" as we move the window around.
Contents Propagation
The purpose of Qt::WA_NoSystemBackground is to speed up drawing by removing unnecessary background initialization, but it is often misinterpreted as a way to make the widget background transparent. Keep in mind that although the widgets are stacked on top of each other, the parent widget does not actually paint underneath child widgets.
We need a different approach if we want the parent widget's background to shine through. This is called "contents propagation", and Qt 4.0 supports this through its Qt::WA_ContentsPropagated widget attribute. In Qt 3, there are several ways to simulate this feature:
- You can use QPainter::redirect()and then call the parent widget's paintEvent() to have the parent draw itself onto the widget's background directly.
- You can set a transparency mask (QWidget::setMask()). The parent widget is aware of the child widget's mask and will paint all areas not covered by the mask.
- If the parent doesn't paint anything underneath the child widget other than its background, you can fill the child widget's background by inheriting the parent widget's brush and applying a suitable offset by calling QWidget::setBackgroundOrigin().
Although Qt 3 doesn't support semi-transparency for widgets, we can simulate it using QImage if necessary.
Window Opacity
Qt 3.3 introduced a new widget property called windowOpacity for making top-level widgets (windows) transparent. This property can't be used to make child widgets semi-transparent, and it only works on window systems that support this feature (Mac OS
X and Windows 2000 or later). In addition, the windowOpacity property applies to the widget as a whole, not to individual shapes or pixels.
No matter which approach is taken, contents propagation takes its toll on the application's performance. Exposing a child widget will trigger a repaint for the parent widget. For complex hierarchies, the results can be unbearably slow. For every region with n overlapping widgets, the bottommost widget gets n repaints, and for every repaint we may have layouts, font metrics, clipping, and perhaps some complex operations such as an image-to-pixmap conversion or gradient generation. Contents propagation can result in nІ paint events, even for the simplest widget exposures.
If two semi-transparent widgets overlap (e.g., a semi-transparent rubber band covers a semi-transparent graph widget background), things get even worse. Because of this, Qt::WA_ContentsPropagated in Qt 4.0 does not support overlapping siblings.
== New in Qt 4.1: The Backing Store Qt's paint system has taken a big step forward in Qt 4.1 with the introduction of a backing store on all platforms. The backing store uses a persistent off-screen buffer for each window, and all raster paint operations are done directly to the backing store.
The backing store results in faster drawing on Windows and faster exposes on all platforms, and makes semi-transparent child widgets possible.
On Mac OS X, the backing store is built into the window system; on other platforms, Qt provides its own backing store. The backing store opens up a whole new world of possibilities:
- Because the backing store contains a complete rendering of the application's windows at all times, window system exposures don't generate any paint events. No matter how complex the widgets are and how deeply they are stacked, exposures are nearly instantaneous.
- The backing store itself can do alpha blending and composition for all widgets, so we now have complete alpha-blending support for all widgets on all platforms. Repaints are only done for the widgets and regions that absolutely need the repaint to recompose the widget background. In the future, it will be possible for Qt to use hardware support to accelerate the composition even more.
- Qt now has full support for composition of overlapped siblings, which wasn't supported by Qt::WA_ContentsPropagated.
How does all of this affect widget backgrounds? Here's a list of the most significant changes in 4.1:
- QPalette::Background has been renamed QPalette::Window to avoid the confusion between QWidget's backgroundRole property and QPalette's Background role. Similarly, QPalette::Foreground has been renamed QPalette::WindowText.
- Window backgrounds are now initialized with the QPalette::Window brush. All child widgets are by default "transparent"; Qt doesn't fill the widget for us. Because of this, painting with an alpha color can now be done directly in paintEvent().
- QWidget has a new property called autoFillBackground, which is false by default. Setting this to true will give your custom widgets the same background behavior that they had in Qt 4.0.
- Qt::WA_NoBackground is now called Qt::WA_OpaquePaintEvent, with the same meaning as before. By setting this option, you provide Qt with a guarantee that you will paint all the pixels in your paint event with non-transparent colors. (If you set Qt::WA_OpaquePaintEvent, or enable QWidget::autoFillBackground and use an opaque background brush, Qt won't need to recompose the background. This will improve the rendering speed of your widgets significantly.)
- Qt::WA_ContentsPropagated has become superfluous, because each widget's contents are now propagated by default.
In some very rare cases, the change might affect the semantics of existing programs. If one of your custom widgets goes transparent, calling setAutoFillBackground(true) on the widget will fix the problem.
The potential for performance using the backing store architecture is far beyond what we had before. What's more, our users now have the option of writing modern widgets that stand out from the competition, on all platforms, without requiring advanced graphics support on their deployed platforms (such as XRender on X11). See the Fading Effects with Qt 4.1 article for a real-world application of semi-transparent child widgets.
With Qt 4.1, you can now use QPainter to its full potential, with semi-transparent backgrounds and antialiased curves blended against the parent widget's background. And it won't just work on Mac OS X and Windows 2000 or better. It will work on all platforms, which is exactly what you'd expect from your favorite cross-platform GUI framework.
Note: More information about contents propagation and child widget transparency can be found in the Transparency and Double Buffering section of the QWidget documentation.
[1]
In Qt 4, this is not an issue because of double-buffering. When we paint on a widget, we're actually drawing on a pixmap that will be copied to the screen.