Hlavní navigace

Rastrové grafické formáty Wireless Bitmap a IMG

22. 2. 2007
Doba čtení: 13 minut

Sdílet

V dnešním článku dokončíme část věnovanou rastrovým, tj. bitmapovým či pixmapovým grafickým formátům. Popíšeme si strukturu jednoduchých rastrových souborů typu WBMP (Wireless Bitmap), který je používán především na mobilních zařízeních, a také formátu IMG (Image) firmy Alias Research.

Obsah

1. Grafický formát IMG (Image)
2. Ukázka obrázku uloženého ve formátu IMG
3. Program pro převod obrázku z formátu IMG do formátu TGA
4. Rastrový grafický formát WBMP (Wireless Bitmap)
5. Odkazy na další informační zdroje
6. Obsah dalšího pokračování tohoto seriálu

1. Grafický formát IMG (Image)

Jak již bylo napsáno v perexu článku, budeme se dnes zabývat velmi jednoduchými rastrovými grafickými formáty, se kterými se můžeme setkat například v datových souborech různých graficky orientovaných programů, her a podobně. Prvním takovým formátem je formát nazvaný jednoduše Image, z čehož je odvozena i jeho tříznaková souborová koncovka – IMG. Tento formát navrhla před mnoha lety firma Alias Research a posléze se stal mezi uživateli osobních počítačů i tehdejších grafických stanic poměrně populární, zejména díky své jednoduchosti a přenositelnosti. Úspěch založený na jednoduchosti použití a dostupnosti dokumentace ostatně měl i dříve popisovaný formát Targa (viz odkazy na další části tohoto seriálu uvedené pod článkem).

Mezi známé aplikace pro počítače PC, které implicitně tento formát používají či používaly, patřil například vizualizační program (konkrétně raytracer) Vivid. Ten pracoval i na procesorech Intel 80286 bez matematického koprocesoru, na rozdíl od mnohem populárnějšího POV-Raye, který již vyžaduje plnohodnotný procesor Intel 80386. IMG byl používán i některými dalšími DOSovskými utilitami pracujícími s plnobarevnou (truecolor) grafikou, včetně prohlížečů (PV apod.). Formát IMG byl (vedle dalších formátů, například již dříve popisovaných TGA, PCX a BMP) podporován i v několika graficky orientovaných knihovnách určených pro tehdy velmi populární Turbo Pascal firmy Borland.

Grafický formát IMG se z programátorského hlediska vyznačuje především jednoduchou a snadno zpracovatelnou hlavičkou, podporou plnobarevné grafiky uložené v barvovém prostoru RGB (Red, Green, Blue) a jednoduchou komprimací obrazových dat prováděnou pomocí metody RLE (Run Length Encoding), která je však prováděna poněkud odlišným způsobem, než u dříve popisovaných rastrových formátů PCX a BMP. Uvidíme, že dále popisovaný postup dekódování je velmi jednoduchý, avšak relativně málo účinný, zejména pro fotografie a jiné zašuměné obrázky. V praxi se však IMG používal především ve vizualizačních programech, zpracování fotografií bylo v té době prakticky neznámé (snad kromě profesionálních DTP).

Podívejme se nejdříve, jak vypadá hlavička tohoto rastrového obrazového formátu. Celá hlavička má délku přesně deseti bytů, přičemž neobsahuje žádné „magické číslo“, které by soubor jednoznačně identifikovalo. To znamená, že typ souboru je odvozen pouze z jeho koncovky, což je velmi nešťastné, protože koncovka IMG se vyskytuje i u několika dalších rastrových formátů s odlišně strukturovaným obsahem, takže například prohlížeče mohou mít s rozhodováním, o jaký formát se konkrétně jedná, velké problémy. Hlavička obsahuje celkem pět položek s následujícím významem:

Offset Počet bytů Význam
0 2 šířka obrázku uváděná v pixelech
2 2 výška obrázku uváděná v pixelech
4 2 číslo prvního uloženého obrazového řádku
6 2 číslo posledního uloženého obrazového řádku
8 2 počet bitů na pixel (bpp)

Šířka i výška obrázku je uložená ve formátu big-endian, tj. jako první se v souboru nachází vyšší byte po němž následuje nižší byte (to znamená, že na platformě x86 musíme byty před čtením či naopak zápisem prohazovat). Číslo prvního uloženého obrazového řádku bývá nastaveno na nulu, číslo posledního uloženého řádku většinou odpovídá výšce obrázku. Pokud jsou zde jiné hodnoty, může to například znamenat, že raytracer nevytvořil celý obrázek (výpočet byl přerušen). V dokumentaci k tomuto formátu se sice píše, že poslední obrazový řádek by měl být roven hodnotě výška-1, soubory, se kterými jsem se setkal, však zde mají přímo uloženou hodnotu výška. Z tohoto důvodu je lepší provádět čtení až do konce souboru a počitadlo řádků průběžně zvyšovat podle načtených dat.

