Hlavní navigace

BMP na vícero způsobů

26. 10. 2006
Doba čtení: 13 minut

Sdílet

V dnešním článku si nejprve uvedeme výpis aplikace, která slouží pro získání základních informací o struktuře souboru typu BMP. Dále si ukážeme, jakým způsobem jsou uložena obrazová data v BMP souborech a předvedeme si strukturu BMP souborů používaných v OS/2 a nové hlavičky BMP začleněné do API Windows 95 a Windows 98.

Obsah

1. Testovací obrázky k tomuto článku
2. Aplikace pro výpis informací o struktuře obrázku typu BMP verze Microsoft Windows
3. Obrazová data uložená v souborech typu BMP
4. Komprimace obrazových dat v souborech typu BMP
5. Obrázky typu BMP ve verzi pro OS/2
6. Hlavička BMP verze 4
7. Hlavička BMP verze 5
8. Obsah dalšího pokračování tohoto seriálu

1. Testovací obrázky k tomuto článku

Testovací obrázky k dnešní části seriálu o grafických formátech byly získány ze stránky http://pobox.com/~ja­son1/bmpsuite/, za uvedení odkazu na tuto stránku tímto děkuji čtenáři Markovi Mauderovi (viz diskusi pod předchozí částí seriálu). Na výše uvedené webové adrese je archiv s několika BMP soubory, které se liší svým rozlišením (včetně „problémových“ rozlišení typu 127×64 pixelů), počtem bitů na pixel a v neposlední řadě také v tom, zda se jedná o soubory primárně určené pro operační systémy Microsoft Windows nebo OS/2.

S těmito obrázky by teoreticky měly pracovat všechny prohlížecí programy nebo grafické editory, avšak, jak je již při použití všech „okrajových podmínek“ obvyklé, narazil jsem na chybné chování některých aplikací. Konkrétně se to týká souboru g16def.bmp, který se mi nepodařilo zobrazit v žádné aplikaci používající KDE 3.3.2 (ze Sarge Debianu). Jedná se například o aplikace Konqueror, KuickShow, KView a KolourPaint. Chyba leží pravděpodobně v grafickém subsystému KDE.

2. Aplikace pro výpis informací o struktuře obrázku typu BMP verze Microsoft Windows

Dnešní (velmi jednoduchá) demonstrační aplikace slouží k výpisu základních informací o struktuře obrázku typu BMP ve verzi pro Microsoft Windows, pravděpodobnost setkání s jiným typem BMP je velmi malá (tento typ bitmap byl poprvé představen ještě v dobách šestnáctibitových Windows). Obrázky v této verzi obsahují, jak již víme z předchozí části tohoto seriálu, dvě základní datové struktury – BITMAPFILEHEADER a BITMAPINFOHEADER. První datová struktura má délku 14 bytů a druhá 40 bytů. Za oběma strukturami by měla ihned následovat buď barvová paleta nebo přímo údaje o jednotlivých pixelech. Výjimku tvoří soubory s 16bpp a 32bpp, které obsahují bitovou masku jednotlivých barvových kanálů.

Po překladu zdrojového kódu demonstrační aplikace je možné tuto aplikaci spustit a předat jí jako parametr jméno souboru s koncovkou BMP. Posléze by se měly na standardní výstup vypsat základní informace o tomto souboru, jako je jeho typ, velikost barvové palety (počet barev uložených v barvové paletě), rozlišení obrázku, počet bitů na pixel, použitá komprimační metoda, fyzické rozlišení obrázku (počet pixelů na metr v horizontálním i vertikálním směru) apod. Pokud se jedná o obrázek jiného typu, je o tom vypsáno upozornění a načítání hlavičky je ukončeno. Zdrojový kód demonstrační aplikace vypadá následovně:

//-----------------------------------------------------------------------------
// Utilita pro výpis základní struktury souboru typu BMP
// ve verzi pro Microsoft Windows.
// Pro elektronický časopis Root vytvořil Pavel Tišnovský
//-----------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>



