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 PhotoShophere, 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 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
andTForm.TransparentColorValue
properties.
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
andTForm.AlphaBlendValue
properties.
Alpha Blended Bitmap 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 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 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
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;
Windows 2000 - Layered Windows
The concept of layered windows was introduced in Windows 2000 beta 3 with theWS_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: TheUpdateLayeredWindow
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 theSetLayeredWindowAttributes
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 theSetLayeredWindowAttributes
API.
UpdateLayeredWindow
Another, and more powerful, way to use layered windows is via theUpdateLayeredWindow
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 normalWM_PAINT
mechanism to draw controls.
For this application we need alpha blended translucency and thus theUpdateLayeredWindow
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 unfortunatelyUpdateLayeredWindow
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.
Create the Image
The first thing to do is to create some stunning graphics for your splash screen. My artistic skills are somewhat, um, limited, so I usually just go with a logo or thematic symbol of some sort and apply some effects. Since we would like to show of the alpha blending, a fat drop shadow, some glow and maybe a lense flare is probably a good choice.
If you are lazy there’s plenty of free goodies you can use to get started. The important thing for now is that we should end up with an image that contains some semitransparent areas.
Creating a premultiplied bitmap in PhotoShop
With your PhotoShop image at hand you should now be ready to practice the black art of creating a premultiplied bitmap. From my research on this topic it appears to be a closely guarded secret known only to a select few ninja PhotoShoppers, as none of the instructions I have found worked for me. The following however works:
- Open or create a transparent image in PhotoShop.
- Merge Visible Layers (
Shift+Ctrl+E
).
This flattens the image while keeping the transparency. - Create a new Solid Color Fill Layer (Layer, New Fill Layer, Solid Color…).
Set the fill color to black. - Move the Fill Layer to the bottom (
Shift+Ctrl+[
). - Auto-select the Image Layer (
Ctrl+Click
on the layer or Right-click on the layer+Select Pixels). - Switch to the Channels tab and Save selection as channel (
).
- Save the image as a BMP (make sure Alpha Channels is enabled and checked).
Under Advanced Modes, select the 32-bit, A8R8G8B8 format. - Presto!
With any luck you should now have created the magic bitmap. Now we just need an application to display it, so without further ado we move on to…
Displaying an Alpha Blended Bitmap
Ready? OK, fire up Delphi and… download the source code. I won’t guide you through the steps needed to create this tiny application. Instead let me explain the key points while you browse through the code:
The project file has been modified so the splash form isn’t created withTApplication.CreateForm
. This is important because the first form that is created withTApplication.CreateForm
automatically becomes the application’s main form and we don’t want that to happen. Why? Because the application terminates once the main form is destroyed. Instead the splash form is created anonymously and itsExecute
method is called.
program AlphaSplashDemo;
...
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
with TFormSplash.Create(Application) do
Execute;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
Moving on to the splash form, the Position
property has been set to poScreenCenter
so the form will center itself on the screen and the FormStyle
property has been set to fsStayOnTop
so it will stay on top of other forms.
The OnFormClose
event handler makes sure the form is destroyed when it it closed and the OnKeyPress
event handler just makes it easier to close the form during tests.
procedure TFormSplash.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
procedure TFormSplash.FormKeyPress(Sender: TObject; var Key: Char);
begin
Close;
end;
The WM_NCHITTEST
message handler makes it possible to move the form even though it hasn’t got a caption or a border.
procedure TFormSplash.WMNCHitTest(var Message: TWMNCHitTest);
begin
Message.Result := HTCAPTION;
end;
The Execute
method contains the meat. In it we set the WS_EX_LAYERED
window flag, load the bitmap and resize the window to fit the bitmap. Then the alpha blending parameters are prepared and UpdateLayeredWindow
is called. Finally we start a timer to close the splash form after a short while. Don’t display the splash form for too long or you will just annoy your users.
procedure TFormSplash.Execute;
var
BlendFunction: TBlendFunction;
BitmapPos: TPoint;
BitmapSize: TSize;
exStyle: DWORD;
Bitmap: TBitmap;
begin
// Enable window layering
exStyle := GetWindowLongA(Handle, GWL_EXSTYLE);
if (exStyle and WS_EX_LAYERED = 0) then
SetWindowLong(Handle, GWL_EXSTYLE, exStyle or WS_EX_LAYERED);
Bitmap := TBitmap.Create;
try
Bitmap.LoadFromFile('splash.bmp');
ASSERT(Bitmap.PixelFormat = pf32bit,
'Wrong bitmap format - must be 32 bits/pixel');
// Resize form to fit bitmap
ClientWidth := Bitmap.Width;
ClientHeight := Bitmap.Height;
// Position bitmap on form
BitmapPos := Point(0, 0);
BitmapSize.cx := Bitmap.Width;
BitmapSize.cy := Bitmap.Height;
// Setup alpha blending parameters
BlendFunction.BlendOp := AC_SRC_OVER;
BlendFunction.BlendFlags := 0;
BlendFunction.SourceConstantAlpha := 255;
BlendFunction.AlphaFormat := AC_SRC_ALPHA;
// ... and action!
UpdateLayeredWindow(Handle, 0, nil, @BitmapSize, Bitmap.Canvas.Handle,
@BitmapPos, 0, @BlendFunction, ULW_ALPHA);
Show;
finally
Bitmap.Free;
end;
// Start timer to hide form after a short while
TimerSplash.Enabled := True;
end;
When you run the demo application, with the sample bitmap, it should look something like this:
You can replace the bitmap I have provided with your own. Just copy it to the demo folder and name it splash.bmp.
What’s next?
In this part we used a windows bitmap for the splash screen. While this works just fine, 32-bit bitmaps can easily get quite big (the demo bitmap is almost 1Mb in size) and they add a considerable overhead to an application - with little benefit to the end user. Wouldn’t it be nice if we could compress the bitmap to minimize the overhead?
In the second part of this article, I will modify the demo application to use a compressed PNG image instead of a BMP. I will also move the bitmap to a resource file, demonstrate run-time premultiplication and enhance the splash screen with a few visual gimmicks.