Developing with Graphics/pt

From Free Pascal wiki
Jump to: navigation, search

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)

Visão Geral

Esta página é o início de uma série de tutoriais a respeito da manipulação de Bitmaps e outros graficos. Como não sou um programador gráfico, eu convido a todos para contribuir. Basta apenas adicionar um link na próxima seção, adicionar uma página e criar seu próprio artigo Wiki.

Nesta página algumas informações gerais serão fornecidas.

Outros Tutoriais Gráficos

  • GLScene - Uma porta para a biblioteca de gráficos visuais OpenGL GLScene
  • TAChart/pt - Componente gráfico para Lazarus
  • PascalMagick - Uma forma fácil de usar a API com interface para ImageMagick, um pacote livre multi-plataforma para criar, editar e organizar imagens bitmaps.
  • PlotPanel - Um componente para traçar e realizar gráficos animados.

Trabalhando com TBitmap

A primeira idéia a ser lembrada é que Lazarus foi desenvolvido para ser multiplataforma, assim o uso de métodos da API do windows estão fora de questão. Deste modo o método ScanLine não é suportado pelo Lazarus pois ele é planejado para Dispositivo de Bitmap Independente (DIB) e utiliza uma função da [biblioteca] GDI32.dll

Um exemplo de desvanescimento (fading)

Digamos que você deseja fazer uma figura desaparecer lentamente. No Delphi você pode fazer algo como:

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;  //  ou 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;

Esta função no Lazarus pode ser implementada como:

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;

O código Lazarus desta página foi pego do projeto $LazarusPath/examples/lazintfimage/fadein1.lpi. Deste modo se você quiser começar bem com programação gráfica veja este exemplo.

Desenhando bitmaps com cor transparente

Uma nova habilidade, implementada no Lazarus 0.9.11, é os bitmaps com cor transparente. Arquivos de Bitmap (*.BMP) não podem guardar informações sobre transparencia, mas podem trabalhar como se contivessem se definirmos uma cor para representar a área transparente. Este é um truque comum utilizado por aplicativos do Windows.

O exemplo a seguir carrega um bitmap guardado dentro do executável como um recurso do Windows, seleciona uma cor para ser transparente (clFuchsia) e desenha a imagem para um 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;

Note as operações de memória executadas com o TMemoryStream. Elas são necessárias para assegurar que a imagem seja carregada corretamente.

Gráficos em movimento - Como evitar a tremulação

Muitos programas desenham gráficos 2D na interface de usuário. Caso estes gráficos precisem mudar rapida e continuamente você logo irá se deparar com um problema: A tremulação. Isto ocorre quando o usuário as vezes vê a imagem inteira e as vezes a imagem apenas parcialmente desenhada. Isto ocorre pois o processo de pintura demanda tempo.

Mas como evitar a tremulação e obter a melhor performance de desenho? É claro que você pode trabalhar com aceleração de hardware usando o OpenGL, mas esta abordagem pode ser muito pesada para programas pequenos ou computadores antigos. Este tutorial foca no desenho utilizando o objeto TCanvas. Se você precisa de ajuda com o OpenGL observe os exemplos que vêm com o Lazarus. Você também pode usar o A.J. Venter's gamepack, que provê um canvas com buffer duplo e um componente de sprite.

Agora nós examinaremos as opções que temos para desenhar na tela:

Desenhando num TImage

Nunca use o evento OnPaint para desenhar na TImage. O TImage é armazenado temporariamente , e assim tudo que você precisa fazer é desenhar em qualquer lugar e e a mudança será para sempre. Todavia, se você está constantemente redesenhando, a imagem tremulará. Neste caso pode tentar outras opções. Desenhar com o TImage é considerado lento comparado a outras abordagens.

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;

Desenhando no evento OnPaint

Neste caso todo o desenho tem que ser feito no evento OnPaint. Não permanece no armazenamento temporário, como no evento TImage.

Criando um controle personalizado que se desenha

Utilizar um controle personalizado possuí a vantagem de estruturar o código e permitir o reuso do controle. Esta abordagem é muito rápida, mas pode gerar tremulação caso a imagem não seja primeiro desenhada para um TBitmap e depois passada para a tela. Neste caso não há necessidade de utilizar o evento OnPaint do controle.

Aqui está um controle personalizado como exemplo:

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;

e como o criamos numa janela:

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;

apenas não esqueça de destrui-lo:

procedure TMyForm.FormDestroy(Sender: TObject);
begin
  MyDrawingControl.Free;
end;

Atribuir zero às propriedades Top e Left não é necessário, logo que essa é a posição padrão, mas isso é feito para reforçar onde o controle será posto.

"MyDrawingControl.Parent := Self;" é muito importante e você não verá seu controle se não o fizer.

"MyDrawingControl.DoubleBuffered := True;" é necessário para evitar flutuação/tremulação no Windows. Mas isto não faz efeito com GTK.

Utilizando o 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