Delphi Tripous

About Borland (then Inprise, then CodeGear and now Embarcadero) Delphi and computer programming

Saturday, February 9, 2008

 

The GradientFill() function

Windows API provides the GradientFill() function which fills rectangular and triangular areas. The problem with that function, as usually, is its documentation. Another problem is that Delphi's 7 import of the function is not correct. So here is a correct header.

  function GradientFill(DC: THandle; Vertex: PTriVertex; NumVertex: ULONG; Mesh: Pointer; NumMesh, Mode: ULONG): BOOL;
stdcall; external 'msimg32.dll' name 'GradientFill';
After playing with the function, searching the internet and reading the documentation about the function and the structures it utilizes I concluded the following regarding its usage and parameters.

In order to use that GradientFill() function the programmer has to decide what is the best way to split the whole area is going to fill. Two choices here. Rectangles or triangles. This is the role of the Mode parameter.

Rectangles and triangles are defined by using the x and y fields of a TTriVertex structure. It takes two TTriVertex structures to define a rectangle (TopLeft and BottomRight) and it takes three TTriVertex stuctures to define a triangle, the three edges of it. Those TTriVertex structures are passed in the Vertex parameter which is a pointer to an array of TTriVertex structures.

Now because each of those TTriVertex structures defines just a point, the programmer has to defined which points form a single rectangle or triangle. This is done by using another pointer to an array parameter, the Mesh parameter. If rectangles are used then the element of the Mesh array is a TGradientRect stucture. If triangles are used then the element of the Mesh array is a TGradientTriangle structure. That's why the Mesh is an untyped pointer. And guess what. The integer fields both of those TGradientRect and TGradientTriangle structures are just indexes in the Vertex array of points, sorry, TTriVertex structures. A TTriVertex structure defines not only a point but a color as well. That is all.

Many thanks to Joseph M. Newcomer, the author of the Gradient Filler application found at http://www.flounder.com/gradientfiller.htm and the valuable information.

Parameteres in a depth

Mode, the last parameter, defines the gradient mode. The Mode controls the interpretation of Vertex and Mesh array parameteres. Valid values are

GRADIENT_FILL_RECT_H
GRADIENT_FILL_RECT_V
GRADIENT_FILL_TRIANGLE

When Mode is one of the two RECT modes (GRADIENT_FILL_RECT_H or GRADIENT_FILL_RECT_V) it means that the function fills rectangular areas. The TTriVertex structures, the Vertex array parameter contains, are used in pairs in order to define the end points (TopLeft, BottomRight) of a single rectangle.

In that case the Mesh untyped pointer parameter is an array of TGradientRect structures, a single TGradientRect structure for each pair of TTriVertex of the Vertex array param. TGradientRect is defined as

   TGradientRect = packed record
UpperLeft: ULONG;
LowerRight: ULONG;
end;

where its two fields (UpperLeft and LowerRight) are indices of a TTriVertex in the Vertex array.

When Mode is GRADIENT_FILL_TRIANGLE it means that the function fills triangular areas. The TTriVertex structures, the Vertex array parameter contains, are used in groups of three in order to define the end points of a single triangle.

In that case the Mesh untyped pointer parameter is an array of TGradientTriangle structures. TGradientTriangle is defined as

    TGradientTriangle = packed record
Vertex1: ULONG;
Vertex2: ULONG;
Vertex3: ULONG;
end;
where its three fields are indices of a TTriVertex in the Vertex array.

So, the Mesh untyped pointer parameter is an array of TGradientRect or TGradientTriangle structures. NumMesh is the number of the elements in that array.

NumVertex is the number of the elements in the Vertex array parameter. The element type of the Vertex parameter is TTriVertex, which is defined as

    TTriVertex = packed record
x : Longint;
y : Longint;
Red : COLOR16;
Green : COLOR16;
Blue : COLOR16;
Alpha : COLOR16;
end;
where X and Y define the starting point in the DC, the first parameter, and Red, Green and Blue define the color. Alpha is not used, according to documentation.

And some code

The DrawGradient() function, below, considers the whole area as a single rectangle. That's the easiest way to use the GradientFill() API function. Here is the easiest way to use it.

  procedure TForm1.Button1Click(Sender: TObject);
begin
DrawGradient(Canvas, ClientRect, clSilver, clNavy);
end;
The DrawGradientFocal() splits the area into eight triangles. It takes nine points to split the passed rectangle into eight triangles. So using the DrawGradientFocal() the programmer may define which of those nine points is the focal point. The point where to start using the StartColor.

Here is the code.


unit Unit2;

interface

uses
Windows
,SysUtils
,Classes
,Graphics
,Controls
;

type
TGradientDirection = (gdLeftRight, gdTopBottom);

procedure DrawGradient(Canvas: TCanvas; R: TRect; StartColor, EndColor: TColor; Direction: TGradientDirection = gdLeftRight);
procedure DrawGradientFocal(Canvas: TCanvas; R: TRect; StartColor, EndColor: TColor; FocalPoint: Integer);

implementation

type
COLOR16 = Word; { is declared as Shortint in Delphi 7 }

PTriVertex = ^TTriVertex;
_TRIVERTEX = packed record
x : Longint;
y : Longint;
Red : COLOR16;
Green : COLOR16;
Blue : COLOR16;
Alpha : COLOR16;
end;
TTriVertex = _TRIVERTEX;
TRIVERTEX = _TRIVERTEX;



function WindowsGradientFill(DC: THandle; Vertex: PTriVertex; NumVertex: ULONG; Mesh: Pointer; NumMesh, Mode: ULONG): BOOL;
stdcall; external 'msimg32.dll' name 'GradientFill';