//-----------------------------------------------------------------------------
// Datové typy - nutno upravit pro ne-32bitové překladače, nebo (pokud
// překladač splňuje ISO), raději použít místo těchto řádků hlavičkový soubor
// <stdint.h>
//-----------------------------------------------------------------------------
typedef unsigned char  uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int   uint32_t;



//-----------------------------------------------------------------------------
// Načtení jednoho byte ze souboru (8 bitů, bez znaménka)
//-----------------------------------------------------------------------------
uint8_t readByte(FILE *fin)
{
    int value=fgetc(fin);
    unsigned char result=(unsigned char)value;
    if (value==EOF) {
        printf("Chyba při čtení bytu na offsetu %lu ze vstupního souboru!\n", ftell(fin));
        exit(1);
        return 0;
    }
    else
        return result;
}



//-----------------------------------------------------------------------------
// Načtení jednoho slova ze souboru (16 bitů, bez znaménka)
//-----------------------------------------------------------------------------
uint16_t readWord(FILE *fin)
{
    return readByte(fin)+(readByte(fin)<<8);
}



//-----------------------------------------------------------------------------
// Načtení jednoho dvouslova ze souboru (32 bitů, bez znaménka)
//-----------------------------------------------------------------------------
uint32_t readDword(FILE *fin)
{
    return readWord(fin)+(readWord(fin)<<16);
}



//-----------------------------------------------------------------------------
// Výpis struktury BITMAPFILEHEADER - první datová struktura v souboru typu BMP
//-----------------------------------------------------------------------------
void printBitmapFileHeader(FILE *fin)
{
    uint8_t b1, b2;
    uint32_t posun;
    b1=readByte(fin);
    b2=readByte(fin);
    printf("\nDatová struktura BITMAPFILEHEADER\n");
    printf("Identifikátor souboru:            %02x %02x ('%c%c')\n", b1, b2, b1, b2);
    printf("Celková velikost souboru:         %u bytů\n", readDword(fin));
    printf("První rezervovaná oblast:         %d\n", (int)readWord(fin));
    printf("Druhá rezervovaná oblast:         %d\n", (int)readWord(fin));
    posun=readDword(fin);
    printf("Posun (začátek) obrazových dat:   %u bytů\n", posun);
    printf("Délka barvové palety:             %d bytů (=%d barev)\n", posun-54, (posun-54)>>2);
}



//-----------------------------------------------------------------------------
// Výpis struktury BITMAPINFOHEADER - druhá datová struktura v souboru typu BMP
//-----------------------------------------------------------------------------
void printBitmapInfoHeader(FILE *fin)
{
    unsigned int u;
    char * comp[]={
        "BI_RGB",
        "BI_RLE8",
        "BI_RLE4",
        "BI_BITFIELDS",
        "BI_JPEG",
        "BI_PNG",
    };
    printf("\nDatová struktura BITMAPINFOHEADER\n");
    u=readDword(fin);
    printf("Velikost celé struktury: %u   ",     u);
    // tři typy hlaviček, všechny jsou však větší než 40 bytů
    // a těchto 40 bytů je společných
    if (u>=40) printf("(soubor ve verzi Microsoft Windows)\n");
    else {
        printf("Neznámý typ souboru (verze pro OS/2?)!\n");
        return;
    }
    printf("Šířka obrázku:           %u px\n",   readDword(fin));
    printf("Výška obrázku:           %u px\n",   readDword(fin));
    printf("Počet bitových rovin:    %u\n", (int)readWord(fin));
    printf("Počet bitů na pixel:     %u\n", (int)readWord(fin));
    printf("Komprimační metoda:      %d ~ %s\n", (u=readDword(fin)), u<6 ? comp[u]: "neznámá");
    printf("Velikost obrazu:         %u bytů\n", readDword(fin));
    printf("Počet pixelů na metr:    %ux%u\n",   readDword(fin), readDword(fin));
    printf("Celkový počet barev:     %u\n",      readDword(fin));
    printf("Počet důležitých barev:  %u\n",      readDword(fin));
}



