Anatomie grafického formátu GIF

24. 8. 2006
Doba čtení: 14 minut

Sdílet

V dnešním pokračování série článků o grafickém formátu GIF si do větších detailů (až na úroveň jednotlivých bitů) popíšeme vnitřní strukturu souborů typu GIF, a to jak verze GIF87a, tak i verze GIF89a. Také si ukážeme, jak jsou v GIFu zakódovány jednoduché rámce, ze kterých se skládají výsledné obrázky.

Obsah

1. Interní struktura formátu GIF
2. Nejmenší obrázek typu GIF – jeden neprůhledný pixel
3. Změna LZW kódu a barvové palety
4. Změna rozlišení logické obrazovky
5. Posun rámce v logické obrazovce
6. Obrázek typu GIF s transparentním pixelem
7. Testovací obrázky ke stažení
8. Obsah závěrečné části – animace, prokládání a LZW komprimace

1. Interní struktura formátu GIF

V první kapitole si řekneme, jakým způsobem jsou data v grafickém formátu GIF interně ukládána. Celý soubor (resp. v případě přenosu po datových sítích „pouhý“ tok bytů) je rozdělen do bloků, z nichž některé jsou povinné, některé se musí vyskytovat na určitém místě souboru, další se mohou opakovat apod. Povolené bloky, které se mohou v GIFu vyskytovat, jsou popsány v následující tabulce spolu s informací, zda se jedná o bloky povinné (tj. bloky, které se musí vyskytovat v každém validním souboru) a zda je možné bloky v rámci jednoho souboru opakovat (pro účely animací, slideshow, obejití maximálního počtu 256 barev apod.). Dále je u každého bloku uvedeno, zda se jeho definice vyskytovala už ve specifikaci GIF87a či až v novější specifikaci GIF89a.

Název bloku Povinná položka? Lze opakovat? Verze
signatura GIFu ano ne 87a
verze souboru ano ne 87a
popis logické obrazovky ano ne 87a
globální barvová paleta ne ne 87a
rozšiřující grafický blok ne ano 89a
popis rámce ano ano 87a
lokální barvová paleta ne ano 87a
data rámce (pixely) ano (pro rámec) ano 87a
rozšiřující informace (textové, binární) ne ano 89a
ukončovací znak GIF souboru ano ne 87a

Při našem zkoumání interní struktury GIFu nejprve začneme s nejjednodušším prakticky použitelným souborem, který je možné vytvořit. Podrobný popis bude uveden v následující kapitole, zde si však řekněme, jaké bloky je nutné i v tom nejjednodušším obrázku použít a v jakém pořadí:

Pořadí Název bloku
1 signatura GIFu
2 verze souboru
3 popis logické obrazovky
4 globální barvová paleta
5 popis rámce
6 data rámce (pixely)
7 ukončovací znak GIF souboru

Výše uvedeným způsobem jsou uloženy prakticky všechny statické GIFy, tj. GIFy neobsahující animaci ani optimalizaci s využitím více rámců (o více než 256 barvách ani nemluvě). V případě, že se ukládají animované GIFy, vypadá situace již poněkud složitěji, protože je nutné každý snímek animace reprezentovat pomocí minimálně jednoho rámce a mezi tyto rámce vložit pauzu pomocí rozšiřujícího grafického bloku. V případě, že se má animace opakovat, je nutné přidat i informaci o počtu opakování (popř. o nekonečné animaci), což se děje pomocí bloku aplikačního rozšíření (Application Extension Block, ten bude popsán v závěrečné části seriálu). Animace tvořená třemi snímky (rámci) bude mít následující strukturu:

Pořadí Název bloku
1 signatura GIFu
2 verze souboru
3 popis logické obrazovky
4 globální barvová paleta
5 aplikační rozšiřující blok se smyčkou
6 první rozšiřující grafický blok (nastavení zpoždění)
7 první popis rámce
8 první lokální barvová paleta (pouze pokud se liší od globální)
9 první data rámce (pixely)
10 druhý rozšiřující grafický blok (nastavení zpoždění)
11 druhý popis rámce
12 druhý lokální barvová paleta (pouze pokud se liší od předchozí)
13 druhý data rámce (pixely)
14 třetí rozšiřující grafický blok (nastavení zpoždění)
15 třetí popis rámce
16 třetí lokální barvová paleta (pouze pokud se liší od předchozí)
17 třetí data rámce (pixely)
18 ukončovací znak GIF souboru

