|
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
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:
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;
We read the first pixel of the form and take it as "transparent".
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).
We get rid of the title bar (if it's still bothering us). To do it:
BorderStyle := bsNone; // Titulo estorba / no Title
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.
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.
|