Curso completo de DarkGdk
Gameprog - Escola de programação de jogos digitais
Contato: gameprog.br@gmail.com
Fase 5.4

index << >>



05.4 Considerações sobre imagem 2D

05.4 Visão geral

Nesta seção vamos fazer tecer algumas considerações técnicas sobre imagens 2D
com o intuito de facilitar a compreensão de algumas funções avançadas de
manipulação de imagens que a Darkgdk oferece e que serão apresentadas nos
próximos capítulos.

Vamos supor agora que seu desafio é gravar a imagem de um quadrado azul em
um arquivo em disco. O tamanho desse quadrado é 5 pixels de largura e altura.

A primeira pergunta que surge é quais são as formas pelas quais eu posso
representar um quadrado.

Imagem vetorial
a) Um quadrado pode ser representado por uma função que aceita como argumentos
o tamanho do quadrado, a posição (x,y) do quadrado na tela e a cor do
quadrado. Então no disco eu posso gravar assim esse quadrado:

// xpos( 1 byte ), ypos( 1 byte ), tamanho (1 byte), cor(3 bytes)
160, 240, 5, [0, 0, 255] 

Gasto então 6 bytes para gravar esse quadrado dessa forma no disco. Depois
carrego esse quadrado do disco e para mostrá-lo uso uma função que pode ter
essa forma:
// ---------------------------------------------------------------------------- void imagem_vetorial(void) { // Propriedades da imagem vetorizada int dados[6] = {160, 120, 5, 0, 0, 255 }; // Variáveis de trabalho int xpos, ypos, ntamanho; int ncor, r, g, b; // Carregando a informação da imagem para as variáveis de trabalho xpos = dados[0]; ypos = dados[1]; ntamanho = dados[2]; r = dados[3]; g = dados[4]; b = dados[5]; // Desenhando a imagem vetorizada ncor = dbRGB(r,g,b); dbInk (ncor, 0); dbBox (xpos, ypos, xpos + ntamanho, ypos + ntamanho); } // imagem_vetorial(void).fim
Nesse primeiro caso dizemos que a imagem do quadrado é vetorial pois é formada a partir de pontos que indicam apenas as propriedades do quadrado (cor, posição, tamanho). Essa imagem ocupa pouca memória no arquivo. Imagem rasterizada b) A segunda maneira de representar esse quadrado é por uma matriz de linhas e colunas que vão conter a cor de cada ponto desse quadrado para toda a área do quadrado. Como o quadrado tem 5 pixels de tamanho, a área dele é de 25 pontos; a cor de cada ponto demanda 3 bytes para ser arquivada, então o arquivo em disco vai ter 75 bytes para suportar a totalidade dos pontos desse quadrado. O arquivo em disco vai ter essa configuração:
(0,0,255),(0,0,255),(0,0,255),(0,0,255),(0,0,255) (0,0,255),(0,0,255),(0,0,255),(0,0,255),(0,0,255) (0,0,255),(0,0,255),(0,0,255),(0,0,255),(0,0,255) (0,0,255),(0,0,255),(0,0,255),(0,0,255),(0,0,255) (0,0,255),(0,0,255),(0,0,255),(0,0,255),(0,0,255)
Depois de carregado do disco, o código para mostrar esse quadrado na posição (80,60) da tela poderia ficar dessa forma:
// ---------------------------------------------------------------------------- void imagem_rasterizada(void) { // Dados de cor literais da imagem rasterizada int dados [25][3] = { {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255} }; // fim da array // Variáveis de trabalho int xpos, ypos; int ncor, r, g, b; int linha, coluna; // Ponteiro para acessar a memória aonde a imagem está localizada int *pImagem; xpos = 80; ypos = 60; // Legenda dbInk( nPreto, nBranco); dbText(xpos + 20, ypos, "<-- imagem_rasterizada()"); // Acessa a localização da imagem na memória pImagem = dados[0]; // Acessa os dados da imagem e coloca-os na tela for (linha=0; linha < 5; linha++) { for (coluna = 0; coluna < 5; coluna++) { // Obtém valores rgb dos 3 primeiros inteiros r = *pImagem; pImagem++; g = *pImagem; pImagem++; b = *pImagem; pImagem++; ncor = dbRGB(r,g,b); // Desenha ponto dbInk( ncor, 0); dbDot (xpos + linha, ypos + coluna); } // fim do for(coluna) } // fim do for(linha) } // imagem_rasterizada().fim
Essa forma de imagem é conhecida como rasterizada, e é muito comum a imagem ser sustentada dessa forma em um arquivo em disco, na memória e na sua tela. A DarkGdk possui funções que permitem manipular diretamente uma imagem rasterizada na memória passando um ponteiro de localização dessa imagem. Isso é interessante para produzir efeitos gráficos nos bastidores do programa e depois exibí-los na tela. Compactação e volume de dados da imagem rasterizada
(0,0,255),(0,0,255),(0,0,255),(0,0,255),(0,0,255) (0,0,255),(0,0,255),(0,0,255),(0,0,255),(0,0,255) (0,0,255),(0,0,255),(0,0,255),(0,0,255),(0,0,255) (0,0,255),(0,0,255),(0,0,255),(0,0,255),(0,0,255) (0,0,255),(0,0,255),(0,0,255),(0,0,255),(0,0,255)
( (*) 75 bytes para gravar um quadrado 5x5 com cor de 3 bytes.) Voltando ao nosso exemplo da imagem rasterizada, perceba que a profundidade de cor impacta muito no volume de dados da imagem. Em nosso simples exemplo nosso quadrado azul de 5 pixels consome 75 bytes porque a representação de cor demanda 3 bytes. Naturalmente que uma imagem com cores de 16 bits ou 8 bits ocupa menor espaço ao custo de uma menor qualidade na imagem.
255,255, 255, 255, 255 255,255, 255, 255, 255 255,255, 255, 255, 255 255,255, 255, 255, 255 255,255, 255, 255, 255
( (*) 25 bytes para gravar um quadrado 5x5 com cor de 1 byte.) Uma primeira forma de compactar uma imagem é criar uma tabela de cores, uma paleta. Por exemplo, na tabela acima você pode interpretar que o byte 255 pode ser o nosso azul (0,0,255) ou outra cor conforme nossa conveniência (78,230, 107). O byte é lido e substituido pela cor final. Veja abaixo uma função de exemplo que lê e mostra uma imagem assim.
// ---------------------------------------------------------------------------- void rasterizada_com_tabela_de_cores(void) { // O valor 255 simboliza uma posição numa tabela de cores que pode representar // qualquer cor conforme nossa necessidade e conveniência int dados [5][5] = { 255,255,255,255,255, 255,255,255,255,255, 255,255,255,255,255, 255,255,255,255,255, 255,255,255,255,255 }; // fim da array // Variáveis de trabalho int cor_tabela[256]; int xpos, ypos; int ncor, r, g, b; int linha, coluna; // Gera uma cor aleatória r = dbRnd(255); g = dbRnd(255); b = dbRnd(255); // Joga essa cor na posição 255 da tabela de cores cor_tabela[255] = dbRGB(r,g,b); // Localização da imagem xpos = 40; ypos = 30; // Legenda dbInk( nPreto, nBranco); dbText(xpos + 20, ypos, "<-- rasterizada_com_tabela_de_cores()"); // Ponteiro para acessar a memória aonde a imagem está localizada int *pImagem; // Acessa a memória aonde a imagem está localizada pImagem = dados[0]; // Desenha a imagem for (linha=0; linha < 5; linha++) { for (coluna = 0; coluna < 5; coluna++) { // *pImagem retorna o valor numérico que representa a posição de // uma cor na tabela ncor = cor_tabela [ *pImagem ]; pImagem++; // Desenha ponto dbInk( ncor, 0); dbDot (xpos + linha, ypos + coluna); } // fim do for(coluna) } // fim do for(linha) } // rasterizada_com_tabela_de_cores().fim
Melhor compactação Geralmente uma imagem apresenta repetições de cor em pontos consecutivos, tendo isso em mente, o processo de compactação pode ser melhorado com aproveitamento disso. Por exemplo, você pode arquivar a quantidade de pontos consecutivos e a cor desses pontos na sequência nessa forma: [10, 1] [20, 2] [10, 3]. O primeiro byte descreve a quantidade de pontos consecutivos com a mesma cor, e o próximo número representa uma cor de uma tabela de cores. Como terceira opção de representação do nosso quadrado no arquivo ou na memória ele poderia ficar dessa forma:
5,1 5,1 5,1 5,1 5,1
O cinco é a quantidade consecutiva de pontos da mesma cor e o 1 representa o azul (0,0,255). Gastamos assim 10 bytes para representar e gravar nosso quadrado 5x5. Veja a função que desdobra e mostra uma imagem assim:
// ---------------------------------------------------------------------------- void rasterizada_compactada(void) { // Dados de uma imagem rasterizada compactada // [5][2] representa uma matriz bidimensional de 10 inteiros // {5,1} o primeiro inteiro representa a quantidade de pontos consecutivos que // tem a cor apontada pelo segundo número em uma tabela int dados [5][2] = { 5,1, 5,1, 5,1, 5,1, 5,1 }; // fim da array // Tabela e variáveis de trabalho int cor_tabela[256]; int xpos, ypos; int ncor, r, g, b; int linha = 0; int coluna = 0; // Gera uma cor aleatória e coloca-a na posição 1 da tabela r = dbRnd(255); g = dbRnd(255); b = dbRnd(255); cor_tabela[1] = dbRGB(r,g,b); // Posição da imagem xpos = 20; ypos = 15; // Legenda dbInk( nPreto, nBranco); dbText(xpos + 20, ypos-5, "<-- rasterizada_compactada()"); // Variáveis de trabalho int nqtd = 0; int repete_cor = 0; int ncor_tabela_pos = 0; // Desenha a imagem descompactando... for (linha=0; linha < 5; linha++) { nqtd = dados[linha][0]; ncor_tabela_pos = dados[linha][1]; ncor = cor_tabela [ ncor_tabela_pos ]; for (repete_cor=0; repete_cor < nqtd; repete_cor++) { // Desenha ponto dbInk( ncor, 0); dbDot (xpos + linha, ypos + coluna); coluna++; } // fim do for(coluna) coluna = 0; } // fim do for(linha) } // rasterizada_compactada().fim
Efeitos especiais com imagens A intenção desse capítulo foi deixar a porta aberta para que você possa aplicar efeitos especiais nas suas imagens através do acesso e manipulação das cores ou dos pontos da sua imagem. Em nosso código apenas exemplificamos como você lê e mostra uma imagem. Porém, no meio do processo você pode aplicar um efeito tipo invertendo ou alterando os valores r-g-b antes de mostrar sua imagem. Um efeito de degradê de cinza pode ser obtido tirando a média dos valores dos canais rgb e igualando-os com o mesmo valor. Veja agora o programa exemplo completo dessa seção.
// desenho2d02.cpp // Esse programa ilustra considerações técnicas sobre processamento de // imagens 2D. #include "DarkGDK.h" // Protótipo das funções void initsys(); // inicializa o sistema void imagem_vetorial(void); // Primeiro método de desenhar um quadrado void imagem_rasterizada(void); // Segundo método de desenhar um quadrado void rasterizada_com_tabela_de_cores(void); // Terceiro método de desenhar um quadrado void rasterizada_compactada(void); // Quarto método de desenhar um quadrado // Cores const int nPreto = 0; const int nBranco = 0xFFFFFF; // ---------------------------------------------------------------------------- void DarkGDK ( void ) { // Começo da aplicação DarkGdk initsys(); // Ativa geração de números aleatórios diferentes a cada rodada do programa // Utilizado para gerar cores diferentes dbRandomize(dbTimer()); // Testando vários métodos de produzir imagens imagem_vetorial(); imagem_rasterizada(); rasterizada_com_tabela_de_cores(); rasterizada_compactada(); while ( LoopGDK ( ) ) { dbSync ( ); } // fim do while return; } // fim da função: DarkGDK // ---------------------------------------------------------------------------- void initsys() { // Esta função inicializa o sistema dbSyncOn( ); dbSyncRate (60); dbCLS(nBranco); dbSetWindowTitle("desenho2d02.cpp"); } // fim da função: initsys() // ---------------------------------------------------------------------------- void imagem_vetorial(void) { // Propriedades da imagem vetorizada int dados[6] = {160, 120, 5, 0, 0, 255 }; // Variáveis de trabalho int xpos, ypos, ntamanho; int ncor, r, g, b; // Carregando a informação da imagem para as variáveis de trabalho xpos = dados[0]; ypos = dados[1]; ntamanho = dados[2]; r = dados[3]; g = dados[4]; b = dados[5]; // Legenda dbInk( nPreto, nBranco); dbText(xpos + 20, ypos, "<-- imagem_vetorial()"); // Desenhando a imagem vetorizada ncor = dbRGB(r,g,b); dbInk (ncor, 0); dbBox (xpos, ypos, xpos + ntamanho, ypos + ntamanho); } // quadrado01().fim // ---------------------------------------------------------------------------- void imagem_rasterizada(void) { // Dados de cor literais da imagem rasterizada int dados [25][3] = { {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255}, {0,0,255} }; // fim da array // Variáveis de trabalho int xpos, ypos; int ncor, r, g, b; int linha, coluna; // Ponteiro para acessar a memória aonde a imagem está localizada int *pImagem; xpos = 80; ypos = 60; // Legenda dbInk( nPreto, nBranco); dbText(xpos + 20, ypos, "<-- imagem_rasterizada()"); // Acessa a localização da imagem na memória pImagem = dados[0]; // Acessa os dados da imagem e coloca-os na tela for (linha=0; linha < 5; linha++) { for (coluna = 0; coluna < 5; coluna++) { // Obtém valores rgb dos 3 primeiros inteiros r = *pImagem; pImagem++; g = *pImagem; pImagem++; b = *pImagem; pImagem++; ncor = dbRGB(r,g,b); // Desenha ponto dbInk( ncor, 0); dbDot (xpos + linha, ypos + coluna); } // fim do for(coluna) } // fim do for(linha) } // imagem_rasterizada().fim // ---------------------------------------------------------------------------- void rasterizada_com_tabela_de_cores(void) { // O valor 255 simboliza uma posição numa tabela de cores que pode representar // qualquer cor conforme nossa necessidade e conveniência int dados [5][5] = { 255,255,255,255,255, 255,255,255,255,255, 255,255,255,255,255, 255,255,255,255,255, 255,255,255,255,255 }; // fim da array // Variáveis de trabalho int cor_tabela[256]; int xpos, ypos; int ncor, r, g, b; int linha, coluna; // Gera uma cor aleatória r = dbRnd(255); g = dbRnd(255); b = dbRnd(255); // Joga essa cor na posição 255 da tabela de cores cor_tabela[255] = dbRGB(r,g,b); // Localização da imagem xpos = 40; ypos = 30; // Legenda dbInk( nPreto, nBranco); dbText(xpos + 20, ypos, "<-- rasterizada_com_tabela_de_cores()"); // Ponteiro para acessar a memória aonde a imagem está localizada int *pImagem; // Acessa a memória aonde a imagem está localizada pImagem = dados[0]; // Desenha a imagem for (linha=0; linha < 5; linha++) { for (coluna = 0; coluna < 5; coluna++) { // *pImagem retorna o valor numérico que representa a posição de uma // cor na tabela ncor = cor_tabela [ *pImagem ]; pImagem++; // Desenha ponto dbInk( ncor, 0); dbDot (xpos + linha, ypos + coluna); } // fim do for(coluna) } // fim do for(linha) } // rasterizada_com_tabela_de_cores().fim // ---------------------------------------------------------------------------- void rasterizada_compactada(void) { // Dados de uma imagem rasterizada compactada // [5][2] representa uma matriz bidimensional de 10 inteiros // {5,1} o primeiro inteiro representa a quantidade de pontos consecutivos que // tem a cor apontada pelo segundo número em uma tabela int dados [5][2] = { 5,1, 5,1, 5,1, 5,1, 5,1 }; // fim da array // Tabela e variáveis de trabalho int cor_tabela[256]; int xpos, ypos; int ncor, r, g, b; int linha = 0; int coluna = 0; // Gera uma cor aleatória e coloca-a na posição 1 da tabela r = dbRnd(255); g = dbRnd(255); b = dbRnd(255); cor_tabela[1] = dbRGB(r,g,b); // Posição da imagem xpos = 20; ypos = 15; // Legenda dbInk( nPreto, nBranco); dbText(xpos + 20, ypos-5, "<-- rasterizada_compactada()"); // Variáveis de trabalho int nqtd = 0; int repete_cor = 0; int ncor_tabela_pos = 0; // Desenha a imagem descompactando... for (linha=0; linha < 5; linha++) { nqtd = dados[linha][0]; ncor_tabela_pos = dados[linha][1]; ncor = cor_tabela [ ncor_tabela_pos ]; for (repete_cor=0; repete_cor < nqtd; repete_cor++) { // Desenha ponto dbInk( ncor, 0); dbDot (xpos + linha, ypos + coluna); coluna++; } // fim do for(coluna) coluna = 0; } // fim do for(linha) } // rasterizada_compactada().fim

index << >>


Produzido por Gameprog: Jair Pereira - Setembro/2013 © gameprog.br@gmail.com http://www.gameprog.com.br http://www.nucleoararat.com.br