Você está na página 1de 4

Como usar scanlines

Leonel Togniolli
Resumo: Este artigo mostra um modo rpido para manipular bitmaps, sem usar canvas: as scanlines.
Uma maneira bastante rpida de acessar uma imagem usar as scanlines, enquanto acessar
diretamente os pixels bastante devagar. bem fcil us-las, e a maioria do cdigo escrito usando
pixels de um canvas pode ser facilmente adaptado para usar scanlines, e rotinas planejadas para uslas podem ter um desempenho vrias vezes melhor.
No pretendo substituir o excelente material de Earl sobre scanlines, mas fornecer uma
introduo mais simples a elas, de modo que seu artigo se torne uma leitura mais avanada.
Scanlines so as linhas horizontais de uma imagem. Uma coisa extremamente importante
que elas so dependentes do PixelFormat (formato de pixel), significando que sua estrutura varia
com a profundidade de cor da imagem. Um dos equvocos mais comuns quando se est aprendendo
esquecer de especificar o PixelFormat adequadamente antes de acess-las. Quando voc obtm
uma scanline, voc obtm um ponteiro para o primeiro byte daquela linha, e tem acesso byte por
byte. Isso significa que o tamanho de uma scanline *, preenchido at a prxima dword. Quando
trabalhando-se no modo pf32bit (4 bytes) que assumiremos daqui em diante , uma scanline se
parece como algo assim:
Azul Verde Vermelho Alpha Azul Verde Vermelho Alpha Azul Verde Vermelho Alpha ...

primeiro pixel

segundo pixel

terceiro pixel

Dica: Alpha normalmente usado para guardar informaes sobre a transparncia da imagem. Na
prtica, contudo, voc pode guardar o que quiser no canal alpha.
Mas isso no uma boa maneira de se trabalhar. Estamos definindo um tipo contendo quatro
componentes de cor e, usando a boa e velha aritmtica de ponteiros, vamos trabalhar do nosso jeito,
de quatro em quatro bytes na scanline. Voc poderia usar tambm TRGBQuad aqui, o importante
que voc compreenda como a scanline funciona:
type
TRGB32 = packed record
B, G, R, A: Byte;
end;
TRGB32Array = packed array[0..MaxInt div SizeOf(TRGB32)-1] of TRGB32;
PRGB32Array = ^TRGB32Array;

Acabamos de definir um registro contendo as componentes de cor, um array realmente


grande dessas componentes, e um ponteiro para esse array. Podemos agora acessar qualquer byte
dessa linha, usando essa definio como uma mscara para o array de bytes para o qual aponta
ScanLine[y].
Coloque uma imagem (image) em um Form. Adicione um boto (button) com o seguinte
cdigo no seu OnClick:
procedure TForm1.Button1Click(Sender: Tobject);
var
x,y : Integer;
begin
with Image1.Picture.Bitmap do
begin
PixelFormat := pf32bit;
Width := Image1.Width;

Height := Image1.Height;
for x := 0 to Width - 1 do
for y := 0 to Height - 1 do
Canvas.Pixels[x,y] := x xor y;
end;
Image1.Invalidate;
end;

Isso dever desenhar um padro de vermelho bem legal. Vamos fazer isso agora usando
scanlines. Uma vez que temos acesso a uma linha inteira de cada vez, faz sentido iterar atravs
delas primeiro. Adicione um segundo boto e adicione esse cdigo a ele:
procedure TForm1.Button2Click(Sender: Tobject);
var
x,y : Integer;
Line : PRGB32Array;
begin
with Image1.Picture.Bitmap do
begin
PixelFormat := pf32bit;
Width := Image1.Width;
Height := Image1.Height;
for y := 0 to Height - 1 do
begin
Line := Scanline[y];
for x := 0 to Width - 1 do
begin
Line[x].B := 0;
Line[x].G := 0;
Line[x].R := x xor y;
Line[x].A := 0;
end;
end;
end;
Image1.Invalidate;
end;

Deve ficar parecido com algo assim:

Note que voc tem acesso agora a qualquer componente de cor de um dado pixel, onde cada
byte representa uma intensidade de cor de 0 a 255, ao invs de fornecer uma TColor como na
primeira rotina. Conforme a ajuda do Delphi, se voc especificar uma TColor como um nmero
hexadecimal de 4 bytes especfico ao invs de usar as constantes definidas na unit Graphics, o trs
bytes mais baixos representam as intensidades de cores RGB para azul, verde, e vermelho,
respectivamente. O valor $00FF0000 representa intensidade completa, azul puro, $0000FF00 o
verde puro, e $000000FF o vermelho puro. $00000000 o preto e $00FFFFFF o branco. Isso
significa que voc pode obter qualquer componente de cor de uma dada TColor, basta voc oper-la
via AND com as constantes acima, ou seja, (clGray AND $000000FF) lhe fornece a componente
vermelha de clGray.
Cronometrei os tempos das duas rotinas, chamando cada uma delas dez vezes num bitmap
de 1024x1024. Button1Click deu uma mdia de 7298ms por chamada, enquanto Button2Click deu
uma mdia de 24ms por chamada. Ou seja, usar scanlines cerca de 300 vezes mais rpido.
Lembre-se que muito importante que o PixelFormat seja especificado como pf32bit, ou
voc poderia ter de usar um registro diferente para acess-los. O valor por omisso dessa
propriedade pfDevice, que a profundida de cor corrente do Windows. Ento, se voc quer que o
seu cdigo funcione adequadamente em cada mquina, lembre-se de especific-lo explicitamente.
Scanlines so armazenadas na memria sequencialmente, normalmente em ordem reversa,
algo como isso:
BGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGR(...)BGR(Preenchimento) // ltima Linha
.
.
.
BGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGR(...)BGR(Preenchimento) // Segunda Linha
BGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGRBGR(...)BGR(Preenchimento) // Primeira Linha

(onde B = Azul, G = Verde, R = Vermelho.)


Elas so tambm igualmente longas. Podemos usar isso em nosso favor. Se temos um
ponteiro para a primeira linha, e a diferena, em bytes, entre cada linha, podemos acessar qualquer
pixel puramente com matemtica. Declarei variveis para cont-las na seo privada (private) do
form:
private
LineLength : Longint;
FirstLine : PRGB32Array;

E, em seguida, coloquei no mtodo OnCreate do form:


procedure TForm1.FormCreate(Sender: Tobject);
begin
with Image1.Picture.Bitmap do
begin
PixelFormat := pf32bit;
Width := Image1.Width;
Height := Image1.Height;
FirstLine := Scanline[0];
LineLength := (Longint(Scanline[1]) - Longint(FirstLine)) div SizeOf(TRGB32);
end;
end;

E as usei no evento OnMouseMove da imagem:


procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if (ssLeft in Shift) and (x in [0..Image1.Width-1]) and (y in [0..Image1.Height-1]) then
begin
with FirstLine[Y*LineLength+X] do

begin
B := 0;
G := 0;
R := 255;
A := 0;
end;
Image1.Invalidate;
end;
end;

Clique agora na imagem e passe o mouse sobre. Sero colocados pontos vermelhos sobre ela
(super rpido!). Tente acessar os pixels como na ltima procedure, use com, porque ser um pouco
mais rpido, haja visto que a expresso avaliada bem como a matriz acessada apenas uma vez.
Voc pode verificar isso na janela CPU, se for pelo menos um pouco proficiente em ASM.
Voc pode baixar essa aplicao exemplo da CodeCentral.
Voc deve se certificar de ter sempre ponteiros vlidos para as scanlines, assim, voc deve
dar um refresh na cache sempre que o bitmap tiver Width, Height, PixelFormat etc. alterados. Um
bom lugar para faz-lo no mtodo virtual Changed de TBitmap, se voc estiver trabalhando em
uma classe descendente de TBitmap. Uma outra coisa a qual voc deve dedicar ateno especial
no misturar esse acesso em cache de scanlines e o acesso direto a GDI. Se voc fizer, certifique-se
de chamar GDIFlush para ter certeza de que as alteraes sejam aplicadas, ou chame ScanLine[x]
novamente, uma vez que certamente isso pe tudo em ordem.
Isso tudo por ora. No prximo artigo escreverei sobre como usar esse mtodo para
produzir efeitos especiais em Delphi. At l!

Original em: http://edn.embarcadero.com/article/29173


Traduo de: Leonardo Coelho