procedure SetVertex(var Vertex: TTriVertex; X, Y: Integer; Color: TColor);
begin
Vertex.x := X;
Vertex.y := Y;
Vertex.Red := (Color and $000000FF) shl 8;
Vertex.Green := (Color and $0000FF00);
Vertex.Blue := (Color and $00FF0000) shr 8;
Vertex.Alpha := 0;
end;

procedure SetTriangle(var Triangle: TGradientTriangle; Vertex1, Vertex2, Vertex3: Integer);
begin
Triangle.Vertex1 := Vertex1;
Triangle.Vertex2 := Vertex2;
Triangle.Vertex3 := Vertex3;
end;

procedure DrawGradient(Canvas: TCanvas; R: TRect; StartColor, EndColor: TColor; Direction: TGradientDirection);
var
Vertices : array[0..1] of TTriVertex;
Rects : array[0..0] of TGradientRect;
Mode : ULONG;
begin
SetVertex(Vertices[0], R.Left, R.Top, StartColor);
SetVertex(Vertices[1], R.Right, R.Bottom, EndColor);

Rects[0].UpperLeft := 0;
Rects[0].LowerRight := 1;

if Direction = gdLeftRight then
Mode := GRADIENT_FILL_RECT_H
else
Mode := GRADIENT_FILL_RECT_V;

WindowsGradientFill(Canvas.Handle, @Vertices, 2, @Rects, 1, Mode);
end;

procedure DrawGradientFocal(Canvas: TCanvas; R: TRect; StartColor, EndColor: TColor; FocalPoint: Integer);
var
Vertices : array[0..8] of TTriVertex;
Triangles : array[0..7] of TGradientTriangle;
MiddleX : Integer;
MiddleY : Integer;
begin
MiddleX := R.Left + (WidthOf(R) div 2);
MiddleY := R.Top + (HeightOf(R) div 2);

SetVertex(Vertices[0], R.Left ,R.Top ,EndColor);
SetVertex(Vertices[1], MiddleX ,R.Top ,EndColor);
SetVertex(Vertices[2], R.Right ,R.Top ,EndColor);
SetVertex(Vertices[3], R.Left ,MiddleY ,EndColor);
SetVertex(Vertices[4], MiddleX ,MiddleY ,EndColor);
SetVertex(Vertices[5], R.Right ,MiddleY ,EndColor);
SetVertex(Vertices[6], R.Left ,R.Bottom ,EndColor);
SetVertex(Vertices[7], MiddleX ,R.Bottom ,EndColor);
SetVertex(Vertices[8], R.Right ,R.Bottom ,EndColor);

if not (FocalPoint in [0..8]) then
FocalPoint := 4;

SetVertex(Vertices[FocalPoint], Vertices[FocalPoint].X, Vertices[FocalPoint].Y, StartColor);


SetTriangle(Triangles[0], 0, 1, 4);
SetTriangle(Triangles[1], 0, 4, 3);
SetTriangle(Triangles[2], 1, 2, 4);
SetTriangle(Triangles[3], 2, 5, 4);
SetTriangle(Triangles[4], 4, 5, 8);
SetTriangle(Triangles[5], 4, 7, 8);
SetTriangle(Triangles[6], 4, 7, 6);
SetTriangle(Triangles[7], 3, 4, 6);

WindowsGradientFill(Canvas.Handle, @Vertices, 9, @Triangles, 8, GRADIENT_FILL_TRIANGLE);

end;

end.

Sunday, January 27, 2008

 

Visual code

Years ago. There was a rumor. Borland (then) is preparing a “VB killer”. Google “VB killer Delphi” and you'll see what I mean. And then Delphi 1 came out.

Was Delphi 1 the long-roumored “VB killer”? Well, I can't tell for sure. For me, yes it was. I abandoned VB and never looked back.

Delphi, for me, was an apocalypse. You see Delphi provides all, well most of, the code of its library, the VCL. It gives you a tremendous opportunity to see and study code written by the best of best. And thus become a better programmer. It was a valuable tool for me and, I believe, many many others. Thank you Borlanders. Thank you very much.

But that “VB killer” notion is not always the best path to go. You see those modern IDEs like Delphi provide great flexibility and tools in building the user interface at design time. Delphi pushed that idea to a limit. Ok, its nice to have design time facilities like the Component Pallette, the Form Designer and the Object Inspector. Its really productive to be able to drop user interface elements on a window, set them up, connect them, etc etc.

But, hey, what a TDataset or a TTransaction component has to do with building a user interface? Or the TXMLDocument? Come on...

Ok. I understand that its not that bad thing to have a “designable” TTimer or TImageList component. But we have to remember that a (pascalish) IDE should promote best coding practices. What is the best place to have, say, a TQuery component initialized and activated? The Object Inspector or the constructor of a form? Or better the constructor of a business object?

You see when a programmer sets up properties using the Object Inspector he writes code. But that code goes to the .dfm file. Hidden. It's not there in the source code unit, along with the rest of the code, to see it and check it out when you reopen the project six months later.

My point is: User interface elements such as buttons and edit buttons are user interface elements and their place is a form. At design time they controlled by using the Object Inspector. And that's great. Non user interface elements should be created and initialized in a constructor. Most of the time. Non user interface elements are usually part of the business logic. So why not keep business logic code in a single place? Why hide it to a .dfm resource file? Does this technique promote maintainability? Readability of the code? No, in my humble opinion.

After all a programmer is a person who not only writes code, but he reads code too.


Archives

January 2008   February 2008  

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]