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[];

O BITMAPFILEHEADER contém informações referentes ao tipo, tamanho e formato do arquivo. Como na estrutura abaixo:

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 tagBITMAPINFOHEADER
    {
    DWORD biSize;
    LONG biWidth;
    LONG biHeight;
    WORD biPlanes;
    WORD biBitCount;
    DWORD biCompression;
    DWORD biSizeImage;
    LONG biXPelsPerMeter;
    LONG biYPelsPerMeter;
    DWORD biClrUsed;
    DWORD biClrImportant;
    } BITMAPINFOHEADER;
 

A 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.  

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).

O valor "biClrUsed" da struct BITMAPINFOHEADER contém o número de cores efetivamente utilizado pela imagem. Se "biClrUsed" for igual zero, então a imagem efetivamente utiliza todas as cores especificadas na  "color table".

 

COMPRESSÃO DE DADOS

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.

O modo "
encoded" possui uma unidade de informação de 2 BYTES. O primeiro BYTE especifica o número de repetições do valor do segundo 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 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 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.

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

Quando o valor "biCompression" da struct  BITMAPINFOHEADER é igual a 2 então o arquivo utiliza compressão RLE de 4 bits. A compressão de 4 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 colunas diferentes.

O modo "encoded" possui uma unidade de informação de 2 BYTES. O primeiro BYTE especifica o número de repetições do valor do segundo BYTE. O segundo BYTE contém o índice de cor com 2 pixels consecutivos.

 

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

 

EXEMPLO DE ARQUIVO

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

BitmapInfoHeader.Size=40
BitmapInfoHeader.Width=80
BitmapInfoHeader.Height=75
BitmapInfoHeader.Planes=1
BitmapInfoHeader.BitCount=4
BitmapInfoHeader.Compression=0
BitmapInfoHeader.SizeImage=3000
BitmapInfoHeader.XPelsPerMeter=0
BitmapInfoHeader.YPelsPerMeter=0
BitmapInfoHeader.ColorsUsed=16
BitmapInfoHeader.ColorsImportant=16
ColorTable
R G B Unused
84 252 84 0
252 252 84 0
84 84 252 0
252 84 252 0
84 252 252 0
252 252 252 0
0 0 0 0
168 0 0 0
0 168 0 0
168 168 0 0
0 0 168 0
168 0 168 0
0 168 168 0
168 168 168 0
84 84 84 0
252 84 84 0
.
. Bitmap data
.
O algoritmo abaixo ilustra, simplificadamente, o esquema de descompressão RLE numa imagem de 8 bits e um plano de cor. Como o formato interno de armazenamento de memória do Windows aceita o arquivo de imagem comprimido, nenhum processamento é necessário, bastando que se copie o conteúdo do arquivo para a memória.
typedef struct tagBITMAPFILEHEADER
    {
    DWORD Type;
    DWORD Size;
    LONG Reserved;
    LONG Reserved2;
    DWORD OffsetBits;

    } typedef struct tagBITMAPINFO
    {
    BITMAPINFOHEADER bmiHeader;
    RGBQUAD bmiColors[1];
    } BITMAPINFO;
typedef struct tagBITMAPINFOHEADER
    {
    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;
  /**********************************************************************/
int TImage::ReadBMP(char *FileName) // Lê uma imagem tipo BMP
    {
    ifstream InputStream(FileName, ios::in | ios::binary);
    if(!InputStream)
        {
        return 0;
        }
    // Lê o header do arquivo imagem
    InputStream.read((unsigned char*)&BMPHdr, sizeof(BITMAPFILEHEADER));

    // 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;

}