Počet bitů na pixel (tato hodnota je taktéž uložena na dvou bytech) by měl být vždy nastaven na hodnotu 24, což odpovídá více než šestnácti milionům barev (truecolor). Ihned po takto strukturované desetibytové hlavičce následují obrazová data, která jsou uložena po jednotlivých obrazových řádcích (scanlines). Můžeme se setkat s obrázky uloženými se vzestupnou sekvencí obrazových řádků (dnes přirozená reprezentace), častěji se však jedná o obrázky uložené právě naopak – nejprve je v souboru uložen poslední řádek, pak předposlední atd.

Obrazová data představující jednotlivé pixely jsou na každém obrazovém řádku zkomprimována jednoduchou metodou RLE, sdružující sousední pixely do takzvaných sledů, jejichž princip spočívá v tom, že je nejprve uložen jeden byte s hodnotou počtu opakování (1–255, nula by se zde neměla vyskytovat), za nímž následuje trojice bytů nesoucích barvové hodnoty Red, Green a Blue. Dekódování hodnot jednotlivých pixelů při načítání obrázku tedy probíhá tak, že se načte počet opakování plus tři další byty, které se na výstupu n× opakují, přičemž n je hodnota prvního načteného bytu.

Při vytváření obrázku se většinou zpracovávají jednotlivé řádky samostatně, tj. pomocí RLE nejsou zpracovávány sledy („runy“) přesahující jeden obrazový řádek – to zjednodušuje proces dekódování, například při zobrazování na grafických kartách pracujících v prokládaném (interlaced) režimu. Každý sled („run“) je uložen na čtyřech bytech a má následující podobu:

Počet bytů Význam
1 počet opakování sledu
1 hodnota modré barvové složky
1 hodnota zelené barvové složky
1 hodnota červené barvové složky

2. Ukázka obrázku uloženého ve formátu IMG

Jak jsem se již zmínil v předchozí kapitole, obrázky typu IMG vytváří například raytracer Vivid. Pomocí tohoto raytraceru (spouštěného v DOS-Boxu, protože na počítači s procesorem Intel Celeron se tato starožitnost již „přímo“ nerozběhla ) jsem vytvořil následující obrázek, který můžete získat v originální podobě pod tímto odkazem. Vzhledem k tomu, že současné webové prohlížeče formát IMG většinou ignorují, přikládám i podobu tohoto obrázku zkonvertovanou do modernějšího a dnes rozšířenějšího formátu PNG:

28_1

Pojďme se nyní podívat na hlavičku obrázku uloženého v souboru typu IMG. Prvních 64 bytů v souboru vypadá po prohlédnutí hexadecimálním editorem (zde konkrétně po filtraci programem xxd) následovně:

0000000: 01 40 00 f0 00 00 00 f0 00 18 0a 35 79 a6 12 36
0000010: 7a a7 06 36 7a a8 12 36 7b a8 12 36 7b a9 12 36
0000020: 7c a9 06 36 7c aa 11 30 6e 97 2c 37 7c aa 18 30
0000030: 6e 97 05 36 7c aa 12 36 7c a9 12 36 7b a9 12 36 

Z této sekvence bytů můžeme vyčíst následující základní údaje o obrázku:

Offset Sekvence bytů Význam
0 01 40 šířka obrázku je rovna 320 pixelům
2 00 f0 výška obrázku je rovna 240 pixelům
4 00 00 číslo prvního uloženého obrazového řádku je 0
6 00 f0 číslo posledního uloženého obrazového řádku je 240
8 00 18 počet bitů na pixel je roven 24 (odpovídá 16 milionům barev)

Po hlavičce již následují jednotlivé sledy, ve kterých jsou uloženy barvy pixelů. Zde si ukážeme pouze první tři sledy:

Offset Sekvence bytů Význam
10 0a 35 79 a6 10 pixelů s barvou #a67935 (HTML formát)
14 12 36 7a a7 18 pixelů s barvou #a77a36
18 06 36 7a a8 6 pixelů s barvou #a87a36

3. Program pro převod obrázku z formátu IMG do formátu TGA

