Detail of an Alpha Blended form

In this first of two articles, I will demonstrate how to easily create an alpha blended translucent splash screen using Delphi.

Although I use Delphi 2007 and PhotoShop here, the techniques apply equally well to other versions of Delphi and other image editing tools.

[Update 2008-05-27] Part 2 has been posted: Alpha Blended Splash Screen in Delphi - Part 2

Transparency, Opacity, Translucency

There are many different ways to implement and use transparency. In this tutorial we will use only one kind of transparency; Alpha Blending, and one implementation; The UpdateLayeredWindow API. Before we get our hands dirty with some code, I will introduce the most common kinds of transparency, but first we need to define some terms (definitions courtesy of dictionary.com):

Transparent
trans·par·ent [trans-pair-uh nt, -par-]
Capable of transmitting light so that objects or images can be seen as if there were no intervening material.
In other words; Transparent means invisible.
Translucent
trans·lu·cent [trans-loo-suh nt, tranz-]
Permitting light to pass through but diffusing it so that persons, objects, etc., on the opposite side are not clearly visible.
In other words: Translucent means partially transparent. Semi transparent is the same as translucent.
Opaque
o·paque [oh-peyk]
Impenetrable by light; neither transparent nor translucent.
In other words; Opaque is the opposite of transparent.

Different Kinds of Transparency

Color Key transparency

Color Key Transparency

Color Key transparency is the simplest form of transparency. Transparency is accomplished by defining one color that wont be drawn when the window is rendered onto the screen. Color Key transparency does not support alpha blending, but it can be combined with Uniform Translucency to soften the edges of the image.

Delphi support Color Key transparency with the TForm.TransparentColor and TForm.TransparentColorValue properties.

Uniform Translucency

Uniform Translucency

Uniform Translucency controls the opacity of the window by applying a single alpha blend value to the whole window. This is often used to make a window semitransparent while it is being moved. It can also be used to create a fade-in/fade-out effect, but the AnimateWindow API is better suited for that purpose.

Delphi support Uniform Translucency with the TForm.AlphaBlend and TForm.AlphaBlendValue properties.

Alpha Blended Bitmap Translucency

Alpha Blended Translucency

With Alpha Blended Bitmap Translucency (or just Alpha Blending for short), each pixel in the source bitmap is accompanied by its own individual transparency value. The transparency values are known as the Alpha channel and is usually of the same depth (bit size) as each of the color channels.

Masked transparency

Masked Transparency

Masked Transparency basically works the same way as Color Key transparency. The transparency is specified with a 1-bit bitmap called the mask.

Color Key transparency is usually implemented by creating a mask from the source bitmap. See the CopyBitmapAsMask function in the Graphics unit for an example.

Clipping

Clipping

Clipping really isn’t a kind of transparency, but it can be used to give the illusion of transparency by altering the normal rectangular shape of a window.

See example 1 below for an example of how to apply clipping to a window.

Transparent windows

Windows 95 - Window regions

Windows has supported simple window pseudo transparency since Windows 95 by means of clipping. Transparency with clipping is accomplished using the SetWindowRgn and CreatePolygonRgn APIs and while clipping based transparency is relatively simple to implement, the results are also very crude.

Example 1

Simple region based transparency

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface
 
type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  protected
    procedure WMNCHitTest(var Msg:TMessage); message WM_NCHITTEST;
  end;
 
implementation
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  BorderStyle := bsNone;
  SetWindowRgn(Handle,CreateEllipticRgn(10,10,Width-10,Height-10),
    False);
end;
 
procedure TForm1.WMNCHitTest(var Msg:TMessage);
begin
  Msg.result := HTCAPTION;
end;

Although more complex shapes can be made by combining a gazzilion small rectangles into a region, the results still aren’t very pretty and performance is poor. The visual appearance suffers from the lack of alpha blending between the window and the background, and the performance suffers because of the conversions that has to take place between the vector based regions and the bitmapped display device.