2. Nejmenší obrázek typu GIF – jeden neprůhledný pixel

Absolutně nejmenší obrázek typu GIF, který je možné zobrazit ve všech prohlížečích, obsahuje pouze globální barvovou paletu se dvěma položkami, jeden rámec o rozlišení 1×1 pixel a v něm uložený pouhopouhý jeden pixel. Délka souboru je rovna 35 bytům, což se sice na první pohled může zdát mnoho, ale „konkurenční“ minimální PNG má celých 65 bytů (délka těchto souborů je většinou menší, než jejich odkaz zapsaný v HTML stránce :-). Teoreticky je sice možné GIF ještě zmenšit, například vynecháním globální barvové palety či dokonce celého rámce, ale takto vytvořený GIF mnoho prohlížečů odmítne zpracovat. Dále se tedy budeme zabývat naším 35bytovým podiobrázkem, který má při zobrazení v hexadecimálním editoru (KHexEdit, bvi, vimxxd, stále nepřekonaný Hiew, biew, Dos Navigator atd.) například tento tvar:

0000000: 47 49 46 38 39 61 01 00 01 00 80 00 00 ff 80 00
0000010: ff ff ff 2c 00 00 00 00 01 00 01 00 00 02 02 44
0000020: 01 00 3b 

gif31

Obrázek 1: Podiobrázek o velikosti 1×1 pixel -najdete ho? (Root obrázky centruje)

Jak jsme si již ukázali v první kapitole, obsahuje tento soubor sedm bloků: signaturu GIF, verzi souboru, popis logické obrazovky, globální barvovou paletu, popis rámce, data rámce a ukončovací znak GIF souboru. Všechny zmiňované bloky jsou uloženy za sebou, takže si předchozí zápis můžeme blíže ujasnit a rozepsat v následující tabulce:

Offset (dec) Byte či sekvence (hex) Význam bytové sekvence
00 47 49 46 ASCII řetězec ‚GIF‘ – „magické“ číslo souboru (signatura GIF)
03 38 39 61 ASCII řetězec ‚89a‘ – verze GIFu
06 01 00 šířka logické obrazovky: jeden pixel
08 01 00 výška logické obrazovky: jeden pixel
10 80 bitové pole: povolení globální barvové palety
11 00 index barvy pozadí
12 00 poměr výšky a šířky pixelu: 1÷1
13 ff 80 00 první barva v paletě: oranžový odstín
16 ff ff ff druhá barva v paletě: čistě bílá
19 2c značka začátku rámce
20 00 00 x-ová pozice levého okraje rámce: nultý sloupec
22 00 00 y-ová pozice horního okraje rámce: nultý řádek
24 01 00 šířka rámce: jeden pixel
26 01 00 výška rámce: jeden pixel
28 00 bitové pole: bez barvové palety, bez prokládání a animace
29 02 počáteční velikost LZW kódu v bitech-1
30 02 velikost bloku zakódovaného pomocí LZW
31 44 01 zakódovaná data rámce (jeden pixel s nulovým indexem)
33 00 ukončovací znak bloku zakódovaného pomocí LWZ
34 3b ukončovací znak GIF souboru: znak ‚;‘

