Lekce 7 - SDL - Práce s 16 a 32bitovou grafikou
V minulé lekci, SDL - Práce s 8bitovou grafikou, jsme si ukázali, jak SDL pracuje s 8 bitovou grafikou a poté se naučili, jak můžeme ručně pixely měnit.
Dnes si ukážeme, jak docílíme stejného výsledku u 16 a 32 bitové grafiky.
Barvy pixelů jsou uloženy přímo
16 a 32 bitová grafika již nepracuje s paletou barev. S matematikou ze základní školy můžeme spočítat počet barev, které může obraz použít. Nebudeme-li počítat alfa kanál, máme pro každou barvu rozsah 0-255. Ve výsledku můžeme tedy použít 255255255 barev (16,6 miliónu). Pro FullHD obrázek, který má rozlišení 1920x1080, máme celkem 1920*1080 pixelů (2 milióny). Velikost palety by tedy několikanásobně převyšovala velikost samotného obrázku (a to jsme nepočítali s alfa kanálem). Navíc by pixel zaujímal 4 bajty v obou případech, protože by musel indexovat všechny barvy palety (zatímco u 8 bitové grafiky jsou 4bajtové pouze barvy). Z tohoto důvodu se barva ukládá přímo v pixelu (v 8 bitová grafice se ukládal index barvy v paletě). Abychom věděli, kde je která barva v oněch 4 bajtech uložena, použijeme masky.
Maska
Protože nemáme žádný univerzální formát, podle kterého by se
určilo, kde je barva uložena, musíme použít masky. Ty nám řeknou, v
kterých bitech je konkrétní barevná složka. Není nutně pravidlem, že
jedna barevná složka barvy musí být uložena v 8 bitech (pro 32 bitovou
grafiku). Jakou masku zvolíme, je pouze na nás. Musíme ovšem dodržet
formáty, které má SDL připravené, jinak funkce pro vytváření
SDL_Surface
vrátí NULL
a při dalším použití
SDL_Surface
program spadne. Možné formáty jsou v dokumentaci pod nadpisem
„Pixel Format Values“. Nás budou zajímat především různé variace
RGB(A). Čísla za konstantou říkají počet bitů, které jsou nastavené pro
danou barvu. Například pro hodnotu SDL_PIXELFORMAT_ARGB1555
zaujímá alfa kanál 1 bit a všechny ostatní barvy mají po 5 bitech.
Formáty jsou uloženy v souboru SDL_pixels.h
, můžeme si tedy
vytvořit vlastní. Jedná se ovšem o pokročilejší postupy mimo rozsah
tohoto seriálu.
Masky pro jednotlivé barvy se udávají samostatně, proto musíme hodnoty spočítat. Pro výše uvedený příklad bude výpočet následovný
1000 0000 0000 0000 = 0x8000 – alfa kanál 0111 1100 0000 0000 = 0x7C00 – červená barva 0000 0011 1110 0000 = 0x03E0 – zelená barva 0000 0000 0001 1111 = 0x001F – modrá barva
Tyto hodnoty předáme při vytváření SDL_Surface
.
SDL_Surface* newSurface = SDL_CreateSurface(NULL, 100, 100, 32, 0x7C00, 0x03E0, 0x001F, 0x8000);
Výpočet barvy pixelu
Pokud budeme chtít nastavit konkrétní barvu, musíme vědět, na kterých pozicích jsou jednotlivé složky umístěny. Budeme uvažovat výše uvedený formát a barvu 0x1234. Přitom budeme chtít získat hodnotu zelené složky. Nejprve musíme barvu "ANDovat" s maskou. Tím vrátíme hodnotu, která je ovšem posunutá od počátku. Použijeme operaci bitového posunu tak, aby se stal poslední nastavený bit masky posledním bitem v celém čísle. Pro zelenou barvu tedy posuneme o pět míst doprava.
Stejným postupem přijdeme i na další barvy. Ve výsledku A=0, R=4, G=17, B=20. Tyto hodnoty jsou ovšem pouze relativní vzhledem k maximální hodnotě, která je 32. Jak víme, všechny monitory pracují s 32 bitovou grafikou, je tedy barvu nutné přepočítat. Tentokrát binárně posuneme hodnotu tak, aby první bit masky byl prvním bitem v bajtu. Vlastně tedy dorovnáme číslo na 8 bitů. Výsledná barva bude A=0, R=32, G=136, B=160.
0x1234 = 0001 0010 0011 0100 0x03e0 = 0000 0011 1110 0000 AND = 0000 0010 0010 0000 >> 5 = 0000 0000 0001 0001 = 0x11 = 17 << 3 = 0000 0000 1000 1000 = 0x88 = 136
Za pozornost stojí alfa kanál, který zabírá pouze jeden bit. Barva je viditelná (nastaven na 1) nebo ne (nastaven na 0). Nemáme několik stupňů průhlednosti, jak jsme tomu zvyklí například ve Photoshopu. Abychom naši barvu viděli, budeme muset první bit nastavit. Výsledná barva tedy bude 0x9234 = 1001 0010 0011 0100. Pokud chceme použít několik stupňů alfa kanálu, budeme muset zvolit jiný formát.
Určení barevných složek při neznámém formátu
Například při nahrání obrázku neznáme formát, ve kterém se v
SDL_Surface
uloží. Nemůžeme tedy pracovat s konstantami, které
si sami určíme, ale s hodnotami relativními ke konkrétní
SDL_Surface
. K tomu slouží již zmiňovaný format. Pro každou barvu
máme uloženou její masku (Rmask
), dále hodnotu, o kterou
musíme posunout číslo doprava (Gshift
), a také hodnotu, o
kterou musíme posunout číslo doprava, aby se dorovnalo do 8 bitů
(Rloss
). Část kódu, která z konkrétního pixelu získá
červenou složku bude vypadat následovně.
Uint32* pixel = (Uint32*)MySurface->pixels; Uint32 PixelValue = *pixel; Uint32 RedColor = PixelValue & MySurface->format->Rmask; RedColor = RedColor >> MySurface->format->Rshift; RedColor = RedColor << MySurface->format->Rloss;
Nyní máme v proměnné RedColor
uloženou červenou složku
barvy prvního pixelu.
Operace s pixely
Pozici konkrétního pixelu zjistíme stejně jako u 8bitové grafiky.
Musíme si dát ovšem pozor na to, že tentokrát může jeden pixel zaujímat
více než 1 bajt. Z toho nám plynou dva způsoby, jak se můžeme na
konkrétní pixel dopočítat. Prvním způsobem je použít Uint8 ukazatel, u
kterého budeme muset hodnotu sloupce násobit BytePerPixel
. Druhou
metodou je použít větší proměnnou (například Uint16
nebo
Uint32
). Tato metoda jde použít pouze v případě, kdy známe
barevnou hloubku SDL_Surface
. Nevýhodou je, že nemůžeme
použít atribut pitch
, protože ten je udávaný v bajtech.
Následující část kódu ukazuje obě metody získání 8 pixelu shora a 4
pixelu zleva u 32 bitové SDL_Surface
.
//první metoda Uint8* pixels = (Uint8*)MySurface->pixels; Uint8* pixel = pixels + 8 * MySurface->pitch + 4 * MySurface->format->BytesPerPixel; Uint32 PixelValue = *(Uint32*)pixel; //druhá metoda Uint32* pixels = (Uint32*)MySurface->pixels; Uint32* pixel = pixels + 8 * MySurface->w + 4; Uint32 PixelValue = *pixel;
Ukázkový příklad
Dnešní příklad bude trochu komplikovanější než ty, které byli v předchozích dílech. V první fázi vytvoříme dva barevné přechody, jeden pro 16 bit a druhý pro 32 bitů. Na nich uvidíme rozdíl mezi 16 a 32 bitovou grafikou. K tomu vytvoříme ještě třetí přechod, který bude animovaný. Využijeme jej v jednom z dalších dílů, až si budeme říkat o optimalizaci FPS. Přechody vytvoříme stejně, jako tomu bylo u 8 bitové grafiky, jen kromě hodnoty v paletě zadáme přímo hodnotu pixelu.
Popíšu ukázku pro 16 bitovou SDL_Surface
. Ostatní přechody
se tvoří naprosto stejným principem. Nejdříve vytvoříme
SDL_Surface
a nastavíme jí masky. Ke každému pixelu se
dostaneme ve vnořeném cyklu. V něm pro pixel vypočítáme hodnoty
jednotlivých barev. U 16 bitové grafiky ještě navíc hodnotu posuneme o 4
bity doprava, protože jsme vycházeli z hodnot 0 až 255, které se do 4 bitů
nevlezou. Poté se jednoduchou aritmetikou, kterou jsem již vysvětlil,
dopočítáme k pixelu, který chceme nastavit, a barvy do něj uložíme. Barvy
musíme bitově posunout, aby náležely každá své masce. Nakonec pouze
vytvoříme SDL_Texture
a SDL_Surface
smažeme.
Výsledná část kódu je zde.
SDL_Surface* SurfaceWith16Transition = SDL_CreateRGBSurface(NULL, 256, 256, 16, 0xF00, 0x0F0, 0x00F, 0xF000); Uint8* pixels = (Uint8*)SurfaceWith16Transition->pixels; SDL_PixelFormat* format = SurfaceWith16Transition->format; for (int a = 0; a < 256; a++) for (int b = 0; b < 256; b++) { int GreenColor = b >> format->Gloss; int RedColor = a >> format->Rloss; Uint8* pixel = pixels + a*SurfaceWith16Transition->pitch + *format->BytesPerPixel; *(Uint16*)pixel = RedColor << format->Rshift | GreenColor << format->Gshift | 0xF << format->Ashift; } SDL_Texture* TextureWith16Transition = SDL_CreateTextureFromSurface(renderer, SurfaceWith16Transition); SDL_FreeSurface(SurfaceWith16Transition);
Pro vytvoření animovaného přechodu postupujeme ze začátku naprosto
totožně. Vytvoříme si jednoduchý červený přechod. Rozdíl bude v tom,
že v hlavní smyčce vždy nahradíme pixel jeho sousedem. Ve výsledku se tedy
bude zdát, že se pixel posouvá. Je několik možností, jak můžeme hodnotu
pixelu spočítat. Já jsem zvolil jako nejjednodušší způsob uložit si
hodnoty od druhého pixelu do queue
a první pixel
uložit až nakonec. Poté při novém překreslování jít od prvního. Ve
výsledku bude starý první pixel novým posledním. Část kódu, která se
stará o překreslení přechodu, je zde.
std::queue<int> QueueWithColors; for (int a = 1; a < 256; a++) { Uint8* pixel = pixels + a*format->BytesPerPixel; QueueWithColors.push(*(Uint32*)pixel); } QueueWithColors.push(*(Uint32*)pixels); for (int a = 0; a < 256; a++) { int ColorToUse = QueueWithColors.front(); for (int b = 0; b < 100; b++) { Uint8* pixel = pixels + b*AnimatedSurface->pitch + a*format->BytesPerPixel; *(Uint32*)pixel = ColorToUse; } QueueWithColors.pop(); }
Výsledek si můžete prohlédnout na tomto obrázku.

To je pro dnešní díl vše. Zdrojové kódy jsou jako obvykle přibaleny pod článkem.
V příští lekci, SDL - Errory a logování, se podíváme na zachytávání errorů, které může SDL vyvolat, a logování zpráv.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkamiStaženo 770x (9.6 MB)