Windows 2000 - Layered Windows

The concept of layered windows was introduced in Windows 2000 beta 3 with the WS_EX_LAYERED window style. Layered windows not only support alpha blended bitmap translucency, but also the more simple uniform- and color key translucency.

Windows provides us with two, mutually exclusive, ways to implement layered windows: The UpdateLayeredWindow API and the SetLayeredWindowAttributes API. I’ll get to these shortly.

A layered window can be created either by specifying the WS_EX_LAYERED style when creating the window, or by setting WS_EX_LAYERED using SetWindowLong after the window has been created.

In Delphi a custom layered window is best created with SetWindowLong since TForm’s own transparency support messes with the WS_EX_LAYERED flag when the window is created.

SetLayeredWindowAttributes

The easy, and most limited, method to use layered windows is through the SetLayeredWindowAttributes API. SetLayeredWindowAttributes causes the window to redirect the normal, WM_PAINT based, drawing of the window into an off-screen bitmap, where the desired effect is applied before the bitmap is drawn onto the screen.

The advantage of SetLayeredWindowAttributes is that it can be applied to existing code with little or no modifications. The limitations are that it only supports uniform- and color key based translucency

Delphi’s native form transparency is implemented with the SetLayeredWindowAttributes API.

UpdateLayeredWindow

Another, and more powerful, way to use layered windows is via the UpdateLayeredWindow API. Where as the SetLayeredWindowAttributes API depends on the application’s regular handling of WM_PAINT messages, the UpdateLayeredWindow API completely does away with them; When using UpdateLayeredWindow the application doesn’t need to handle WM_PAINT or other painting messages. Instead the application must provide UpdateLayeredWindow with a 32-bit alpha blended bitmap and the desired color key and transparency values.

The primary advantage of UpdateLayeredWindow is the support for per-pixel alpha blending. The disadvantage is that the application cannot rely on the normal WM_PAINT mechanism to draw controls.

For this application we need alpha blended translucency and thus the UpdateLayeredWindow API.

Alpha Blending

Typically a color bitmap image has three channels: Red, Green and Blue (RGB). In the case of a 24 bit bitmap, each pixel is divided into three channels of 8 bits each; 8 bits for Red, 8 bits for Green and 8 bits for Blue. An Alpha Channel is just like any one of the RGB color channels, except its value doesn’t represent a color value, but rather a transparency value. In most alpha channels a value of zero means completely transparent while a value of 255 means completely opaque or not transparent. Any value in between means partly transparent. Since the color channels requires 24 bits and the Alpha channel uses 8 bits, we need 32 bit per pixel for a color bitmap with an Alpha channel.

Most image editing tools can save 32 bit windows bitmaps with an alpha channel (also known as RGBA, “A” being the Alpha channel), but unfortunately UpdateLayeredWindow is a bit of a snob and doesn’t eat regular alpha blended bitmap; UpdateLayeredWindow only works correctly with premultiplied alpha.

Premultiplied Alpha

Premultiplied Alpha means that the Red, Blue and Green color channels have already been multiplied with the Alpha channel. The premultiplication is performed after the following formula: Color = Color * Alpha / 255. Or to put it another way:

Red := MulDiv(Red, Alpha, 255);
Blue := MulDiv(Blue, Alpha, 255);
Green := MulDiv(Green, Alpha, 255);

This calculation is fairly easy to perform at run-time, and I will show you an example of how to do it in a later tutorial, but I will also demonstrate how to create a premultiplied bitmap in PhotoShop so you don’t have to fiddle with the pixels at run-time.

The reason UpdateLayeredWindow requires premultiplied alpha is probably to improve run-time performance by moving an operation, that has to be performed under all circumstances, from run-time to design-time. In my opinion it would have been nice if the API had also supported regular alpha blended bitmaps. After all, on modern hardware, it only takes a few µS to premultiply a large bitmap at run-time.

OK, enough of the theory. Let’s try this stuff out.