Difference between revisions of "Developing with Graphics"

From Free Pascal wiki
Jump to navigationJump to search
m
Line 97: Line 97:
  
 
Now we will examine the options we have for drawing on a TCanvas:
 
Now we will examine the options we have for drawing on a TCanvas:
* Draw to a TImage
+
* [[#Draw to a TImage|Draw to a TImage]]
 
* Draw on the OnPaint event of the form, a TPaintBox or another control
 
* Draw on the OnPaint event of the form, a TPaintBox or another control
 
* Create a custom control witch draws itself
 
* Create a custom control witch draws itself

Revision as of 13:35, 2 October 2005

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 tutorials

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.

Avoid flickering

Some times there is a need to draw 2D graphics witch are quickly changing. Now 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 witch 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 backgroun
 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 witch 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

 TTela = class(TCustomControl)
 public
   procedure Paint; override;
 end;

implementation

procedure TTela.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 TfrmPrincipal.FormCreate(Sender: TObject); begin

 Tela := TTela.Create(Self);
 Tela.Height := 400;
 Tela.Width := 500;
 Tela.Top := 0;
 Tela.Left := 0;
 Tela.Parent := Self;

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.

Setting "Tela.Parent := Self;" is very important and you won't see your control if you don't do so.