Developing with Graphics
Overview
This page will be the start for tutorials with regard to manipulating Bitmaps and other graphics in your program. As I'm not a graphics programmer I invite all who are, to share their expertise! Just add a link to the next section, add a page and create your own WiKi article. On this page some general information will be given.
Other graphics articles
- GLScene - A port of the visual OpenGL graphics Library GLScene
- TAChart - Charting component for Lazarus
Working with TBitmap
The first thing to remember is that Lazarus is meant to be platform independent, so any methods using Windows API functionality are out of the question. So a method like ScanLine is not supported by Lazarus because it is intended for Device Independant Bitmap and uses functions from the GDI32.dll.
Be careful that if you do not specify the width and height of your TBitmap it will have the standard one, witch is quite small.
A fading example
Say you want to make a Fading picture. In Delphi you could do something like:
type
PRGBTripleArray = ^TRGBTripleArray;
TRGBTripleArray = array[0..32767] of TRGBTriple;
procedure TForm1.FadeIn(aBitMap: TBitMap);
var
Bitmap, BaseBitmap: TBitmap;
Row, BaseRow: PRGBTripleArray;
x, y, step: integer;
begin
Bitmap := TBitmap.Create;
try
Bitmap.PixelFormat := pf32bit; // or pf24bit
Bitmap.Assign(aBitMap);
BaseBitmap := TBitmap.Create;
try
BaseBitmap.PixelFormat := pf32bit;
BaseBitmap.Assign(Bitmap);
for step := 0 to 32 do begin
for y := 0 to (Bitmap.Height - 1) do begin
BaseRow := BaseBitmap.Scanline[y];
Row := Bitmap.Scanline[y];
for x := 0 to (Bitmap.Width - 1) do begin
Row[x].rgbtRed := (step * BaseRow[x].rgbtRed) shr 5;
Row[x].rgbtGreen := (step * BaseRow[x].rgbtGreen) shr 5; // Fading
Row[x].rgbtBlue := (step * BaseRow[x].rgbtBlue) shr 5;
end;
end;
Form1.Canvas.Draw(0, 0, Bitmap);
InvalidateRect(Form1.Handle, nil, False);
RedrawWindow(Form1.Handle, nil, 0, RDW_UPDATENOW);
end;
finally
BaseBitmap.Free;
end;
finally
Bitmap.Free;
end;
end;
This function in Lazarus could be implemented like:
procedure TForm1.FadeIn(ABitMap: TBitMap);
var
SrcIntfImg, TempIntfImg: TLazIntfImage;
ImgHandle,ImgMaskHandle: HBitmap;
FadeStep: Integer;
px, py: Integer;
CurColor: TFPColor;
TempBitmap: TBitmap;
begin
SrcIntfImg:=TLazIntfImage.Create(0,0);
SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);
TempIntfImg:=TLazIntfImage.Create(0,0);
TempIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);
TempBitmap:=TBitmap.Create;
for FadeStep:=1 to 32 do begin
for py:=0 to SrcIntfImg.Height-1 do begin
for px:=0 to SrcIntfImg.Width-1 do begin
CurColor:=SrcIntfImg.Colors[px,py];
CurColor.Red:=(CurColor.Red*FadeStep) shr 5;
CurColor.Green:=(CurColor.Green*FadeStep) shr 5;
CurColor.Blue:=(CurColor.Blue*FadeStep) shr 5;
TempIntfImg.Colors[px,py]:=CurColor;
end;
end;
TempIntfImg.CreateBitmap(ImgHandle,ImgMaskHandle,false);
TempBitmap.Handle:=ImgHandle;
TempBitmap.MaskHandle:=ImgMaskHandle;
Canvas.Draw(0,0,TempBitmap);
end;
SrcIntfImg.Free;
TempIntfImg.Free;
TempBitmap.Free;
end;
The Lazarus code on this page has been taken from the $LazarusPath/examples/lazintfimage/fadein1.lpi project. So if you want a flying start with graphics programming take a closer look at this example.
Motion Graphics - How to Avoid flickering
Some times there is a need to draw 2D graphics which are quickly changing. There are many options on how to do that. The first choice is about whether you will work with hardware acceleration using something like OpenGL or simply use the Canvas. OpenGL has the best speed on new PCs, specially those with good video cards, but it is quite heavy for simple programs or old computers, so we will be drawing to the standard Canvas here. If you need help with OpenGL, take a look at the example that comes with Lazarus.
Now we will examine the options we have for drawing on a TCanvas:
- Draw to a TImage
- Draw on the OnPaint event of the form, a TPaintBox or another control
- Create a custom control which draws itself
Draw to a TImage
Never use the OnPaint event to draw to a TImage. A TImage is buffered so all you need to do is draw to it from anywhere and the change is there forever. However, if you are constantly redrawing, the image will flicker. In this case you can try the other options. Drawing to a TImage is considered slower then the other approaches.
procedure TForm1.BitBtn1Click(Sender: TObject);
var
x, y: Integer;
begin
// Draws the backgroung
Image.Canvas.Pen.Color := clWhite;
Image.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
// Draws squares
Bitmap.Canvas.Pen.Color := clBlack;
for x := 1 to 8 do
for y := 1 to 8 do
Image.Canvas.Rectangle(Round((x - 1) * Image.Width / 8), Round((y - 1) * Image.Height / 8),
Round(x * Image.Width / 8), Round(y * Image.Height / 8));
end;
Draw on the OnPaint event
In this case all the drawing has to be done on the OnPaint event. It doesn't remain on the buffer, like on the TImage.
Create a custom control which draws itself
Creating a custom control has the advantage of structuring your code and you can reuse the control. This approach is quite fast, but it can still generate flickering if you don't draw to a TBitmap first and then draw to the canvas. On this case, the use of the OnPaint event of the custom control will probably generate flickering.
Here is an example custom control:
type
TMyDrawingControl = class(TCustomControl)
public
procedure Paint; override;
end;
implementation
procedure TMyDrawingControl.Paint;
var
x, y: Integer;
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
// Initializes the Bitmap Size
Bitmap.Height := Height;
Bitmap.Width := Width;
// Draws the background
Bitmap.Canvas.Pen.Color := clWhite;
Bitmap.Canvas.Rectangle(0, 0, Width, Height);
// Draws squares
Bitmap.Canvas.Pen.Color := clBlack;
for x := 1 to 8 do
for y := 1 to 8 do
Bitmap.Canvas.Rectangle(Round((x - 1) * Width / 8), Round((y - 1) * Height / 8),
Round(x * Width / 8), Round(y * Height / 8));
Canvas.Draw(0, 0, Bitmap);
finally
Bitmap.Free;
end;
inherited Paint;
end;
and how we create it on the form:
procedure TMyForm.FormCreate(Sender: TObject);
begin
MyDrawingControl:= TMyDrawingControl.Create(Self);
MyDrawingControl.Height := 400;
MyDrawingControl.Width := 500;
MyDrawingControl.Top := 0;
MyDrawingControl.Left := 0;
MyDrawingControl.Parent := Self;
MyDrawingControl.DoubleBuffered := True;
end;
just don´t forget to destroy it:
procedure TMyForm.FormDestroy(Sender: TObject);
begin
MyDrawingControl.Free;
end;
Setting Top and Left to zero is not necessary, since this is the standard position, but is done so to reinforce where the control will be put.
"MyDrawingControl.Parent := Self;" is very important and you won't see your control if you don't do so.
"MyDrawingControl.DoubleBuffered := True;" is required to avoid flickering on Windows. It has no effect on gtk.