V předchozích dvou kapitolách jsme si řekli, že formát IMG je z programátorského hlediska velmi jednoduchý. Načtení hlavičky je snadné, nesmíme však zapomenout na pořadí uložených bytů, které odpovídá konvenci big-endian. Na platformě x86 a dalších platformách používajících konvenci little-endian je možné použít buď funkce htons() a ntohs(), nebo (pokud tyto funkce nejsou dostupné, což bývá v případě mikrořadičů poměrně časté), například následující céčkové makro:

#define twoBytes2int(b1, b2) ((int)(b1)<<8 | (b2)) 

Funkce, která se stará o načítání a rozkódování jednotlivých sledů do sekvence barev pixelů, vypadá následovně. Za povšimnutí stojí způsob konstrukce vnitřní smyčky, díky kterému je zajištěno korektní chování i v případě, že je do proměnné count načtena nulová hodnota.

//-----------------------------------------------------------------------------
// Prevod obrazovych dat (dekomprimace) z formatu IMG do formatu TGA
//-----------------------------------------------------------------------------
int convert(FILE *fin, FILE *fout)
{
    int count, r, g, b;
    // nacteme vsechny ctverice hodnot ve sledu ("runu")
    while ((count=fgetc(fin))!=EOF &&
               (r=fgetc(fin))!=EOF &&
               (g=fgetc(fin))!=EOF &&
               (b=fgetc(fin))!=EOF) {
        // rozkodovat cely sled a zapsat do vystupniho TGA
        while (count--) {
            if (fputc(r, fout)==EOF) return 0;
            if (fputc(g, fout)==EOF) return 0;
            if (fputc(b, fout)==EOF) return 0;
        }
    }
    return 1;
} 

Následuje výpis programu, který slouží pro převod obrázků z formátu IMG do formátu TGA. Program po svém spuštění očekává, že je mu jako první argument předán název souboru s obrázkem BEZ koncovky. Dále je načtena hlavička obrázku, ze které je zjištěno rozlišení a následuje dekódování jednotlivých sledů spolu s ukládáním hodnot barev pixelů do nově vytvářeného obrázku ve formátu Targa (TGA). Zdrojový kód programu je dostupný zde, k dispozici je i HTML verze se zvýrazněním syntaxe.

//-----------------------------------------------------------------------------
// Prevodni program mezi grafickym formatem IMG a TGA
//
// Soucast serialu "Graficke formaty" publikovaneho na http://www.root.cz
// Autor: Pavel Tisnovsky
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define EXT_IMG ".img"
#define EXT_TGA ".tga"



//-----------------------------------------------------------------------------
// Otevreni souboru typu IMG (jmeno neobsahuje koncovku)
//-----------------------------------------------------------------------------
FILE *openImg(char *name)
{
    char *imgName=(char *)malloc(strlen(name)+strlen(EXT_IMG)+1);
    strcpy(imgName, name);
    strcat(imgName, EXT_IMG);
    printf("Opening file %s for reading\n", imgName);
    return fopen(imgName, "rb");
}



//-----------------------------------------------------------------------------
// Otevreni souboru typu TGA (jmeno neobsahuje koncovku)
//-----------------------------------------------------------------------------
FILE *openTga(char *name)
{
    char *tgaName=(char *)malloc(strlen(name)+strlen(EXT_TGA)+1);
    strcpy(tgaName, name);
    strcat(tgaName, EXT_TGA);
    printf("Opening file %s for writing\n", tgaName);
    return fopen(tgaName, "wb");
}



//-----------------------------------------------------------------------------
// Nacteni hlavicky ze souboru typu IMG, naplnuje se i rozliseni
//-----------------------------------------------------------------------------
int readIMGHeader(FILE *fin, int *width, int *height)
{
    // makro pro prevod dvojice bytu na integer v usporadani big endian
#define twoBytes2int(b1, b2) ((int)(b1)<<8 | (b2))
    unsigned char imgHeader[10];
    int firstLine, lastLine, bpp;
    if (fread(imgHeader, 10, 1, fin)!=1) return 0;
    *width=    twoBytes2int(imgHeader[0], imgHeader[1]);
    *height=   twoBytes2int(imgHeader[2], imgHeader[3]);
    firstLine= twoBytes2int(imgHeader[4], imgHeader[5]);
    lastLine=  twoBytes2int(imgHeader[6], imgHeader[7]);
    bpp=       twoBytes2int(imgHeader[8], imgHeader[9]);
    printf("IMG header: width=     %d\n", *width);
    printf("IMG header: height=    %d\n", *height);
    printf("IMG header: 1st line=  %d\n", firstLine);
    printf("IMG header: last line= %d\n", lastLine);
    printf("IMG header: bpp=       %d\n", bpp);
    return 1;
}