//-----------------------------------------------------------------------------
// Funkce spuštěná po inicializaci testovací aplikace
//-----------------------------------------------------------------------------
int main(int argc, char *argv[])
{
    FILE *fin;

    // kontrola, zda je zadáno jméno vstupního souboru
    if (argc<2) {
        printf("Není zadáno jméno vstupního souboru (počet parametrů=%d)!\n", argc);
        return -1;
    }

    // pokus o otevření zadaného vstupního souboru
    if (!(fin=fopen(argv[1], "rb"))) {
        printf("Nelze otevřít soubor %s pro čtení!\n", argv[1]);
        return -1;
    }

    // výpis základních datových struktur souboru
    printf("Jméno souboru: %s\n", argv[1]);
    printBitmapFileHeader(fin);
    printBitmapInfoHeader(fin);

    // pokus o uzavření souboru
    if (fclose(fin)==EOF) {
        printf("Chyba při uzavírání souboru %s!\n", argv[1]);
    }
    return 0;
}



//-----------------------------------------------------------------------------
// finito
//----------------------------------------------------------------------------- 

3. Obrazová data uložená v souborech typu BMP

V předchozí části tohoto seriálu jsme si ukázali, jakým způsobem jsou v obrazovém formátu BMP (verze Microsoft Windows) uloženy základní informace o obrázku, tj. jeho rozlišení, počet bitových rovin, počet bitů na pixel, použitá komprimační metoda apod. Také jsme si uvedli příklad barvové palety reprezentované polem datových struktur typu RGBQUAD. Ještě však neznáme formát uložení vlastního rastrového obrázku, tj. pravidelné mřížky složené z pixelů.

Rastrová data (pixely) jsou v grafickém souboru typu BMP uložena buď za barvovou paletou (v případě obrázků s 1bpp, 4bpp a 8bpp), nebo přímo za informační hlavičkou (obrázky s 16bpp, 24bpp a 32bpp). Data reprezentují po sobě jdoucí obrazové řádky (scanlines). Každý obrazový řádek sestává z hodnot reprezentujících jednotlivé pixely v pořadí zleva doprava. Počet bytů popisujících obrazový řádek je závislý na šířce obrázku a na barvovém formátu, tj. počtu bitů na pixel. Obrazový řádek musí být vždy zarovnaný na 4 byty (32 bitů), v případě nutnosti se doplňuje na 4 byty nulami – na toto zarovnání je zapotřebí myslet při načítání i ukládání.

Obrazové řádky se v rastrovém obrázku ukládají směrem zdola nahoru, což je odlišný způsob od většiny ostatních obrazových formátů (výjimku tvoří například grafický formát TGA, v jehož hlavičce je možné také nastavit opačné ukládání). To znamená, že první byte v poli BITS reprezentuje pixel (resp. pixely či jejich část) v levém dolním rohu bitmapy. Poslední byte v tomto poli potom reprezentuje barvu pixelů v pravém horním rohu.

Položka struktury BITMAPINFOHEADER nazvaná biBitCount (viz předchozí část tohoto seriálu) určuje počet bitů, které jsou zapotřebí pro reprezentaci barvy každého pixelu. Tato položka současně určuje maximální počet barev v bitmapě. Význam jednotlivých hodnot této položky je následující:

1 – Bitmapa je monochromatická, přičemž barevná paleta obsahuje dvě položky. Každý bit pole bitmapy reprezentuje jeden pixel. Jestliže není bit nastavený (je nulový), pixel bude zobrazený barvou první položky barvové palety. Jestliže je bit nastavený (je jedničkový), pixel bude zobrazený barvou druhé položky barvové palety.

4 – Bitmapa má maximálně 16 barev, přičemž barvová paleta obsahuje maximálně 16 položek. Každý pixel bitmapy je reprezentován čtyřmi bity. To znamená, že v jednom byte je uložen index do tabulky barev pro dva pixely. Pokud například první byte v bitmapě má hodnotu 0×2a, byte reprezentuje dva pixely. První pixel bude mít barvu třetí položky barvové palety (hodnota horních čtyřech bitů je 2), druhý pixel bude mít barvu jedenácté položky barvové palety (hodnota spodních čtyřech bitů je 10).