Rozpoznání jednotlivých bloků a informací v nich uložených si můžeme popsat ještě podrobněji:

  1. Každý soubor typu GIF musí začínat jeho signaturou, což je sekvence bytů s hexadecimálními hodnotami 0×47, 0×49 a 0×46. Po převodu do ASCII můžeme tuto sekvenci číst jako řetězec ‚GIF‘.
  2. Ihned po signatuře GIFu musí následovat další trojice bytů, která udává verzi GIFu. V současné době (a pravděpodobně i v budoucnosti) je možné použít pouze dvě verze, první je GIF87a (sekvence bytů 0×38 0×37 0×61) a druhá GIF89a (sekvence bytů 0×38 0×39 0×61).
  3. Po signatuře je uložen popis logické obrazovky. Nejprve je na dvou bytech uvedena šířka obrazovky v pixelech, následuje výška obrazovky, samozřejmě taktéž v pixelech. Stejně jako u dalších vícebytových polí, i u hodnot šířky a výšky jsou byty uspořádány v pořadí nižší-vyšší (little endian). V našem obrázku, který má rozlišení nastaveno na 1×1 pixel, tedy bude šířka i výška nastavena na hexadecimální hodnotu 0×01 0×00.
  4. Posléze následuje bitové pole, ve kterém je uvedeno, zda je použita globální barvová paleta (sedmý bit), počet bitů na pixel-1 (bity 3–6), zda je barvová paleta setříděna (bit 2) a konečně délka barvové palety (bity 0 a 1). Skutečná délka palety se vypočte podle vzorce: 2hodnota+1. V našem obrázku je použita globální barvová paleta a její délka je rovna 2 (zapisujeme jako nulu, protože 20+1=2), tj. hodnota zapsaná do bitového pole je rovna 0×80.
  5. Další byte obsahuje index barvy, která je použita jako pozadí v případě, že by plocha rámců nevyplnila celý obrázek. My tuto hodnotu prozatím nevyužijeme, proto zde může být nula – 0×00.
  6. Následuje byte, ve kterém je uveden poměr mezi výškou a šířkou pixelu. Vztah pro výpočet je následující: Aspect Ratio=(Pixel Aspect Ratio+15)/64. Pokud je v tomto byte uložena nula (0×00), není poměr specifikovaný. U verze GIF87a není tento byte využit a podle specifikace zde musí být zapsána hodnota nula. Tímto bytem také končí blok s informacemi o logické obrazovce a následuje globální barvová paleta.
  7. Jelikož je délka globální barvové palety rovna nule, obsahuje paleta pouze dvě barvy, každou reprezentovanou trojicí bytů. První barva, tj. barva s indexem nula, má barvové složky RGB nastaveny na hodnoty 0×ff 0×80 0×00, tj. jedná se o oranžový odstín. Druhá barva má barvové složky nastaveny na maximální hodnoty (0×ff 0×ff 0×ff), tj. jedná se o čistě bílou barvu.
  8. Po globální barvové paletě ihned následuje hlavička rámce. Ta je v souboru rozpoznána značkou začátku rámce, tj. bytem s hodnotou 0×2c. Odpovídající ASCII znak je ‚,‘ (čárka), což je pro oddělovač vhodná mnemotechnická pomůcka.
  9. Dva byty za značkou udávají x-ovou pozici levého okraje rámce, další dva byty pak y-ovou pozici horního okraje rámce. Vzhledem k tomu, že rámec začíná v levém horním rohu obrázku, jsou zde uloženy nulové hodnoty (0×00 0×00 0×00 0×00).
  10. Hlavička rámce pokračuje dvěma byty se šířkou rámce a dalšími dvěma byty s jeho výškou. Obě hodnoty nastavíme na jedničku, protože rámce bude mít rozlišení 1×1 pixel (0×01 0×00 0×01 0×00).
  11. Následuje bitové pole, ve kterém je uvedeno, zda se v rámci bude používat lokální barvová paleta (sedmý bit), zda je rámec prokládán (šestý bit), zda jsou barvy v lokální barvové paletě setříděny (pátý bit) a konečně délka lokální barvové palety (bity 0–2). Vzhledem k tomu, že lokální barvovou paletu nepotřebujeme, je v tomto bitovém poli uložena hodnota 0×00. Pokud by byla lokální barvová paleta přítomna, začínala by ihned za tímto bytem.
  12. Po hlavičce rámce již začíná sekce s daty rámce (pixely). Jednotlivé pixely jsou kódovány pomocí LZW algoritmu. Dekodér potřebuje při své inicializaci znát počáteční velikost kódu ukládaného ho hashovací tabulky. Tato velikost odpovídá počtu bitů na pixel zvýšeného o jedničku. Pro náš (maximálně dvoubarevný) obrázek by tedy měla být velikost nastavená na dvojku, vzhledem k implementačním detailům LZW je však definitoricky nastavena na trojku, tj. kód má velikost tři bity. V souboru je tato velikost snížena o jedničku, tj. nalezneme zde hodnotu 0×02. Hashovací tabulka je inicializována do podoby ukázané níže.
  13. Následuje byte udávající velikost bloku zakódovaného pomocí LZW. Kód pro náš jediný pixel se kupodivu musí rozepsat na dva byty, proto je i zde uložena hodnota 0×02.
  14. Následují vlastní obrazová data komprimovaná algoritmem LZW. Tato data mají délku dva byty a hodnotu 0×44 0×01. Po rozpisu do binární podoby získáme bitový vzorek 01000100 00000001, který musíme správně dekódovat. Víme, že velikost kódu je nastavena na tři bity, takže bitový vzorek rozložíme do trojic: 100 000 101 ??? ??? ? (začíná se od bitu s nejnižší váhou, hranice bytů se prostě překračují). První trojice bitů tvoří Clear code, ten zde musí být vždy umístěn, aby se hashovací tabulka dekodéru správně inicializovala. Následuje trojice bitů 000, což je první index do barvové palety (oranžová barva). Poslední trojice bitů značí End of LZW code, čímž je dekodéru řečeno, že má tuto fázi zpracování rámce ukončit. Hodnoty dalších bitů ve druhém byte nás tedy nezajímají, jsou zde uloženy jako výplň (vhodné místo pro vodoznak :-).
  15. Náš jediný pixel je uložen, proto se celý blok ukončí takzvaným ukončovacím znakem, který má hodnotu 0×00.
  16. V obrázku je použitý pouze jeden rámec, je tedy na čase celý soubor uzavřít. K tomu slouží byte, jehož ASCII hodnota je rovna znaku ‚;‘ a hexadecimální kód je tedy 0×3b. Teoreticky by za tímto kódem mohla následovat další data, prohlížeč by je však měl ignorovat.
