Difference between revisions of "Developing with Graphics"
Sekelsenmat (talk | contribs) |
|||
Line 136: | Line 136: | ||
==Motion Graphics - How to Avoid flickering== | ==Motion Graphics - How to Avoid flickering== | ||
− | + | Many programs draw their output to the GUI as 2D graphics. If those graphics need to change quickly you will soon face a problem: quickly changing graphics often flicker on the screen. This happens when the users sometimes sees the hole images and sometimes sees it when it is only partially drawn. It occurs because the painting process requires time. | |
+ | |||
+ | But how can I avoid the flickering and get the best drawing speed? Of course you could work with hardware acceleration using OpenGL, but this approach is quite heavy for small programs or old computers. This tutorial will focus on drawing to a TCanvas. If you need help with OpenGL, take a look at the example that comes with Lazarus. You can also use A.J. Venter's gamepack, witch provides a double-buffered canvas and a sprite component. | ||
Now we will examine the options we have for drawing to a Canvas: | Now we will examine the options we have for drawing to a Canvas: | ||
Line 153: | Line 155: | ||
begin | begin | ||
// Draws the backgroung | // Draws the backgroung | ||
− | + | MyImage.Canvas.Pen.Color := clWhite; | |
− | + | MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height); | |
// Draws squares | // Draws squares | ||
− | + | MyImage.Canvas.Pen.Color := clBlack; | |
for x := 1 to 8 do | for x := 1 to 8 do | ||
for y := 1 to 8 do | for y := 1 to 8 do | ||
− | + | MyImage.Canvas.Rectangle(Round((x - 1) * Image.Width / 8), Round((y - 1) * Image.Height / 8), | |
Round(x * Image.Width / 8), Round(y * Image.Height / 8)); | Round(x * Image.Width / 8), Round(y * Image.Height / 8)); | ||
end; | end; | ||
Line 166: | Line 168: | ||
===Draw on the OnPaint event=== | ===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. | + | In this case all the drawing has to be done on the OnPaint event of the form. It doesn't remain on the buffer, like on the TImage. |
===Create a custom control which draws itself=== | ===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 | + | Creating a custom control has the advantage of structuring your code and you can reuse the control. This approach is very 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 there is no need to use the OnPaint event of the control. |
Here is an example custom control: | Here is an example custom control: |
Revision as of 18:32, 13 December 2005
│
Deutsch (de) │
English (en) │
español (es) │
français (fr) │
italiano (it) │
日本語 (ja) │
한국어 (ko) │
Nederlands (nl) │
português (pt) │
русский (ru) │
slovenčina (sk) │
中文(中国大陆) (zh_CN) │
中文(台灣) (zh_TW) │
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, which 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.
Drawing color transparent bitmaps
A new feature implemented on Lazarus 0.9.11 are color transparent bitmaps. Bitmap files cannot contain any information about transparency on them, but they can work as they had if you select a color on them to represent the transparent area. This is a common trick used on Win32 applications.
The following example loads a bitmap from a Windows resource, selects a color to be transparent (clFuchsia) and then draws it to a canvas.
procedure MyForm.MyButtonOnClick(Sender: TObject); var buffer: THandle; bmp: TBitmap; memstream: TMemoryStream; begin bmp := TBitmap.Create; buffer := Windows.LoadBitmap(hInstance, MAKEINTRESOURCE(ResourceID)); if (buffer = 0) then exit; // Error loading the bitmap bmp.Handle := buffer; memstream := TMemoryStream.create; try bmp.SaveToStream(memstream); memstream.position := 0; bmp.LoadFromStream(memstream); finally memstream.free; end; bmp.Transparent := True; bmp.TransparentColor := clFuchsia; MyCanvas.Draw(0, 0, bmp); bmp.Free; // Release allocated resource end;
Notice the memory operations performed with the TMemoryStream. They are necessary to ensure the correct loading of the image.
Motion Graphics - How to Avoid flickering
Many programs draw their output to the GUI as 2D graphics. If those graphics need to change quickly you will soon face a problem: quickly changing graphics often flicker on the screen. This happens when the users sometimes sees the hole images and sometimes sees it when it is only partially drawn. It occurs because the painting process requires time.
But how can I avoid the flickering and get the best drawing speed? Of course you could work with hardware acceleration using OpenGL, but this approach is quite heavy for small programs or old computers. This tutorial will focus on drawing to a TCanvas. If you need help with OpenGL, take a look at the example that comes with Lazarus. You can also use A.J. Venter's gamepack, witch provides a double-buffered canvas and a sprite component.
Now we will examine the options we have for drawing to a Canvas:
- Draw to a TImage
- Draw on the OnPaint event of the form, a TPaintBox or another control
- Create a custom control which draws itself
- Using A.J. Venter's gamepack
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
MyImage.Canvas.Pen.Color := clWhite;
MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);
// Draws squares
MyImage.Canvas.Pen.Color := clBlack;
for x := 1 to 8 do
for y := 1 to 8 do
MyImage.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 of the form. 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 very 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 there is no need to use the OnPaint event of the control.
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.
Using A.J. Venter's gamepack
The gamepack approach is to draw everything to one double-buffered canvas, which only gets updated to the visible canvas when you are ready. This takes quite a bit of code, but it has the advantage of being able to do large rapidly changing scenes with multiple sprites on them. If you wish to use this approach, you may be interested in A.J. Venter's gamepack, a set of components for game development in Lazarus, which provides a double-buffered display area component as well as a sprite component, designed to integrate well with one another. You can get gamepack via subversion:
svn co svn://silentcoder.co.za/lazarus/gamepack