8 – Bitmapa má maximálně 256 barev, přičemž barvová paleta obsahuje maximálně 256 položek. Každý pixel bitmapy je reprezentovaný jedním bytem, který udává index do tabulky barev, podobně jako v předchozích případech.

16 – Bitmapa je uložena Hi-color režimu, na každou barvovou složku připadá 5 bitů, výjimku tvoří zelená barvová složka, která může být uložena na 6 bitů. Barvová paleta není použita.

24 – Bitmapa je uložena v TrueColor režimu, tzn. obsahuje maximálně 224 barev (16 777 216 barev). Barevná paleta není uložena, protože je pixel reprezentován přímo svými RGB hodnotami. Každý pixel je uložen ve třech bytech, kde každý byte reprezentuje intenzity červené, zelené a modré barvové složky.

32 – Bitmapa je uložena v TrueColor režimu, tzn. obsahuje maximálně 224 barev (16 777 216 barev). Podobně jako v předchozím typu, i zde není použita barvová paleta, zato je použita bitová maska na hodnoty jednotlivých barvových složek.

4. Komprimace obrazových dat v souborech typu BMP

Operační systémy Windows od verze 3.0 podporují jednoduchou RLE (Run Length Encoding) komprimaci bitových map. RLE komprimaci lze využít pouze u bitmap s čtyřmi resp. osmi bity na pixel. U  jednobitových a TrueColor bitmap jsou obrázky vždy uloženy v nekomprimované podobě, což však, jak uvidíme dále, nemusí být na škodu, protože komprimační poměry jsou malé.

Jestliže je položka biCompression struktury BITMAPINFOHEADER nastavena na hodnotu BI_RLE8, je bitmapa komprimovaná RLE metodou pro 256ti barvové bitmapy.

Informace jsou v bitmapě organizovány do skupiny po dvou bytech. První byte určuje počet po sobě jdoucích pixelů, které budou mít index barvy určený v druhém bytu. První byte ve skupině může být nastavený na nulu, což znamená tzv. únik: například konec řádku, konec bitmapy nebo delta. Typ úniku je zapsán ve druhém bytu skupiny. Význam jednotlivých únikových hodnot je následující:

hodnota druhého byte ve skupině význam úniku
0×00 indikuje konec řádku
0×01 indikuje konec bitmapy
0×02 delta – dva byty následující za touto hodnotou obsahují bezznaménkové (unsigned char) hodnoty, které znamenají horizontální a vertikální posunutí následujících pixelů od aktuální pozice
0×03–0×ff druhý byte určuje počet bytů, které následují. Tyto byty přímo určují pixely v nezkomprimovaném tvaru – co byte to jeden pixel.

Následuje ukázka 256ti barevné bitmapy (všechny hodnoty jsou zapsané v hexadecimálním tvaru):

Komprimovaná data Nekomprimovaná data
05 10 10 10 10 10 10
03 aa aa aa aa
00 03 12 34 56 12 34 56
02 bb bb bb
00 02 0a 14 posun o 10 pixelů vpravo a 20 pixelů dolů
02 cc cc cc
00 00 konec řádku
01 dd dd
00 01 konec bitmapy

Jestliže je položka biCompression struktury BITMAPINFOHEADER nastavena na hodnotu BI_RLE4, je bitmapa komprimovaná RLE metodou pro šestnáctibarevné bitmapy.

Informace jsou v bitmapě organizovány do skupiny dvou bytů. První byte určuje počet po sobě jdoucích pixelů, které budou mít index barvy určený v druhém bytu. Druhý byte obsahuje dva indexy barev, uložené v nejvyšších resp. nejnižších čtyřech bitech daného bytu (masky pro získání těchto čtyřech bitů jsou 0×0f a 0×f0). První pixel ve skupině je zobrazený barvou s indexem získaným maskou 0×f0, druhý pixel barvou s indexem získaným maskou 0×0f. Třetí pixel má barvu stejnou jako první pixel atd. Barvy jsou postupně přiřazeny všem pixelům ve skupině.

