O FORMATO BMP DE ARMAZENAMENTO DE IMAGENS
Este Capítulo descreve o formato de codificação adotado pela Microsoft para arquivamento de imagens em seus sistemas operacionais MS Windows. A estrutura de arquivos BMP é também utilizada para armazenamento de imagens em memória pelo próprio sistema operacional, assim sendo arquivos BMP podem ser lidos diretamente do disco para as áreas de arquivamento de imagens em memória do MS Windows, necessitando de pouco pré-processamento.FORMATOS SUPORTADOS PELO PADRÃO BMP
Imagens manipuladas em memória para apresentação em MS Windows são internamente armazenadas em formato DIB (Device-Independent Bitmap) o que permite ao Windows apresentar a imagem em qualquer tipo de dispositivo suportado pelo sistema operacional (telas, impressoras, projetores, etc.). O termo "device independent" embute o conceito de especificação de cores da imagem de uma maneira independente das limitações do dispositivo padrão de saída.
ESTRUTURA INTERNA DO FORMATO BMP
Cada arquivo BMP é composto por um cabeçalho (header), por uma área de informação do cabeçalho (bitmap-information header), uma tabela de cores (color table) e uma seqüência de bits (bitmap bits) que definem o conteúdo da imagem. Assim sendo o arquivo possui a seguinte forma genérica:
BITMAPFILEHEADER bmfh; |
BITMAPINFOHEADER bmih; |
RGBQUAD ColorTable[]; |
BYTE BitmapBits[]; |
typedef struct tagBITMAPFILEHEADER
{ DWORD Type;
DWORD Size;
LONG Reserved;
LONG Reserved2;
DWORD OffsetBits; }
A BITMAPINFOHEADER contém informações a respeito do tamanho, tipo de compressão e codificação de cores da imagem. E pode ser definido como na estrutura abaixo:
typedef struct tagBITMAPINFOHEADERA ColorTable[] é definida como um array de structs do tipo RGBQUAD e contém quantos elementos forem necessários para acomodar a codificação de paleta adotada pelo arquivo em questão. A ColorTable[] não é utilizada para imagens de 24 bits pois o seu tamanho superaria em muito o tamanho da própria imagem. As cores na tabela são apresentadas por ordem decrescente de ocorrência. Esta prioridade possibilita ao driver do dispositivo otimizar o seu uso de maneira a apresentar os dados da imagem da melhor maneira possível no dispositivo de saída.
{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
typedef struct tagRGBQUAD
{
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
A array BitmapBits[] contém seqüência de bits da imagem, seguindo-se imediatamente à ColorTable[]. Os bits da imagem são representados por valores do tipo BYTE, organizados na forma de "scan lines" que quando superpostas compõem a imagem.
Cada "scan line" consiste de uma seqüência de bytes que representam uma codificação de cor para cada pixel da linha, no sentido esquerda ->direita do monitor. O número de bytes utilizados para representar cada uma das linhas e seus pixels é dependente do formato de cor adotado pela imagem e da largura da imagem.
Por definição, o comprimento de cada linha deve ser múltiplo de 4, ou seja base 32 bits, o que traz melhor desempenho na manipulação da memória pela CPU. Os pixels excedentes numa linha deverão sempre ser preenchidos com o valor "null".
A ordem de apresentação das linhas é não usual: de baixo -> para cima, assim sendo a origem da imagem situa-se no canto INFERIOR esquerdo da tela, enquanto o sistema de coordenadas de tela normalmente tem sua origem do canto SUPERIOR esquerdo. Esta característica técnica do BMP e do MS Windows obriga a maioria dos códigos de carga de imagens não BMP a implementarem a carga "inversa" da imagem em memória.
O valor "biBitCount", membro da struct BITMAPINFOHEADER, determina o número de bits utilizados para definir cada pixel da imagem e pode assumir os seguintes valores:
Tabela - Significado dos valores de codificação de biBitCount para
Valor |
Significado |
1 |
A imagem contida no arquivo é monocromática, assim sendo a "color table" contém apenas duas cores. Cada bit na "bitmap bits" representa um pixel. Se o bit for null sua cor de apresentação é definida pelo primeiro valor de "color table", caso contrário, pelo segundo valor. |
4 |
A imagem contida no arquivo possui um máximo de 16 cores, assim sendo a "color table" contém 16 cores. Cada BYTE na "bitmap bits" representa dois pixels. Assim sendo se o valor lido de um BYTE for 243 (11110011) significa que o primeiro pixel deve ser apresentado pela 16a cor da "color table" e que o segundo pixel deve ser apresentado pela 3a cor de "color table" |
8 |
A imagem contida no arquivo possui um máximo de 256 cores, assim sendo a "color table" contém 256 cores. Cada BYTE na "bitmap bits" representa um pixel. Assim sendo se o valor lido de um BYTE for 243 (11110011) significa que o pixel deve ser apresentado pela 243a cor da "color table". |
24 |
A imagem contida no arquivo possui um máximo de 16777216 cores, assim sendo a "color table" contém 0 cores e seu valor é null. Cada pixel da imagem é representado por 3 BYTES na "bitmap bits", cada um referente a um componente de cor do pixel na ordem padrão RGB (vermelho, vede e azul). |
Os formatos BMP suportam compressão RLE de dados para codificações de imagens de 4 e 8 bits de cor. Por não utilizar a codificação por planos de cor, como o formato PCX, a compressão RLE de arquivos BMP de 24 bits seria altamente ineficiente.
Quando o valor "biCompression" da struct BITMAPINFOHEADER é setado em 1 então o arquivo utiliza compressão RLE de 8 bits. A compressão de 8 bits pode ocorrer de duas maneiras: "encoded" ou "absolute mode". Os dois modos de compressão podem ocorrer simultaneamente numa mesma imagem, em linhas ou coluna diferentes.
A interpretação deste par depende do valor do segundo BYTE que pode ser 0, 1 ou 2 de acordo com a Tabela abaixo:
Tabela - Interpretação do valor do segundo byte do modo encoded quando o primeiro byte é igual a zero.
Segundo BYTE |
Significado |
0 | Fim de linha. |
1 | Fim de imagem. |
2 | Indica que os seguintes 2 BYTES contém o número de colunas e linhas, respectivamente, que o cursor de leitura deve se deslocar a partir da posição corrente. |
O segundo BYTE representa o número de BYTES que se seguem sem compressão.
Tabela abaixo exemplifica uma seqüência de descompressão em função do conteúdo de "biCompression"
Tabela - Exemplos de descompressão RLE de 8 bits
Dado Comprimido |
Dado Expandido |
03 04 | 04 04 04 |
05 06 | 06 06 06 06 06 |
00 03 45 56 67 | 45 56 67 |
02 78 | 78 78 |
00 02 05 01 | Cursor 5 à direita e 1 abaixo |
02 78 | 78 78 |
00 00 | Fim de linha |
09 1E | 1E 1E 1E 1E 1E 1E 1E 1E 1E |
00 01 | Fim de imagem |
O primeiro pixel deve utilizar o índice de cor dos bits de alta ordem do segundo BYTE e o segundo pixel deve usar o índice de cor dos bits de baixa ordem do segundo BYTE, sucessivamente até esgotar-se o número de repetições especificado pelo primeiro BYTE.
Quando o primeiro BYTE de um par é setado em zero, ele indica um fim de linha, fim de imagem ou fim de um "off set". A interpretação deste par depende do valor do segundo BYTE que pode ser 0,1 ou 2 de acordo com a Tabela que segue.
O modo "absolute" também possui uma unidade de informação de 2 BYTES. Sua presença é sinalizada pelo primeiro BYTE do par contendo NULL, e o segundo BYTE um valor entre 3 e 255.
O segundo BYTE representa o número de BYTES que se seguem sem compressão, contendo cada um 2 pixels em seus bits de alta e baixa ordem. Segue-se um exemplo de compressão RLE4:
Tabela - Exemplos de descompressão RLE de 4 bits
Dado Comprimido |
Dado Expandido |
03 04 | 0 4 0 |
05 06 | 0 6 0 6 0 |
00 06 45 56 67 | 4 5 5 6 6 7 |
04 78 | 7 8 7 8 |
00 02 05 01 | Cursor 5 à direita e 1 abaixo |
04 78 | 7 8 7 8 |
00 00 | Fim de linha |
09 1E | 1 E 1 E 1 E 1 E 1 |
00 01 | Fim de imagem |
O exemplo a seguir ilustra o conteúdo de cada variável das estruturas que compõem um arquivo exemplo BMP de 4 bits (16 cores):
BitmapFileHeader.Type=19778
BitmapFileHeader.Size=3118
BitmapFileHeader.Reserved=0
BitmapFileHeader.Reserved2=0
BitmapFileHeader.OffsetBits=118
// Verifica se o formato da imagem é aceitável (deve ser 'BM')
if(BMPHdr.bfType != (('M' << 8) | 'B'))
return NULL;
// Determina o tamanho do DIB para leitura através do campo bfSize
// menos o tamanho do BITMAPFILEHEADER
long bmpsize = BMPHdr.bfSize - sizeof(BITMAPFILEHEADER);
// Aloca espaço para o bitmap
PointerToDIB = GlobalAllocPtr(GHND, bmpsize);
// Se a memória falha retorna NULL
if(PointerToDIB == 0)
return NULL;
// Aloca espaço para a imagem
unsigned char *rbuf = new unsigned char[MaxBlock];
if(rbuf == NULL)
return NULL;
unsigned char huge *DIBData = (unsigned char huge*)PointerToDIB;
unsigned int chunksize;
unsigned int i;
// Faz leitura por pedaços até acabar o arquivo
while(bmpsize > 0)
{
if(bmpsize > MaxBlock)
chunksize = MaxBlock;
else
chunksize = (WORD)bmpsize;
InputStream.read(rbuf, chunksize);
// Copia para o DIB
for(i = 0; i < chunksize; i++)
DIBData[i] = rbuf[i];
bmpsize -= chunksize;
DIBData += chunksize;
}
delete rbuf;
// Computa o número de bytes por linha, arredondando para múltiplo de 4
LPBITMAPINFOHEADER pBMPInfo = (LPBITMAPINFOHEADER)PointerToDIB;
BytePerLine = ((long)pBMPInfo->biWidth *
(long)pBMPInfo->biBitCount + 31L) / 32 * 4;
return TRUE;
}