Developing with Graphics/pt
│
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
- Desenhando no evento OnPaint de uma janela, TPaintBox ou outro controle
- Criando um controle personalizado que se desenha
- Utilizando o A.J. Venter's gamepack
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