Efeitos em bitmaps
Matheus Degiovani

Efeitos especiais sempre incrementam o visual de qualquer aplicação e são elementos necessários em quase todos os tipos de jogos. No entantoé comum que os programadores reescrevam uma rotina para espelhar uma imagem ou realizar uma operação de alpha blending sempre que preciso.

Pensando em facilitar a sua vida, a reunimos dez efeitos que podem ser usados em bitmaps de 24 bits com exatamente uma linha de código, agilizando o desenvolvimento de qualquer jogo. Clique aqui e baixe um programinha exemplo, para acompanhar os resultados de cada aplicação de filtro, descrita a seguir.

Os efeitos:

Para utilizar as rotinas, basta declarar a unit "tiltefeitos" na seção uses do formulário, lembrando-se de colocar o arquivo tiltefeitos.pas no diretório do projeto ou em um que esteja no caminho de procura do Delphi. O link para baixar o fonte do tiltefeitos.pas está no final desta matéria.

Todas as funções reunidas trabalham pixel-a-pixel, em bitmaps de 24 bits. Para executar as operações de leitura e escrita, é utilizada a função scanline, que retorna um ponteiro para o endereço de memória de uma determinada linha da imagem. Para maiores informações sobre essa função, leia o artigo "Scanline" aqui mesmo no club TILT, no menu de matérias, da seção Programação.

Os seguintes efeitos foram criados (os nomes são os usados nos próprios procedimentos):

fxFiltro(bmp: TBitmap; cor: TColor): Aplica um "filtro" de uma determinada cor na imagem passada como parâmetro.

fxBrilho(bmp: TBitmap; valor: byte): Aumenta o brilho de uma imagem, de acordo com o valor especificado de 0 a 255.

fxEspelharHorz(bmp: TBitmap): Espelha a imagem horizontalmente, isto é, o lado direito passa a ser o esquerdo e vice-versa.

fxEspelharVert(bmp: TBitmap): Espelha a imagem verticalmente, isto é, o lado superior passa a ser o inferior e vice-versa.

fxScanline(bmp: TBitmap): Aplica um efeito de "scanline" sobre a imagem (linhas horizontais intercaladas).

fxBlur(bmp: TBitmap): Aplica um efeito de blur na imagem (tira a imagem de foco).

fxEmboss(Bmp: TBitmap): Executa um efeito do tipo "emboss" na imagem (destaca as transições de cores)

fxSharpen(bmp: TBitmap): Torna a imagem mais áspera (nítida).

fxCopiarComAlfa(src, dst: TBitmap; x, y: integer; alfa: byte): Copia uma imagem (src) para outra (dst) porém executando um efeito de alpha-blending de acordo com o valor (alpha) especificado (ou seja, o valor de opacidade que pode variar de 0 - transparente - até 255 - opaco).

fxGrayscale(bmp: TBitmap): Transforma uma imagem em tons de cinza (não muda o número de bits da imagem).

fxConvolution(bmp: TBitmap; matrix: TConvoMatrix; divisor, bias: single): Não é exatamente um efeito em si, mas um procedimento que aplica um filtro de convolução (Convolution Filter) na imagem (leia sobre esse tipo de filtro abaixo).

O programa de demonstração executa todos esses efeitos em uma determinada imagem que pode ser carregada do disco, no formato bmp (bitmap). A função que aplica o filtro na imagem, depois que ela foi carregada, é bem simples: ela requer uma imagem fonte (pode ser um TImage ou TBitmap) e executa o efeito de acordo com a solicitação do usuário.

Por exemplo:

  fxFiltro(ImagemFonte.Picture.Bitmap, clCorDoFiltro);

Filtro e Brilho:

As funções fxFiltro e fxBrilho são diretas: dada uma cor de filtro, um índice é calculado para a cor e multiplicado pela cor de cada pixel da imagem. O valor encontrado é colocado entre os valores 0 e 255 e recolocados na imagem.