Bitový vzorek Význam
000 první index do barvové palety
001 druhý index do barvové palety
100 clear code (CC) – inicializace hashovací tabulky
101 end of LZW code (EC)

3. Změna LZW kódu a barvové palety

Pro ilustraci si ještě uveďme, jak by se náš pidiobrázek změnil v případě, že by jediný uložený pixel měl hodnotu 1 a ne 0. Také se změnila globální barvová paleta tak, aby barva číslo jedna nebyla bílá, ale čistě zelená:

Offset (dec) Byte či sekvence (hex) Význam bytové sekvence
00 47 49 46 ASCII řetězec ‚GIF‘ – „magické“ číslo souboru (signatura)
03 38 39 61 ASCII řetězec ‚89a‘ – verze GIFu
06 01 00 šířka logické obrazovky: jeden pixel
08 01 00 výška logické obrazovky: jeden pixel
10 80 bitové pole: povolení globální barvové palety
11 00 index barvy pozadí
12 00 poměr výšky a šířky pixelu: 1÷1
13 ff 80 00 první barva v paletě: oranžová barva
16 00 ff 00 druhá barva v paletě: čistě zelená
19 2c značka začátku rámce
20 00 00 x-ová pozice levého okraje rámce: nultý sloupec
22 00 00 y-ová pozice horního okraje rámce: nultý řádek
24 01 00 šířka rámce: jeden pixel
26 01 00 výška rámce: jeden pixel
28 00 bitové pole: bez barvové palety, bez prokládání a animace
29 02 počáteční velikost LZW kódu v bitech
30 02 velikost bloku zakódovaného pomocí LZW-1
31 4c 01 zakódovaná data rámce (jeden pixel)
33 00 ukončovací znak bloku zakódovaného pomocí LWZ
34 3b ukončovací znak GIF souboru: ASCII znak ‚;‘