//-----------------------------------------------------------------------------
// Ulozeni hlavicky souboru typu TGA
//-----------------------------------------------------------------------------
int writeTGAHeader(FILE *fout, int width, int height)
{
    unsigned char tgaHeader[18]={               // hlavicka formatu typu TGA
                        0x00,                   // typ hlavicky TGA
                        0x00,                   // nepouzivame paletu
                        0x02,                   // typ obrazku je RGB TrueColor
                        0x00, 0x00,             // delka palety je nulova
                        0x00, 0x00, 0x00,       // pozice v palete nas nezajima
                        0x00, 0x00, 0x00, 0x00, // obrazek je umisteny na pozici [0, 0]
                        0x00, 0x00, 0x00, 0x00, // sirka a vyska obrazku (dva byty na polozku)
                        0x18,                   // format je 24 bitu na pixel
                        0x20                    // orientace bitmapy v obrazku
    };
    memcpy(tgaHeader+12, &width, 2);            // do hlavicky TGA zapsat sirku obrazku
    memcpy(tgaHeader+14, &height, 2);           // do hlavicky TGA zapsat vysku obrazku
    return fwrite(tgaHeader, 18, 1, fout)==1;   // zapsat hlavicku TGA do souboru
}



//-----------------------------------------------------------------------------
// Prevod obrazovych dat z formatu IMG do formatu TGA
//-----------------------------------------------------------------------------
int convert(FILE *fin, FILE *fout)
{
    int count, r, g, b;
    // nacteme vsechny ctverice hodnot ve sledu ("runu")
    while ((count=fgetc(fin))!=EOF &&
               (r=fgetc(fin))!=EOF &&
               (g=fgetc(fin))!=EOF &&
               (b=fgetc(fin))!=EOF) {
        // rozkodovat cely sled a zapsat do vystupniho TGA
        while (count--) {
            if (fputc(r, fout)==EOF) return 0;
            if (fputc(g, fout)==EOF) return 0;
            if (fputc(b, fout)==EOF) return 0;
        }
    }
    return 1;
}



//-----------------------------------------------------------------------------
// Hlavni funkce konzolove aplikace
//-----------------------------------------------------------------------------
int main(int argc, char **argv)
{
#define my_assert(cond, text)\
    if ((cond)) {\
        fprintf(stderr, "error: %s\n", (text));\
        return 1;\
    }

    FILE *fin;
    FILE *fout;
    int width, height;

    puts("img2tga processing:");
    my_assert(argc<2, "file name (without extension) required!");
    my_assert(!(fin=openImg(argv[1])), "open IMG for reading failed!");
    my_assert(!(fout=openTga(argv[1])), "open TGA for writing failed!");
    my_assert(!readIMGHeader(fin, &width, &height), "read IMG header failed!");
    my_assert(!writeTGAHeader(fout, width, height), "write TGA header failed!");
    my_assert(!convert(fin, fout), "unable to convert data!");
    my_assert(fclose(fin), "close IMG failed!");
    my_assert(fclose(fout), "close TGA failed!");
    puts("img2tga done.\n");
    return 0;
}



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

4. Rastrový grafický formát WBMP (Wireless Bitmap)

Posledním rastrovým grafickým formátem, který si v tomto seriálu podrobněji popíšeme, je formát označovaný zkratkou WBMP, neboli Wireless Bitmap Format. Ve skutečnosti se nejedná pouze o souborový formát (obrázky tohoto typu jsou typicky uloženy na WAP serverech), ale i o formát přenosu obrázků pro aplikace mobilních zařízení, typicky mobilních telefonů a PDA. Samotné WBMP je součástí protokolu WAP 2.0 (Wireless Application Protocol), proto je možné na soubory tohoto typu narazit zejména na serverech, jejich podpora je většinou zajištěna v aplikacích sloužících pro tvorbu (i automatickou) a editaci WAP stránek. WBMP je podporováno i v knihovně libgd, kterou jistě mnozí tvůrci dynamických webů znají, třeba jen pod jménem GD. Pojďme si nyní říci, jaké vlastnosti tento formát má a pro které obrázky je ho možné použít.

Formát WBMP je prozatím definovaný pouze pro černo-bílé (obecněji i pro dvoubarevné) bitmapové obrázky ukládané bez komprimace. Vlastní komprimaci může zajišťovat například přenosový protokol, což je vcelku rozumná volba. WBMP pro dvoubarevné obrázky se označuje jako WBMP úrovně 0 (WBMP level 0). Další úrovně nejsou specifikovány a podle všeho to vypadá tak, že ani specifikovány nebudou, protože se v současnosti pro vícebarevné obrázky doporučuje i na mobilních zařízeních používat oblíbený formát PNG, kterým jsme se zabývali v předchozích částech tohoto seriálu (tento postup zcela jistě souvisí s rostoucí schopností používaných displejů i rychlostí procesorů, které jsou v mobilních zařízených použity).

