Desvendando o Objeto TCanvas
Origem: A Bíblia do Lazarus, a enciclopédia livre.
No Delphi, o Objeto TCanvas nada mais faz que encapsular a API do Windows, para assim conseguir manipular os gráficos em programas.
Nas versões do Delphi, não havia diferença entre utilizar o TCanvas de cada componentes, ou a API do Windows. A única diferença é que caso você optasse pela API, você deveria saber qual o Handle do Objeto a ser "modificado", o que não é dificil de se descobrir...
Já no Kylix, a coisa mudou, o API não podia ser usada, não a do Windows...
E o Lazarus trouxe o melhor dos dois mundos: você utiliza o TCanvas e não precisa se preocupar com nenhuma API...
Se você já programou na unit GRAPH, seja em Turbo Pascal ou em Free Pascal, você não terá problemas para assimilar os conceitos do objeto TCanvas. Caso você ainda não tenha programado com gráficos, seja bem-vindo!
Um Objeto TCanvas manipula os gráficos de cada componente, e toda vez que esse componente recebe uma pintura (Invalidate, Repaint), tudo o que foi modificado pelo TCanvas desparece.
Porém existe uma excessão: no componente TImage, a modificação feita no objeto TCanvas do TImage continua...
Tenha em mente que nem todos os componentes da VCL/LCL contém um objeto TCanvas, mas a maioria o contém...
Vamos dar uma olhada no componente TCanvas do Lazarus, que descende da classe TFPCustomCanvas. Vejamos sua "apresentação" na unit Graphics:
TCanvas = class(TFPCustomCanvas)
private
FAutoRedraw: Boolean;
FState: TCanvasState;
FSavedFontHandle: HFont;
FSavedPenHandle: HPen;
FSavedBrushHandle: HBrush;
FSavedRegionHandle: HRGN;
FCopyMode: TCopyMode;
FHandle: HDC;
FOnChange: TNotifyEvent;
FOnChanging: TNotifyEvent;
FTextStyle: TTextStyle;
FLock: TCriticalSection;// FLock is initialized on demand
FRegion: TRegion;
FPen: TPen;
FFont: TFont;
FBrush: TBrush;
procedure BrushChanged(ABrush: TObject);
procedure FontChanged(AFont: TObject);
procedure PenChanged(APen: TObject);
procedure RegionChanged(ARegion: TObject);
function GetColor: TColor;
function GetHandle: HDC;
procedure SetAutoRedraw(Value: Boolean); virtual;
procedure SetColor(c: TColor);
procedure SetLazFont(value: TFont);
procedure SetLazPen(value: TPen);
procedure SetLazBrush(value: TBrush);
procedure SetRegion(Value: TRegion);
protected
function DoCreateDefaultFont: TFPCustomFont; override;
function DoCreateDefaultPen: TFPCustomPen; override;
function DoCreateDefaultBrush: TFPCustomBrush; override;
procedure SetColor(x, y: integer; const Value: TFPColor); override;
function GetColor(x, y: integer): TFPColor; override;
procedure SetHeight(AValue: integer); override;
function GetHeight: integer; override;
procedure SetWidth(AValue: integer); override;
function GetWidth: integer; override;
procedure SetPenPos(const AValue: TPoint); override;
procedure DoLockCanvas; override;
procedure DoUnlockCanvas; override;
procedure DoTextOut(x, y: integer; Text: string); override;
procedure DoGetTextSize(Text: string; var w,h:integer); override;
function DoGetTextHeight(Text: string): integer; override;
function DoGetTextWidth(Text: string): integer; override;
procedure DoRectangle(const Bounds: TRect); override;
procedure DoRectangleFill(const Bounds: TRect); override;
procedure DoRectangleAndFill(const Bounds: TRect); override;
procedure DoEllipse(const Bounds: TRect); override;
procedure DoEllipseFill(const Bounds: TRect); override;
procedure DoEllipseAndFill(const Bounds: TRect); override;
procedure DoPolygon(const Points: array of TPoint); override;
procedure DoPolygonFill(const Points: array of TPoint); override;
procedure DoPolygonAndFill(const Points: array of TPoint); override;
procedure DoPolyline(const Points: array of TPoint); override;
procedure DoFloodFill(x, y: integer); override;
procedure DoMoveTo(x, y: integer); override;
procedure DoLineTo(x, y: integer); override;
procedure DoLine(x1, y1, x2, y2: integer); override;
procedure DoCopyRect(x, y: integer; SrcCanvas: TFPCustomCanvas;
const SourceRect: TRect); override;
procedure DoDraw(x, y: integer; const Image: TFPCustomImage); override;
procedure CheckHelper(AHelper: TFPCanvasHelper); override;
protected
function GetClipRect: TRect; override;
Function GetPixel(X,Y: Integer): TColor; virtual;
procedure CreateBrush; virtual;
procedure CreateFont; virtual;
procedure CreateHandle; virtual;
Procedure CreatePen; virtual;
Procedure CreateRegion; virtual;
procedure DeselectHandles; virtual;
procedure PenChanging(APen: TObject); virtual;
procedure RealizeAutoRedraw; virtual;
procedure RequiredState(ReqState: TCanvasState); virtual;
procedure SetHandle(NewHandle: HDC); virtual;
procedure SetInternalPenPos(const Value: TPoint); virtual;
Procedure SetPixel(X,Y: Integer; Value: TColor); virtual;
public
constructor Create;
destructor Destroy; override;
procedure Lock; virtual;
procedure Unlock; virtual;
procedure Refresh; virtual;
procedure Changing; virtual;
procedure Changed; virtual;
// extra drawing methods (there are more in the ancestor TFPCustomCanvas)
procedure Arc(ALeft, ATop, ARight, ABottom, angle1, angle2: Integer); virtual;
procedure Arc(ALeft, ATop, ARight, ABottom, SX, SY, EX, EY: Integer); virtual;
Procedure BrushCopy(Dest: TRect; InternalImages: TBitmap; Src: TRect;
TransparentColor: TColor); virtual;
procedure Chord(x, y, AWidth, AHeight,
StartAngle16Deg, EndAngle16Deg: Integer); virtual;
procedure Chord(x, y, AWidth, AHeight, SX, SY, EX, EY: Integer); virtual;
Procedure CopyRect(const Dest: TRect; SrcCanvas: TCanvas;
const Source: TRect); virtual;
Procedure Draw(X,Y: Integer; SrcGraphic: TGraphic); virtual;
procedure StretchDraw(const DestRect: TRect; SrcGraphic: TGraphic); virtual;
procedure Ellipse(const ARect: TRect); // already in fpcanvas
procedure Ellipse(x1, y1, x2, y2: Integer); virtual; // already in fpcanvas
Procedure FillRect(const ARect: TRect); virtual;
Procedure FillRect(X1,Y1,X2,Y2: Integer);
procedure FloodFill(X, Y: Integer; FillColor: TColor;
FillStyle: TFillStyle); virtual;
procedure Frame3d(var ARect: TRect; const FrameWidth: integer;
const Style: TGraphicsBevelCut); virtual;
procedure Frame(const ARect: TRect); virtual; // border using pen
procedure Frame(X1,Y1,X2,Y2: Integer); // border using pen
procedure FrameRect(const ARect: TRect); virtual; // border using brush
procedure FrameRect(X1,Y1,X2,Y2: Integer); // border using brush
Procedure Line(X1,Y1,X2,Y2: Integer); virtual; // short for MoveTo();LineTo();
// already in fpcanvas (Line)
Procedure LineTo(X1,Y1: Integer); virtual; // already in fpcanvas
Procedure MoveTo(X1,Y1: Integer); virtual; // already in fpcanvas
procedure RadialPie(x,y,AWidth, AHeight,
StartAngle16Deg, EndAngle16Deg: Integer); virtual;
procedure RadialPie(x, y, AWidth, AHeight, sx, sy, ex, ey: Integer); virtual;
procedure Pie(EllipseX1,EllipseY1,EllipseX2,EllipseY2,
StartX,StartY,EndX,EndY: Integer); virtual;
procedure PolyBezier(Points: PPoint; NumPts: Integer;
Filled: boolean = False;
Continuous: boolean = False); virtual;
procedure PolyBezier(const Points: array of TPoint;
Filled: boolean = False;
Continuous: boolean = False);
procedure Polygon(const Points: array of TPoint;
Winding: Boolean;
StartIndex: Integer = 0;
NumPts: Integer = -1);
procedure Polygon(Points: PPoint; NumPts: Integer;
Winding: boolean = False); virtual;
Procedure Polygon(const Points: array of TPoint); // already in fpcanvas
procedure Polyline(const Points: array of TPoint;
StartIndex: Integer;
NumPts: Integer = -1);
procedure Polyline(Points: PPoint; NumPts: Integer); virtual;
procedure Polyline(const Points: array of TPoint); // already in fpcanvas
Procedure Rectangle(X1,Y1,X2,Y2: Integer); virtual; // already in fpcanvas
Procedure Rectangle(const ARect: TRect); // already in fpcanvas
Procedure RoundRect(X1, Y1, X2, Y2: Integer; RX,RY: Integer); virtual;
Procedure RoundRect(const Rect: TRect; RX,RY: Integer);
procedure TextOut(X,Y: Integer; const Text: String); virtual; // already in fpcanvas
procedure TextRect(const ARect: TRect; X, Y: integer; const Text: string);
procedure TextRect(ARect: TRect; X, Y: integer; const Text: string;
const Style: TTextStyle); virtual;
function TextExtent(const Text: string): TSize; virtual;
function TextHeight(const Text: string): Integer; virtual;
function TextWidth(const Text: string): Integer; virtual;
function HandleAllocated: boolean; virtual;
function GetUpdatedHandle(ReqState: TCanvasState): HDC; virtual;
public
property Pixels[X, Y: Integer]: TColor read GetPixel write SetPixel;
property Handle: HDC read GetHandle write SetHandle;
property TextStyle: TTextStyle read FTextStyle write FTextStyle;
published
property AutoRedraw: Boolean read FAutoRedraw write SetAutoRedraw;
property Brush: TBrush read FBrush write SetLazBrush;
property CopyMode: TCopyMode read FCopyMode write FCopyMode default cmSrcCopy;
property Font: TFont read FFont write SetLazFont;
property Pen: TPen read FPen write SetLazPen;
property Region: TRegion read FRegion write SetRegion;
property Color: TColor read GetColor write SetColor;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
property OnChanging: TNotifyEvent read FOnChanging write FOnChanging;
end;
Antes de começar, você deve saber alguns conceitos básicos, não do objeto TCanvas, mas sim da programação com gráficos:
Normalmente, em muitas funções e procedimentos, você deve utilizar coodernadas.
Essas coordenadas normalmente são representadas por X e Y...
Você deve ter em mente que sempre, sempre, o X estará antes do Y, porque essa metodologia segue a norma de coordenadas X,Y...
A coordenada X representa a Largura, e a coordenada Y representa a Altura...
Se você tem um monitor com resolução 1024x768 significa que você tem 1024 pixels de largura e 768 pixels de altura no seu monitor...
Tenha em mente que se você está manipulando um formulário de 100x50 então você só poderá manipular os pixels de 0 a 99 (Largura) e 0x49 (Altura)...
Você tem, obrigatoriamente que começar no pixel 0 e ir até a medida - 1... Isso é um padrão, então você terá de se acostumar desde cedo...
Você deve saber também algums detalhes sobre o objeto TCanvas:
Ele possui uma caneta (Pen), invisivel, que você pode mudar de posição na tela, porém ela inicia sempre nas coordenadas 0,0...
Essa caneta não passa de um objeto TPen que vamos estudar a seguir...
O Objeto TCanvas também conta com outro grande recurso, uma propriedade Brush, que nada mais é que um objeto TBrush. Vamos estudá-lo também a seguir..
Conteúdo |
Sobre os Objetos TPen e TBrush
Os Objetos TPen e TBrush são de fácil manuseio em geral. Você provavelmente já deve ter usado o PaintBrush do Ms-Windows®. Nele você tem duas cores: a primária e a secundária. Desenhando um Retângulo por exemplo, a cor primária é a cor da linha (TPen) e a secundária é a cor de fundo (TBrush). Isso já ajuda muito na explicação dos objetos TPen e TBrush. Vamos começar detalhando as propriedades que eles têm em comum: Color: A Cor Style:
Detalhes do Objeto TCanvas
Vamos estudar somente as propriedades, métodos e eventos públicos e publicados (public e published), pois você só pode utilizar o que for público ou publicado nas suas aplicações.
Você também pode modificar o que for protected (protegido) caso deseje criar um componente descendente de TCanvas. E caso deseje modificar o que for privado (private), você deverá modificar diretamente o código-fonte de TCanvas (Essa é a grande vantagem do OpenSource :-)).
ATENÇÃO:
Antes ainda de começarmos, um detalhe deve ser ressaltado:
O Lazarus, diferente do Delphi e por ser multi-plataforma, não insere automaticamente na clausula USES a unit Windows.
Se você necessita utilizar a unit Windows e precisa utilizar uma função Rect então utilize: Classes.Rect(X1,Y1,X2,Y2)
Ou caso contrário um erro acontecerá, porque a unit Classes tem uma função Rect e a unit Windows contém um tipo Rect.
Outra forma de evitar este erro é inserir a unit Windows na clausula USES antes de inserir a unit Classes.
Exemplo:
Uses Windows, Classes {, Outras units que você utiliza};
Procedures Públicas:
Procedure Draw(X,Y: Integer; SrcGraphic: TGraphic); virtual;
Desenha nas coordenadas X e Y o gráfico especificado em SrcGraphic.
Exemplo:
Self.Canvas.Draw(0,0,Image1.Picture.Bitmap);
Procedure Ellipse(x1, y1, x2, y2: Integer); virtual;
Desenha uma ellipse nas coordenadas.
Exemplo:
Self.Canvas.Ellipse(0,0,100,100);
Se X2=Y2 então será uma Circunferência (Ellipse perfeita).
Procedure Ellipse(const ARect: TRect);
Também desenha uma ellipse, porém precisa de um parâmetro TRect.
Sendo ARect = O TRect que será utilizado...
Exemplo:
Self.Canvas.Ellipse(Classes.Rect(0,0,100,100));
Se X2 e Y2 do TRect foram iguais então será uma Circunferência (Ellipse perfeita).
Procedure Line(X1,Y1,X2,Y2: Integer); virtual;
Desenha uma linha nos pontos especificados (começando em X1 e Y1 e terminando em X2 e Y2).
Exemplo:
Self.Canvas.Line(0,0,100,100);
Procedure LineTo(X1,Y1: Integer); virtual;
Desenha uma linha desde o ponto onde a Caneta do objeto TCanvas se encontra até os parâmetros X1,Y1...
Exemplo:
Self.Canvas.LineTo(100,100);
Procedure MoveTo(X1,Y1: Integer); virtual;
Move a caneta do TCanvas, que inicialmente se encontra nas coordenada 0 e 0.
Exemplo:
Self.Canvas.MoveTo(10,10);
Procedure Rectangle(X1,Y1,X2,Y2: Integer); virtual;
Desenha um retângulo nas coordenadas.
Exemplo:
Self.Canvas.Rectangle(0,0,100,100);
Procedure Rectangle(const ARect: TRect);
Também desenha um retângulo, porém precisa de um parâmetro TRect.
Sendo ARect = O TRect que será utilizado...
Exemplo:
Self.Canvas.Rectangle(Classes.Rect(0,0,100,100));
Manipulando Gráficos no Lazarus
Gráficos é um dos assuntos mais legais de se estudar em programação. Porém, existem alguns detalhes que você deve saber antes de programar em Gráficos no Lazarus:
- O Tipo TIcon ainda não está totalmente implementado. É possível carregar sem problemas um TIcon, mas ainda não está implementada a parte de salvamento. TIcon está salvando em um Bitmap e não em um Ícone.
- O Objeto TBitmap tem alguns problemas em criação de RunTime, então eu aconselho que utilize sempre um TImage (mesmo que em RunTime) e utilize a propriedade TImage.Picture.Bitmap ao invés de um objeto TBitmap.
- O Componente TImage ainda não é perfeito! O Componente TImage carrega com sucesso Bitmaps, Ícones, Pixmaps (XPM) e salva com sucesso Bitmaps, Gifs, Pixmaps (XPM) entre outros. Já possui uma unit LazJpeg (Licença LGPL) para carregamento de Jpegs. Um dos obstáculos do componente TImage, é que quando há muitas pinturas nele, ele começa a oscilar.
- O Tipo TCanvas também não é perfeito! Praticamente é perfeito o componente TCanvas em TImages, mas nem em todos componentes o TCanvas funciona como deveria ser.
Mas em geral a programação Gráfica no Lazarus funciona igualzinho à programação Gráfica em Delphi®. Se você tem conhecimento aprofundado do Objeto TCanvas (e é bom em matemática), poderá criar qualquer programa ou animação gráfica. Você pode modificar facilmente um Componente TImage e salvar, com as modificações, em um arquivo *.bmp por exemplo (O Lazarus possui suporte a salvamento de outros formatos também).
Como criar um programa Gráfico
Antes de iniciar é bom ressaltar que no diretório Lazarus/Components existe um exemplo de manipulação de imagem com efeito "Fade In" muito interessante. Seria uma boa estudá-lo :-).
Como criar um programa gráfico?
Simples. Depois que você já sabe o que vai conter o seu programa, só terá de calcular matemáticamente coordenadas, e depois desenhar desde elipse até poligonos. É tão simples que funciona como se você tivesse um lápis e uma régua na mão (em alguns casos um compasso também :-)).
Vamos aos exemplos.
Exemplo 1 - Inserindo Formas Básicas em um Programa.
Inicie uma aplicação e insira um componente TImage no formulário. Na propriedade picture carregue uma imagem qualquer (de preferência carregue uma imagem em formato BMP).
Agora vamos começar o "desenho" em si.
Você quando criança provavelmente já deve ter feito o famoso "desenho do cubo" onde se desenham dois quadrados e juntam-se esses quadrados com 4 linhas. Vamos fazê-lo para nossa primeira aplicação.
Insira um botão e no evento OnClick do botão insira:
const Topo, Esquerda = 10; //Define o Topo e a Esquerda do Cubo; Largura, Altura = 100; //Define a Largura e Altura. Como é um Quadrado as duas são iguais. Distancia = 25; //Define a distancia de um Quadrado ao Outro. begin Image1.Picture.Bitmap.Canvas.Brush.Style:=BsClear; //Para que "dentro" do cubo seja transparente. Image1.Picture.Bitmap.Canvas.Rect(Esquerda,Topo,Esquerda+Largura,Topo+Altura); Image1.Picture.Bitmap.Canvas.Rect(Esquerda+Distancia,Topo+Distancia,Esquerda+Largura+Distancia,Topo+Altura+Distancia); //Começando o código das quatro Linhas que juntam os dois quadrados. Image1.Picture.Bitmap.Canvas.MoveTo(Esquerda,Topo); //Juntando Topo Esquerdo dos Dois Quadrados; Image1.Picture.Bitmap.Canvas.LineTo(Esquerda+Distancia,Topo+Distancia); Image1.Picture.Bitmap.Canvas.MoveTo(Esquerda+Largura,Topo); //Juntando Topo Direito dos Dois Quadrados; Image1.Picture.Bitmap.Canvas.LineTo(Esquerda+Largura+Distancia,Topo+Distancia); end;
Exemplo 2: Criando uma Moldura Simples
Siga os primeiros passos do exemplo 1. Insira a Imagem, o botão e no evento OnClick do Botão insira:
const Largura=5; //Define a largura da moldura begin Image1.Picture.Bitmap.Canvas.Brush.Width:=Largura; Image1.Picture.Bitmap.Canvas.Rect(0,0,Image1.Width,Image1.Height); end;
Ficou Bonito? Depende do Ponto de Vista. Realmente é muito simples a moldura, você poderia por exemplo implementa-lá utilizando quatro linhas (funções MoveTo e LineTo) de cores distintas. Mas que tal uma mais "complexa"?
Exemplo 3: Criando uma Moldura Razoável
Essa moldura realmente é mais complexa, ela pode colorir com até quatro cores (acima,esquerda,direita,abaixo) e começa sua "pintura" em diagonal.
Para este código, eu me inspirei no famoso programa IrfaView. É claro que o efeito não é igual, mas é um 3D, um pouco menos complexo.
const Largura=50; //Largura da Moldura
var i, e:integer; //Utilizados para o Loop
begin
with Image1.Picture.Bitmap do
begin
for e:=0 to Largura-1 do
begin
Canvas.Pen.Color:=clWhite;
Canvas.MoveTo(e,e);
Canvas.LineTo(Width-e,e);
end; end;
//Segunda Parte
with Image1.Picture.Bitmap do
begin
for e:=0 to Largura-1 do
begin
Canvas.Pen.Color:=$938E87;
Canvas.MoveTo(e,e);
Canvas.LineTo(e,Height-e);
end; end;
//Terceira Parte
with Image1.Picture.Bitmap do
begin
for e:=0 to Largura-1 do
begin
Canvas.Pen.Color:=clWhite;
Canvas.MoveTo(e,(Height-e)-1);
Canvas.LineTo(Width-e,(Height-e)-1);
end; end;
//Quarta Parte
with Image1.Picture.Bitmap do
begin
for e:=0 to Largura-1 do
begin
Canvas.Pen.Color:=$938E87;
Canvas.MoveTo((Width-e)-1,e);
Canvas.LineTo((Width-e)-1,Height-e);
end; end;
//Final da Quarta parte
end;
Realmente esta ficou "bonitinha". Você poderia implementa-lá ainda mais, se você utilizasse a propriedade pixels ao invés da MoveTo e LineTo.
Como? Simples. Antes de pintar de uma cor, você teria de ler a cor do pixel referente e fazer um pouco mais clara ou escura. Ficaria um efeito 3D muito mais interessante.
Provavelmente os exemplos acima tenham sido um bom começo para utilização da propriedade Canvas, tanto de Imagens como em Formulários, ela não muda nada.
É possível criar um programa de edição de imagens um pouco complexo com o Lazarus. Efeitos desde 3D até Fade In/Fade Out são um bons recursos em um programa gráfico.
Apenas utilize sua Imaginação e coloque em prática.