LZW kód má v tomto obrázku hodnotu 0×4c 0×01, což je binárně 01001100 00000001 a po rozpisu na tříbitové složky dostáváme posloupnost 100 (Clear Code), 001 (barva číslo 1) a 101 (konec LZW kódu).

gif32

Obrázek 2: Podiobrázek o velikosti 1×1 pixel (pixel je čistě zelený)

4. Změna rozlišení logické obrazovky

Přišel čas ukázat si první trik, který je možné s grafickým formátem GIF provést. Tento trik spočívá v tom, že se zvětší rozlišení grafické obrazovky, ovšem původní velikost jediného rámce zůstane nastavena na 1×1 pixel. To povede k tomu, že prohlížeč (pokud ovšem dodržuje specifikaci) zobrazí obrázek o specifikované velikosti, který je celý vybarven barvou pozadí. Na tomto obrázku se v levém horním rohu „krčí“ rámec o velikosti 1×1 pixel, jehož pixel má nastavenou odlišnou barvu. Následuje kód obrázku, jehož rozlišení je rovno 400×300 pixelům, což se hexadecimálně do hlavičky zapíše jako 0×90 0×01 a 0×2c 0×01, protože 0×190=400 a 0×12c=300 (tyto údaje jsou zapsány od šestého bytu). Barvy v globální barvové paletě jsou nastaveny na ff ff 00 (oranžová=barva pozadí) a 00 00 00 (černá=barva pixelu v rámci).

0000000: 47 49 46 38 39 61 90 01 2c 01 80 00 00 ff ff 00
0000010: 00 00 00 2c 00 00 00 00 01 00 01 00 00 02 02 4c
0000020: 01 00 3b 

gif33

Obrázek 3: Obrázek o velikosti 400×300 pixelů s rámcem umístěným v levém horním rohu

Některé webové prohlížeče u takto vytvořeného obrázku ignorují nastavenou barvu pozadí a místo toho nastaví buď svoji vlastní barvu (například bílou či šedou), nebo pozadí vůbec nepřekreslí, to znamená, že se chovají, jako kdyby bylo pozadí vyplněno průhlednými pixely. Tohoto chování se dá využít k tvorbě různých efektů, ovšem s tím rizikem, že se každý webový prohlížeč může chovat jinak.

5. Posun rámce v logické obrazovce

Předchozí příklad s uměle zvětšeným obrázkem je ještě možné upravit tak, že se rámec posune z levého horního rohu na jiné místo, například do středu obrázku. Ten se nachází na souřadnicích [200, 150], což je hexadecimálně 0×c8 0×00 a 0×96 0×00 (přesněji řečeno, střed je roven [199.5, 149.5], to však můžeme při dané „sudé“ velikosti obrázku ignorovat). Prohlížeč odpovídající specifikaci by měl zobrazit oranžový obrázek o rozlišení 400×300 pixelů, v jehož středu se bude nacházet pixel černé barvy. Některé webové prohližeče však barvu pozadí ignorují a místo ní zobrazí původní plochu či podkladovou texturu HTML stránky.

0000000: 47 49 46 38 39 61 90 01 2c 01 80 00 00 ff ff 00
0000010: 00 00 00 2c c8 00 96 00 01 00 01 00 00 02 02 4c
0000020: 01 00 3b 

gif34

Obrázek 4: Obrázek o velikosti 400×300 pixelů s rámcem umístěným v jeho středu

6. Obrázek typu GIF s transparentním pixelem

Při designu webových stránek se mnohdy neobejdeme bez jednoho velmi užitečného pomocníka – obrázku typu GIF s jediným transparentním (neprůhledným) pixelem. Ten je možné využít buď pro rezervaci místa na HTML stránce (například při „roztahování“ buněk tabulek) nebo při vykreslování rastrových obrázků pomocí JavaScriptu. Obrázek s transparentním pixelem je možné, stejně jako každý jiný obrázek, pomocí atributů width a height zvětšit na libovolnou hodnotu. Obrázek s jediným transparentním pixelem má také tu výhodu, že ho zobrazí všechny prohlížeče, včetně Lynxu :-) Podívejme se nyní, jak bude příslušný GIF soubor vypadat.