procedure fxFiltro(bmp: TBitmap; cor: TColor);
var
r,g,b: byte;
ir,ig,ib: single;
x,y: integer;
nr,ng,nb: single;
c: TColor;
begin
if
bmp.PixelFormat <> pf24bit then
bmp.PixelFormat:= pf24bit;
colortorgb(cor, b, g, r);
ir:= r / 255;
ig:= g / 255;
ib:= b / 255;
for x:= 0 to bmp.width -1 do
for
y:= 0 to bmp.height -1 do begin
c:= getScanline(bmp, x, y);
colortorgb(c, b, g, r);
nr:= r + (r * ir); if nr > 255 then nr:= 255;
ng:= g + (g * ig); if ng > 255 then ng:= 255;
nb:= b + (b * ib); if nb > 255 then nb:= 255;
c:= RGBToColor(trunc(nb), trunc(ng), trunc(nr));
setScanline(bmp, x, y, c);
end;
end;

A função de brilho é simplesmente um filtro branco aplicado à imagem.

procedure fxBrilho(bmp: TBitmap; valor: byte);
begin
if bmp.PixelFormat <> pf24bit then
bmp.PixelFormat:= pf24bit;
fxFiltro(bmp, RGBToColor(valor, valor, valor));
end;

Espelhos e Scanline:

As funções fxEspelharHorz e fxEspelharVert são ainda mais simples que as anteriores: elas apenas copiam os dados de um pixel da imagem para outro pixel na extremidade oposta. No primeiro efeito isso é feito horizontalmente e no segundo verticalmente.

procedure fxEspelharHorz(bmp: TBitmap);
var
x,y: integer;
aux: TColor;
begin
if bmp.PixelFormat <> pf24bit then
bmp.PixelFormat:= pf24bit;
for y:= 0 to bmp.Height -1 do
for x:= 0 to (bmp.Width -1) div 2 do begin
aux:= getScanline(bmp, bmp.width -1 -x, y);
setScanline(bmp,bmp.width-1-x,y,getScanline(bmp,x,y));
setScanline(bmp, x, y, aux);
end; end; procedure fxEspelharVert(bmp: TBitmap);
var
aux: TColor;
x,y: integer;
begin
if bmp.PixelFormat <> pf24bit then
bmp.PixelFormat:= pf24bit;
for x:= 0 to bmp.width -1 do
for
y:= 0 to (bmp.height -1) div 2 do begin
aux:= getScanline(bmp, x, y);
setScanline(bmp,x,y,getScanline(bmp,x,bmp.height-1-y));
setScanline(bmp, x, bmp.height -1 -y, aux);

end;
end;

O efeito fxScanline percorre a imagem verticalmente e, à cada duas linhas, preenche uma com a cor preto.

procedure fxScanline(bmp: TBitmap);
var
x,y: integer;
begin
if bmp.PixelFormat <> pf24bit then
bmp.PixelFormat:= pf24bit;
y:= 0;
while (y < bmp.height) do begin
for
x:= 0 to bmp.width -1 do
setScanline(bmp, x, y, clBlack);
y:= y + 2;
end;
end;

Filtros de Convolução:

Os efeitos de convolution filters são um dos mais famosos e usados no mundo do processamento gráfico. Eles consistem no uso de uma matriz chamada kernel (com tamanho que pode ser especificado) que indica qual o "peso" das cores que estão ao redor de um pixel sendo lido. Esses resultados são somados junto com um valor de "bias" e divididos por um número divisor e então retornados à imagem.

Ou seja, o algoritmo para esse tipo de efeito percorre cada pixel da imagem somando os valores dos pixels adjacentes (multiplicados antes pela matrix kernel) e divindo o valor resultante por um divisor.

Diferentes kernels resultam em diferentes imagens, e por esse motivo as funções fxBlur, fxSharpen e fxEmboss se traduzem em chamadas para a aplicação de um filtro de convolução (fxConvolution) apenas mudando-se os valores da matriz.

