Help & Manual authoring tool
Delphi: Non-rectangular shapes - Part II

Non-rectangular shapes - Part II

Copyright © 2001 Alirio A. Gavidia B.. To see more of my articles
visit Programación Orientada en Delphi (Site in Spanish)
Translated by Ernesto De Spirito with the author's permission

KnowledgeBase Vortex 2.9

Introduction

This is the second part of an article oriented to the use of non- rectangular shapes with Delphi under Windows. In the first part we gave a non-rectangular shape to a form creating a hands watch as an example. In this part the intention is to be able to create forms whose shape are defined from some file external to the application. This behavior corresponds to the widely spread use of "Skins". For example "Windows Media Player".

Basic realization of "skins"

The first approach that we introduce here not only allows us to define the background, but also the shape of the form. In the market there is a VCL component that does this job and more. The referred component makes use of two BMP files, the first one with the figure in black and white and the second with the background in full color. In the present essay both characteristics are combined in one single file simply taking the color of the first pixel as the transparent color, actually like the color the that defines the limits of the shape (there is a little difference). The rest of the image, inside the shape, is taken as the background of the form.

Note: The use of a BMP has many cons. The major ones: size and scalability. For the first maybe the alternative would be a JPG, but the compression process alters the frontier of the shape generating intermediate colors, resulting in a blurred frontier. On the other hand we have the GIF format, but they have been banished from my routines, libraries and functions because certain company intends to get paid royalties for the use of the compression algorithms used in GIF files. Regarding scalability, one alternative is the use of a Metafile, better yet I suspect that the metafiles are definitions of regions completely analogous to those used here. However, I'll leave the study of Metafiles for another occasion.

A complete version of a "Skinner" requires to be able to define the background image of the form, its shape and also the one of each present control. We'll concentrate in the form leaving the controls aside.

The background

The problem of drawing the background is addressable in two ways as far as I know. One is intercepting some Windows message to draw on the canvas of the background (like some VCL controls do to place backgrounds in MDI and non-MDI forms) and the other is placing a TImage control as the last control in the form's client area. I'll choose the latter way because it'll make possible the use of some TImage events.

The shape

The problem of the shape basically resides in creating a region of a given shape. The determinant shape I have assumed is given thru a bitmap, so that for example a bitmap consisting of a blue circle over a white background must generate a form visible like a circle. The shape can be anything like the face of Mickey Mouse or the silhouette of a spider. For this purpose the bitmap has to be read to generate a polygonal shape out of it. This will give us freedom, but eliminates much the possibilities of scalability.

Step by step

In general terms, the algorithm goes like this:

  1. We adjust the form to the dimensions of the BMP file. This is simple. For the purpose, the following two lines will do it:

    // Ajustar el área / to Ajust the area
    ClientWidth := Image1.Width;
    ClientHeight:= Image1.Height;

  2. We read the first pixel of the form and take it as "transparent".

  3. We search for the first "non-transparent" pixel and from this point we generate a polygon with the silhouette of the "non-transparent" part (see GenRegion in the file ffigura.pas).

  4. We get rid of the title bar (if it's still bothering us). To do it:

    BorderStyle := bsNone; // Titulo estorba / no Title

  5. We change the shape of the form according to the polygon. To do it we make use of the CreatePolygonRgn and SetWindowRgn API functions (see ActiveSkin in ffigura.pas). Unlike the example of the first part of this article, in this one we don't define an elliptical (circular) region.

  6. We set from the TImage the necessary events for dragging the form like we did in the example of the first article.

Except for the third step, nothing seems too difficult. To generate the polygonal region we just look for the frontier of the figure, then we advance thru all the outline of the figure until we reach the starting point (or run out of memory, in this case 1024 points). This series of points define a polygon used to generate a region with the function CreatePolygonRgn. Then we use SetWindowRgn y we get the form with the given shape. It works but it has limitations: our figure must have only one frontier, therefore it can't be a figure with a hole in the middle (in any case the algorithm will ignore the hole in the middle).

As usual this article is accompanied with an example to illustrate the points referred here.

Note: The algorithm that scans the silhouette given in the bitmap is relatively slow and coarse, but is maintainable and performs a certain optimization with shapes that aren't very complex. There is a given limit since an array in memory is generated when we still don't know how many points will define the figure. It's up to the user to vary this limit to his/her convenience.

The controls

The example corresponding to this article also contains buttons with shapes. In design mode they are square because they are of TBitBtn type, but in the OnCreate event of the form each button is applied the shape of the graphics it contains in the same way as we did with the form in the ActiveSkin procedure:

procedure TForm1.FormCreate(Sender: TObject);
Var
  hRegion : Thandle;
  Par     : Array [0..1024] of TPoint; // Data -> region
  Cnt     : Integer;
begin
  Cnt := GenRegion(Par, BitBtn1.Glyph,
                  (BitBtn1.Width - BitBtn1.Glyph.Width+1) div 2,
                  (BitBtn1.Height - BitBtn1.Glyph.Height+1) div 2);
  hRegion := CreatePolygonRgn(Par, Cnt, ALTERNATE);
  SetWindowRgn(BitBtn1.handle, hRegion, true);
  ...

This shows the definition of regions for controls other than forms.

Going further

The problem of the BMP size remains latent, but we could pack the BMP and use it packed like Winamp does. There are two ways for this approach: we can use some almost-proprietary, semi-secret or little- common algorithm (inevitably I think of the Windows Media Player) or something like the zip. This last choice has the advantage that we are not limited regarding its use. You can find more information to know how to handle in the web site of Latium Software:

If the images are simple, like the ones I used in the example, you can save them using 16 colors and RLE 4 encoding (this can be done for example with the old but beloved Borland Resource Workshop that comes in the Delphi installation CD).

What others do

We should separate the areas of the form in client and non-client, and also considerer the captions. Eventually we could use semi- transparencies, what will become more common every day (thanks to Steve Jobs for the received favors, although not all of us use a Mac ;)

There's a library out there that considers shadows, animation, morphing and even a scripting language, but they go beyond the purpose of this article.

 
The full source code of the example is available for download.

JfControls Library - for Delphi and C++ Builder
Copyright © 2000/2006 Ernesto De Spirito.   All rights reserved.