Obrázky typu WBMP obsahují informační hlavičku o proměnné délce, za níž následují zakódované barvy jednotlivých pixelů. Nejzajímavější je z programátorského hlediska struktura hlavičky, protože některé údaje v ní mohou mít různou délku v závislosti na konkrétní zapisované hodnotě. Tento způsob ukládání bývá označován jako multibyte nebo též multioctet. Hlavička má formát:

Byte (octet) Význam
0 úroveň WBMP, nastaveno na 0
1 pro fixní hlavičku je zde 0
zde může být uložena rozšiřující hlavička
2 šířka bitmapy uložená ve formátu multibyte (délka 8 bitů až m×8 bitů)
? výška bitmapy uložená ve formátu multibyte (délka 8 bitů až n×8 bitů)

Jak vypadá způsob ukládání typu multibyte? Hodnoty, které jsou menší než 128, jsou jednoduše uloženy v jednom bytu, to znamená, že jejich nejvyšší bit je roven nule. Hodnoty větší než 128 jsou uloženy ve více bytech a to tak, že pro byty, za kterými ještě nějaká hodnota následuje, mají nejvyšší bit nastavený na jedničku a poslední byte v celé sekvenci má tento bit nulový, tj. nabývá hodnot 0 až 127. Nejvyšší bit v každém bytu tedy slouží jako indikace zarážky. Jednotlivé byty jsou uloženy ve formátu big-endian, tj. nejvyšší byte je přečten i uložen jako první. V praxi to může vypadat následovně:

hodnota 0x10 se uloží jako jeden byte s hodnotou 0x10
hodnota 0xa0 se uloží na dvou bytech 0x81 0x20, protože:
             0x81 ~ 1000 0001 (sekvence pokračuje)
             0x20 ~ 0010 0000 (nastavena zarážka)
             bitově složíme výslednou hodnotu: 000 0001 010 0000 ~ 0xa0 

Za hlavičkou následují hodnoty jednotlivých pixelů, vždy osm pixelů je složeno do jednoho bytu (octetu). Nulový bit značí černý pixel, jedničkový bit pixel bílý. Konkrétní způsob zobrazení je samozřejmě závislý na použitém displeji, takže místo černých a bílých pixelů se může jednat o černé a světle zelené pixely atd. Vzájemný kontrast by však měl být zachován, tj. nemělo by dojít k inverzi obrázku. Každý obrazový řádek je zarovnán na celé byty, tj. například pro horizontální rozlišení 60 pixelů má každý řádek vždy osm bytů (8×8=64), přičemž poslední čtyři bity zůstávají nevyužity a měly by být vynulovány. Jak je z tohoto popisu patrné, jedná se o velmi jednoduchý formát, jediné komplikace mohou nastat při dekódování multibyte sekvencí.

root_podpora

5. Odkazy na další informační zdroje

  1. Wireless Application Protocol (WAP 2.0)
    Technical White Paper, January 2002
  2. Open Mobile Aliance:
    http://www.wap­forum.org
  3. WBMP v Javě:
    http://www-128.ibm.com/de­veloperworks/wi­reless/library/wi-wbmp/?t=grwrl­s,p=WBMP
  4. WapTiger WBMP Converter (bmp->wbmp):
    http://gcov.php­.net/PHP4_4/lcov_html/ex­t/gd/libgd/wbmp­.c.gcov.php
  5. http://www.wap­tiger.com/dow­nload/
  6. Zdrojový kód aplikace z předchozího odkazu:
    http://www.wap­tiger.com/dow­nload/src/bmp2w­bmp.c
  7. WAP na Wikipedii:
    http://en.wiki­pedia.org/wiki/WAP
  8. WBMP na Wikipedii:
    http://en.wiki­pedia.org/wiki/Wi­reless_Applica­tion_Protocol_Bit­map_Format

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

V následujícím pokračování tohoto seriálu se již zaměříme na vektorové grafické formáty a smíšené bitmapově-vektorové formáty, které jsou také velmi zajímavé, rozmanité a užitečné, například v podobě takzvaných metaformátů často používaných pro přenos grafických dat mezi aplikacemi s různým zaměřením (CAD->grafický editor->tabulkový procesor atd.).

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.