Se
existe uma coisa que irrita os programadores de jogos
e demais espécimes desta incrível fauna
informática é a forma estúpida
e bitolada com a qual os sistemas manipulam bits e bytes
gráficos. Já vai longe o tempo em que
se podia dar um Peek e um Poke,
na área de vídeo, para reciprocamente
plotar um ponto na tela do monitor.
No PC isso
sempre foi um problemão e tanto, devido às
rotinas inéptas que nos foram impostas (via DOS
e INTs) notadamente quando se desejava usar modos gráficos.
No Windows isso não mudou muito, apesar de estarmos
trabalhando sob um ambiente gráfico (santa contradição,
né não Batmam?).
Então,
não era de se esperar que o Delphi nos presenteasse
com uma solução inteligente, afinal ele
foi feito para uso em programação de sistemas
comerciais. Só mesmo esses doidos da TILT
que usam Delphi para escrever jogos e acessar diretamente
o vídeo. Ninguém mais faz isso.
Bem, se
você se interessa pelo assunto mas está
chegando agora no pedaço, então vamos
rever alguns pontos básicos. Sem eles todo o
resto não faz muito sentido.
No Windows
dispomos de ferramentas (funções internas
da API) que manipulam maravilhosamente áreas
gráficas (bitmaps), transportando-as para lá
e para cá, mais rápido que o Sedex. Fazem
operações booleanas com essas áreas.
Pintam e bordam. Mas o pobre coitado do pixel fica sempre
a ver navios.
Experimente
só rodar o seguinte programa:
procedure TForm1.SpeedButton1Click(Sender: TObject); var X,Y: integer; Z: DWord; begin Z:= Random(255)*65536 + Random(255)*256 + Random(255); for Y:= 0 to ClientHeight -1 do begin for X:= 0 to ClientWidth -1 do Canvas.Pixels[X,Y]:= Z; end; end; |
X e Y são
os valores que irão apontar pixel a pixel todos
os pontos dentro da imagem; Z é uma cor que será
definida aleatoriamente. Nossa procedure pode ser chamada
de um CLS especial, colorido aleatoriamente. Quem faz
o trabalho sujo (ou limpo, como preferirem) é
a instrução Canvas.Pixels[X,Y]:= Z. É
o antigo Poke na área de vídeo.
Mas tem
um porém: isso é de uma lentidão
exasperante. Tente rodar esse programinha em não
menos que um Pentium 4 de 2.5 Ghz e você vai ver
como a coisa empaca. Se pintar a tela à mão,
fazemos mais rápido. O Windows não está
preparado para lidar com uma coisa tão simples
quanto um pixel.
A saída
é fazer primeiro na memória e só
depois mandar para o vídeo (ou mais precisamente
para o canvas do formulário, que pode ser considerado
como a nossa área particular de vídeo).
Para isso
definimos um buffer que será a nossa imagem em
memória (globalmente falando, é claro):
Ao inicializar
o programa, precisamos criar o buffer, carregá-lo
com um padrão gráfico (é a forma
mais rápida e fácil de definir a estrutura
do bitmap, que no caso será padrão True
Color):
procedure TForm1.FormCreate(Sender: TObject); begin Buffer:= TBitmap.Create; Buffer.LoadFromFile('padrao2.bmp'); Buffer.Width:= Form1.ClientWidth; Buffer.Height:= Form1.ClientHeight - 35; end; |
Não
esquecer (não vamos deixar para depois) de destruir
tudo o que foi feito, na saída:
procedure TForm1.FormDestroy(Sender: TObject); begin Buffer.Free; end; |
Agora criamos
um TSpeedButton2 e acrescentamos a seguinte programação:
procedure TForm1.SpeedButton2Click(Sender: TObject); var X,Y: integer; Z: DWord; begin Z:= Random(255)*65536 + Random(255)*256 + Random(255); for Y:= 0 to Buffer.Height -1 do begin for X:= 0 to Buffer.Width -1 do Buffer.Canvas.Pixels[X,Y]:= Z; end; Canvas.Draw(0,0,Buffer); end; |
A diferença
é que fizemos o "Poke" na memória e, no
final, mandamos o buffer para o vídeo, todo de
uma vez (Canvas.Draw(0,0,Buffer)). Muda alguma coisa,
mas quase não se percebe. Ainda é um processo
demorado e que não serve para animações
e nem para usar em jogos mais elaborados.
Mas, salvos
pelo gongo, existe uma função chamada
ScanLine que atua não no pixel diretamente, mas
numa linha de pixels. Mas antes é preciso criar
uma variável pointer que apontará para
a matriz da linha (variável global):
var Buffer: TBitmap; P: PByteArray; |
Agora é
usar o mesmo princípio da escrita em memória,
que acabamos de ver:
procedure TForm1.SpeedButton3Click(Sender: TObject); var X,Y: integer; R,G,B: byte; begin R:= Random(255); G:= Random(255); B:= Random(255); for Y:= 0 to Buffer.height -1 do begin P:= Buffer.ScanLine[Y]; for X:= 0 to Buffer.width -1 do begin P[X*3]:= R; P[X*3+1]:= G; P[X*3+2]:= B; end; end; Canvas.Draw(0,0,Buffer); end; |
Quase lá!
Fica hiper rápido e não se nota o tempo
entre soltar o botão e o processamento da imagem.
Em computadores mais lentos (abaixo de 166 Mhz) pode
até haver um pequeno "flicking" na imagem, mas
é porque estamos usando cores em True Color e
isso multiplica por três o tempo de processamento.
Veja como
ficaria uma televisão que teima em ficar fora
do ar:
procedure TForm1.Timer1Timer(Sender: TObject); var X,Y: integer; begin if Vid1 = 1 then begin for Y:= 0 to Buffer.height -1 do begin P:= Buffer.ScanLine[Y]; for X:= 0 to (Buffer.width *3)-1 do P[X]:= Random(255); end; Canvas.Draw(0,0,Buffer); end; end; |
O processamento
está dentro de um TTimer, ajustado para um intervalo
igual a 10. Basta definir uma variável byte (Vid1
= 0) e um botão para ligar e desligar a TV. Só
fica faltando mesmo o som.
Podemos
melhora ainda mais essas procedures. O truque é
definir a área de imagem com o padrão
8 bits, para as cores. Assim, teríamos uma palette
que poderia servir a uma grande quantidade de imagens
e poderíamos tratar nossas figuras como se cada
pixel fosse um único byte. Coisa fácil
e rápida de ser feita.
Mas isso
eu deixo para vocês (ou para uma outra oportunidade).