V této chvíli si již nevystačíme s obrázkem o velikosti 35 bytů, ale musíme před první (a jediný) rámec vložit ještě takzvaný rozšiřující grafický blok (Graphic Control Extension – GCE), což vede ke zvětšení obrázku na stále rozumných 43 bytů. To znamená, že GCE má délku osmi bytů s následujícím formátem:

Offset (dec) Byte či sekvence (hex) Význam bytové sekvence
00 21 značka začátku rozšiřujícího bloku
01 f9 identifikace rozšiřujícího bloku jako GCE
02 04 délka GCE (vždy nastavena na hodnotu 4)
03 ?? bitové pole s příznaky
04 ?? čas zpoždění zobrazení následujícího rámce
05 ?? vyšší byte hodnoty zpoždění (specifikováno v setinách sekundy)
06 ?? index barvy, která bude považována za průhlednou (transparentní)
07 00 ukončení rozšiřujícího bloku

Je vhodné se zmínit o tom, že mnoho programů při ukládání GIFů automaticky vytvoří i GCE a to i v případech, kdy GCE není zapotřebí. Proto byly výše uvedené jednopixelové obrázky upraveny ručně v hexadecimálním editoru. Zbývá vysvětlit roli jednotlivých bitů v bitovém poli (o zpoždění snímků se budeme bavit příště v textu o animacích). V nejnižším bitu je uvedeno, zda je nějaká barva nastavena jako průhledná. Pokud je tento bit nastavený na jedničku, přečte se ze šestého bytu GCE index barvy, který označuje ty pixely, které nemají být vykresleny, a tím pádem pod nimi bude prosvítat původní barva. Druhým bitem je specifikováno, zda se po zobrazení rámce bude čekat na vstup od uživatele. Významem tohoto bitu se budeme zabývat v dalším pokračování seriálu. Další tři bity specifikují způsob zobrazení rámce; opět se jimi budeme podrobněji zabývat příště. Vzhledem k tomu, že nám dostačuje povolit průhlednou (transparentní) barvu, bude v bitovém poli zapsána hodnota 01 a index průhledné barvy necháme na nule. To vede k této podobě GCE:
21 f9 04 01 00 00 00 00

Rámec obsahuje pouze jeden pixel s barvou číslo 0, která je nastavena jako průhledná. Celý rámec je zakódován do posloupnosti:
2c 00 00 00 00 01 00 01 00 00 02 02 44 01 00

Což vede k následující podobě celého průhledného pidiobrázku:

0000000: 47 49 46 38 39 61 01 00 01 00 80 00 00 ff ff ff
0000010: 00 00 00 21 f9 04 01 00 00 00 00 2c 00 00 00 00
0000020: 01 00 01 00 00 02 02 44 01 00 3b 

gif35

Obrázek 5: Podiobrázek o velikosti 1×1 pixel s průhledným pixelem (pokud nic nevidíte, je to v pořádku)

7. Testovací obrázky ke stažení

Pokud jste se nemohli trefit na výše uvedené obrázky (a prohlížeč neumí zobrazit odkazy na ně ve vlastnostech stránky), můžete všech pět výše popsaných obrázků získat z následujících odkazů:

bitcoin_skoleni

  1. podiobrázek o velikosti 1×1 pixel (pixel má oranžovou barvu)
  2. podiobrázek o velikosti 1×1 pixel (pixel je čistě zelený)
  3. obrázek o velikosti 400×300 pixelů s rámcem umístěným v levém horním rohu
  4. obrázek o velikosti 400×300 pixelů s rámcem umístěným v jeho středu
  5. podiobrázek o velikosti 1×1 pixel s průhledným pixelem
  6. maxiobrázek o velikosti 65535×65535 pixelů na pouhých 35 bytech!

8. Obsah závěrečné části – animace, prokládání a LZW komprimace

V poslední části tohoto seriálu si ukážeme, jakým způsobem se v GIFu pracuje s animacemi, jaký význam má prokládání obrázků a také si podrobněji popíšeme použitou LZW komprimaci.

Autor článku

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