První byte ve skupině může být nastavený na nulu, což znamená takzvaný únik: například konec řádku, konec bitmapy nebo delta. Typ úniku je zapsán v druhém bytu skupiny. Význam jednotlivých únikových hodnot:

hodnota druhého byte ve skupině význam úniku
0×00 indikuje konec řádku
0×01 indikuje konec bitmapy
0×02 delta – dva byty následující za touto hodnotou obsahují bezznaménkové (unsigned char) hodnoty, které znamenají horizontální a vertikální posunutí následujících pixelů od aktuální pozice
0×03–0×ff druhý byte určuje počet indexů barev, které následují v nezkomprimo­vaném tvaru.

Ukázka 16ti barevné bitmapy (všechny hodnoty jsou zapsané v hexadecimálním tvaru):

Komprimovaná data Nekomprimovaná data
05 01 0 1 0 1 0
03 12 1 2 1
00 06 12 34 56 1 2 3 4 5 6
04 ab a b a b
00 02 0a 14 posun o 10 pixelů vpravo a 20 pixelů dolů
02 cc c c
00 00 konec řádku
09 ef e f e f e f e f e
00 01 konec bitmapy

5. Obrázky typu BMP ve verzi pro OS/2

Aby práce se soubory typu BMP nebyla tak jednoduchá, existuje několik verzí hlaviček. Historickým předchůdcem výše popsané hlavičky BMP dle Microsoftu je hlavička používaná v OS/2 a Windows 3.0. První datová struktura v souboru, tj. BITMAPFILE­HEADER zůstává stejná, ale mění se struktura BITMAPINFOHEADER na BITMAPCOREHEADER, která je kratší – oproti 40 bytům má velikost pouhých 12 bytů. To je ostatně jediný rozlišující znak mezi oběma hlavičkami a všechny aplikace pracující s BMP by tento údaj měly brát v úvahu. Strukturu BITMAPCOREHEADER je možné získat například z hlavičkového souboru wingdi.h:

typedef struct tagBITMAPCOREHEADER {
        DWORD   bcSize;          // velikost struktury (12)
        WORD    bcWidth;         // šířka obrázku v pixelech
        WORD    bcHeight;        // výška obrázku v pixelech
        WORD    bcPlanes;        // počet bitových rovin, zde musí být jednička
        WORD    bcBitCount;      // počet bitů na pixel (1, 4, 8, 24)
}; 

(struktura je získána z hlavičkových souborů MinGW, žádnou licenci jsem tím pravděpodobně neporušil). Také barvová paleta je uložena odlišně, protože je použita struktura RGBTRIPLE, kde je každé barvové složce přiřazen jeden byte bez doplňující nuly. Dnes je již vzácností se s tímto typem bitmap setkat, spíše je najdeme v historických aplikacích určených pro šestnáctibitová Windows nebo první dvě verze OS/2.

6. Hlavička BMP verze 4

Dvě verze hlaviček bitmap pravděpodobně pro zmatení programátorů nedostačují, proto byl formát BMP rozšířen s příchodem Windows 95. Nová hlavička dostala jméno BITMAPV4HEADER, protože Windows 95 se vlastně měly jmenovat Windows 4.0. Tato „čtvrtá“ hlavička je již poměrně složitá jak na zpracování, tak i na popis a rozrostla se na délku 108 bytů:

typedef struct
{
    DWORD        bV4Size ;           // velikost celé struktury=108 bytů
    LONG         bV4Width ;          // šířka bitmapy v pixelech
    LONG         bV4Height ;         // výška bitmapy v pixelech
    WORD         bV4Planes ;         // počet bitových rovin je vždy nastaven na 1
    WORD         bV4BitCount ;       // počet bitů na pixel (1, 4, 8, 16, 24, or 32)
    DWORD        bV4Compression ;    // komprimační metoda
    DWORD        bV4SizeImage ;      // počet bytů pro uložení celého obrázku
    LONG         bV4XPelsPerMeter ;  // horizontální rozlišení (pixely na metr)
    LONG         bV4YPelsPerMeter ;  // vertikální rozlišení (pixely na metr)
    DWORD        bV4ClrUsed ;        // počet všech barev
    DWORD        bV4ClrImportant ;   // počet použitých barev
    DWORD        bV4RedMask ;        // maska pro červenou barvovou složku
    DWORD        bV4GreenMask ;      // maska pro zelenou barvovou složku
    DWORD        bV4BlueMask ;       // maska pro modrou barvovou složku
    DWORD        bV4AlphaMask ;      // maska pro alfa kanál
    DWORD        bV4CSType ;         // typ barvového prostoru
    CIEXYZTRIPLE bV4Endpoints ;      // hodnoty pro převod mezi RGB a CIE-xy
    DWORD        bV4GammaRed ;       // hodnota gamma faktoru pro červenou barvovou složku
    DWORD        bV4GammaGreen ;     // hodnota gamma faktoru pro zelenou barvovou složku
    DWORD        bV4GammaBlue ;      // hodnota gamma faktoru pro modrou barvovou složku
} BITMAPV4HEADER; 

7. Hlavička BMP verze 5

Firma Microsoft je v některých oblastech velmi pilná a proto spolu s Windows 98 „svůj“ formát BMP znovu rozšířila a definovala další hlavičku, tentokrát se jménem BITMAPV5HEADER. Sice jsem se s obrázkem oplývajícím touto hlavičkou nikdy nesetkal, ale pro jistotu uvádím její „céčkovský“ popis:

CS24_early

typedef struct
{
    DWORD        bV5Size ;           // velikost celé struktury=124 bytů
    LONG         bV5Width ;          // šířka bitmapy v pixelech
    LONG         bV5Height ;         // výška bitmapy v pixelech
    WORD         bV5Planes ;         // počet bitových rovin je vždy nastaven na 1
    WORD         bV5BitCount ;       // počet bitů na pixel (1, 4, 8, 16, 24, or 32)
    DWORD        bV5Compression ;    // komprimační metoda
    DWORD        bV5SizeImage ;      // počet bytů pro uložení celého obrázku
    LONG         bV5XPelsPerMeter ;  // horizontální rozlišení (pixely na metr)
    LONG         bV5YPelsPerMeter ;  // vertikální rozlišení (pixely na metr)
    DWORD        bV5ClrUsed ;        // počet všech barev
    DWORD        bV5ClrImportant ;   // počet použitých barev
    DWORD        bV5RedMask ;        // maska pro červenou barvovou složku
    DWORD        bV5GreenMask ;      // maska pro zelenou barvovou složku
    DWORD        bV5BlueMask ;       // maska pro modrou barvovou složku
    DWORD        bV5AlphaMask ;      // maska pro alfa kanál
    DWORD        bV5CSType ;         // typ barvového prostoru
    CIEXYZTRIPLE bV5Endpoints ;      // hodnoty pro převod mezi RGB a CIE-xy
    DWORD        bV5GammaRed ;       // hodnota gamma faktoru pro červenou barvovou složku
    DWORD        bV5GammaGreen ;     // hodnota gamma faktoru pro zelenou barvovou složku
    DWORD        bV5GammaBlue ;      // hodnota gamma faktoru pro modrou barvovou složku
    DWORD        bV5Intent ;         // typ barvového profilu (také informace, zda je vůbec využit)
    DWORD        bV5ProfileData ;    // většinou jméno souboru s barvovým profilem
    DWORD        bV5ProfileSize ;    // délka barvového profilu
    DWORD        bV5Reserved ;       // rezervovaná položka (nastavená na nulu)
} BITMAPV4HEADER; 

8. Obsah dalšího pokračování tohoto seriálu

V následujícím pokračování tohoto seriálu se zaměříme na další oblíbený grafický formát. Jedná se o formát Targa (TGA), s nímž je práce ještě jednodušší než se zde popisovaným formátem BMP. Historicky šlo o jeden z prvních formátů, který i na obyčejná PC přinesl TrueColor grafiku, tj. 16 milionů barev.

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.