procedure fxBlur(bmp: TBitmap);
var
mat: TConvoIndexMatrix;
begin
mat[-1,-1]:= 1; mat[ 0,-1]:= 1; mat[+1,-1]:= 1;
mat[-1, 0]:= 1; mat[ 0, 0]:= 1; mat[+1, 0]:= 1;
mat[-1,+1]:= 1; mat[ 0,+1]:= 1; mat[+1,+1]:= 1;
fxConvolution(bmp, getConvoMatrix(mat), 9, 0 );
end; procedure fxSharpen(bmp: TBitmap);
var
mat: TConvoIndexMatrix;
begin
mat[-1,-1]:= -1; mat[ 0,-1]:= 0; mat[+1,-1]:= 0;
mat[-1, 0]:= 0; mat[ 0, 0]:= 3; mat[+1, 0]:= 0;
mat[-1,+1]:= 0; mat[ 0,+1]:= 0; mat[+1,+1]:= -1;
fxConvolution(bmp, getConvoMatrix(mat), 1, 0 );
end; procedure fxEmboss(Bmp: TBitmap);
var
mat: TConvoIndexMatrix;
begin
mat[-1,-1]:= -1; mat[ 0,-1]:= -1; mat[+1,-1]:= 0;
mat[-1, 0]:= -1; mat[ 0, 0]:= 0; mat[+1, 0]:= +1;
mat[-1,+1]:= 0; mat[ 0,+1]:= +1; mat[+1,+1]:= +1;
fxConvolution(bmp, getConvoMatrix(mat), 1, 128 );
end;

Alpha Blending e Grayscale:

A operação de cópia com alpha blending resulta numa imagem de destino sobreposta pela imagem fonte porém com a sua opacidade reduzida (ou seja, consegue-se ver partes da imagem de destino "embaixo" da imagem fonte), ao contrário da cópia normal que simplesmente sobrepõe os dados.

procedure fxCopiarComAlfa(src, dst: TBitmap;
x, y: integer; alfa: byte);
var
cx,cy,ax,ay: integer;
sr,sg,sb,dr,dg,db: byte;
begin
if src.PixelFormat <> pf24bit then
src.PixelFormat:= pf24bit;
if dst.PixelFormat <> pf24bit then
dst.PixelFormat:= pf24bit;
if x >= dst.Width then exit;
if y >= dst.height then exit;
ay:= 0;
for cy:= y to min(dst.height-1,y+src.height-1) do begin
ax:= 0;
for cx:= x to min(dst.width-1,x+src.width -1) do begin
colortorgb(getScanline(src, ax, ay), sr, sg, sb);
colortorgb(getScanline(dst, cx, cy), dr, dg, db);
dr:= dr + ((sr - dr) * alfa) shr 8;
dg:= dg + ((sg - dg) * alfa) shr 8;
db:= db + ((sb - db) * alfa) shr 8;
setScanline(dst, cx, cy, RGBToColor(dr, dg, db));
ax:= ax + 1;
end;
ay:= ay + 1;
end;
end;

Esse efeito se traduz como uma subtração, uma multiplicação e uma divisão dos valores das cores da fonte e do destino, que resulta no efeito desejado.

procedure fxGrayscale(bmp: TBitmap);
var
x,y: integer;
r,g,b,nr,ng,nb: byte;
begin
if bmp.PixelFormat <> pf24bit then
bmp.PixelFormat:= pf24bit;
for x:= 0 to bmp.width -1 do
for
y:= 0 to bmp.height -1 do begin
colortorgb(getScanline(bmp, x, y), r, g, b);
nr:= trunc(r * 0.2125 + g * 0.7154 + b * 0.0721);
ng:= nr;
nb:= nr;
setScanline(bmp, x, y, rgbToColor(nr, ng, nb));
end;
end;

A transformação para preto e branco é semelhante, porém ela apenas executa uma multiplicação entre os pixels da imagem e valores de ponderação para os elementos vermelho (R), verde (G) e azul (B) da imagem que retornam a imagem em escala de cinzas.

Concluindo:

Os efeitos expostos nesse artigo podem servir de inspiração para diversas outras funções interessanes, especialmente os filtros de convolução que podem ser facilmente criados. Portanto, mãos à obra!

Alguns kernels para os filtros de convolução.
http://www.sgi.com/software/opengl/advanced97/notes/node152.html


Download...
Clique no link para fazer o download dos arquivos. Se sua assinatura do club TILT está para vencer, clique aqui e saiba como renová-la.

Fonte completo do programa exemplo
Fonte das funções
 
online