7. diel - SDĽ - Práca s 16 a 32-bitovou grafikou
V minulom dieli sme si ukázali, ako SDL pracuje s 8 bitovou grafikou a potom sa naučili, ako môžeme ručne pixely meniť. Dnes si ukážeme, ako docielime rovnaký výsledok u 16 a 32 bitové grafiky.
Farby pixelov sú uložené priamo
16 a 32 bitová grafika už nepracuje s paletou farieb. S matematikou zo základnej školy môžeme spočítať počet farieb, ktoré môže obraz použiť. Ak nebudeme počítať alfa kanál, máme pre každú farbu rozsah 0-255. Vo výsledku môžeme teda použiť 255 255 255 farieb (16,6 milióna). Pre FullHD obrázok, ktorý má rozlíšenie 1920x1080, máme celkom 1920 * 1080 pixelov (2 milióny). Veľkosť palety by teda niekoľkonásobne prevyšoval veľkosť samotného obrázka (a to sme nepočítali s alfa kanálom). Navyše by pixel zaujímal 4 bajty v oboch prípadoch, pretože by musel indexovať všetky farby palety (zatiaľ čo pri 8 bitové grafiky sú 4-bajtovým iba farby). Z tohto dôvodu sa farba ukladá priamo v pixelu (v 8 bitová grafike sa ukladal index farby v palete). Aby sme vedeli, kde je ktorá farba v oných 4 bajtoch uložená, použijeme masky.
Maska
Pretože nemáme žiadny univerzálny formát, podľa ktorého by sa určilo,
kde je farba uložená, musíme použiť masky. Tie nám povedia, v ktorých
bitoch je konkrétny farebná zložka. Nie je nutne pravidlom, že jedna
farebná zložka farby musí byť uložená v 8 bitoch (pre 32 bitovú grafiku).
Akú masku zvolíme, je len na nás. Musíme však dodržať formáty, ktoré
má SDL pripravené, inak funkcia pre vytváranie SDL_Surface
vráti NULL
a pri ďalšom použití SDL_Surface
program spadne. Možné formáty sú v dokumentácii pod
nadpisom "Pixel Format Values". Nás budú zaujímať predovšetkým rôzne
variácie RGB (A). Čísla za konštantou hovoria počet bitov, ktoré sú
nastavené pre danú farbu. Napríklad pre hodnotu
SDL_PIXELFORMAT_ARGB1555
zaberá alfa kanál 1 bit a všetky
ostatné farby majú po 5 bitoch. Formáty sú uložené v súbore
SDL_pixels.h
, môžeme si teda vytvoriť vlastné. Ide však o
pokročilejšie postupy mimo rozsah tohto seriálu.
Masky pre jednotlivé farby sa udávajú samostatne, preto musíme hodnoty spočítať. Pre vyššie uvedený príklad bude výpočet nasledovný
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
Tieto hodnoty odovzdáme pri vytváraní SDL_Surface
.
SDL_Surface* newSurface = SDL_CreateSurface(NULL, 100, 100, 32, 0x7C00, 0x03E0, 0x001F, 0x8000);
Výpočet farby pixelu
Ak budeme chcieť nastaviť konkrétnu farbu, musíme vedieť, na ktorých pozíciách sú jednotlivé zložky umiestnené. Budeme uvažovať vyššie uvedený formát a farbu 0x1234. Pritom budeme chcieť získať hodnotu zelenej zložky. Najprv musíme farbu "Andová" s maskou. Tým vrátime hodnotu, ktorá je však posunutá od počiatku. Použijeme operáciu bitového posunu tak, aby sa stal posledný nastavený bit masky posledným bitom v celom čísle. Pre zelenú farbu teda posunieme o päť miest doprava.
Rovnakým postupom prídeme aj na ďalšie farby. Vo výsledku A = 0, R = 4, G = 17, B = 20. Tieto hodnoty sú však iba relatívna vzhľadom k maximálnej hodnote, ktorá je 32. Ako vieme, všetky monitory pracujú s 32 bitovou grafikou, je teda farbu nutné prepočítať. Tentoraz binárne posunieme hodnotu tak, aby prvý bit masky bol prvým bitom v bajte. Vlastne teda dorovnáme číslo na 8 bitov. Výsledná farba 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 pozornosť stojí alfa kanál, ktorý zaberá len jeden bit. Farba je viditeľná (nastavený na 1) alebo nie (nastavený na 0). Nemáme niekoľko stupňov transparentnosti, ako sme tomu zvyknutí napríklad vo Photoshope. Aby sme našu farbu videli, budeme musieť prvý bit nastaviť. Výsledná farba teda bude 0x9234 = 1001 0010 0011 0100. Ak chceme použiť niekoľko stupňov alfa kanála, budeme musieť zvoliť iný formát.
Určenie farebných zložiek pri neznámom formáte
Napríklad pri nahranie obrázka nepoznáme formát, v ktorom sa v
SDL_Surface
uloží. Nemôžeme teda pracovať s konštantami,
ktoré si sami určíme, ale s hodnotami relatívnymi ku konkrétnej
SDL_Surface
. K tomu slúži už spomínaný format. Pre každú farbu
máme uloženú jej masku (Rmask
), ďalej hodnotu, o ktorú musíme
posunúť číslo doprava (Gshift
), a tiež hodnotu, o ktorú
musíme posunúť číslo doprava, aby sa dorovnal do 8 bitov
(Rloss
). Časť kódu, ktorá z konkrétneho pixelu získa
červenú zložku bude vyzerať nasledovne.
Uint32* pixel = (Uint32*)MySurface->pixels; Uint32 PixelValue = *pixel; Uint32 RedColor = PixelValue & MySurface->format->Rmask; RedColor = RedColor >> MySurface->format->Rshift; RedColor = RedColor << MySurface->format->Rloss;
Teraz máme v premennej RedColor
uloženú červenú zložku
farby prvého pixelu.
Operácie s pixely
Pozíciu konkrétneho pixelu zistíme rovnako ako u 8-bitové grafiky.
Musíme si dať však pozor na to, že tentoraz môže jeden pixel zaujať viac
ako 1 bajt. Z toho nám plynú dva spôsoby, ako sa môžeme na konkrétne pixel
dopočítať. Prvým spôsobom je použiť Uint8 ukazovateľ, u ktorého budeme
musieť hodnotu stĺpca násobiť BytePerPixel
. Druhou metódou je
použiť väčší premennú (napríklad Uint16
alebo
Uint32
). Táto metóda ide použiť iba v prípade, keď poznáme
farebnú hĺbku SDL_Surface
. Nevýhodou je, že nemôžeme použiť
atribút pitch
, pretože ten je udávaný v bajtoch. Nasledujúci
časť kódu ukazuje obe metódy získania 8 pixelu zhora a 4 pixelu zľava 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ážkový príklad
Dnešné príklad bude trochu komplikovanejšie ako tie, ktoré boli v predchádzajúcich dieloch. V prvej fáze vytvoríme dva farebné prechody, jeden pre 16 bit a druhý pre 32 bitov. Na nich uvidíme rozdiel medzi 16 a 32 bitovú grafikou. K tomu vytvoríme ešte tretí prechod, ktorý bude animovaný. Využijeme ho v jednom z ďalších dielov, až si budeme hovoriť o optimalizácii FPS. Prechody vytvoríme rovnako, ako tomu bolo u 8 bitové grafiky, len okrem hodnoty v palete zadáme priamo hodnotu pixelu.
Popíšem ukážku pre 16 bitovú SDL_Surface
. Ostatné prechody
sa tvoria úplne rovnakým princípom. Najskôr vytvoríme
SDL_Surface
a nastavíme jej masky. Ku každému pixelu sa
dostaneme pri použití vloženého cykle. V ňom pre pixel vypočítame hodnoty
jednotlivých farieb. U 16 bitové grafiky ešte naviac hodnotu posunieme o 4
bity doprava, pretože sme vychádzali z hodnôt 0 až 255, ktoré sa do 4 bitov
nezmestia. Potom sa jednoduchou aritmetikou, ktorú som už vysvetlil,
dopočítame k pixelu, ktorý chceme nastaviť, a farby do neho uložíme. Farby
musíme bitovo posunúť, aby patrili každá svoje maske. Nakoniec iba
vytvoríme SDL_Texture
a SDL_Surface
zmažeme.
Výsledná časť kódu je tu.
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);
Pre vytvorenie animovaného prechodu postupujeme zo začiatku úplne
totožne. Vytvoríme si jednoduchý červený prechod. Rozdiel bude v tom, že v
hlavnej slučke vždy nahradíme pixel jeho susedom. Vo výsledku sa teda bude
zdať, že sa pixel posúva. Je niekoľko možností, ako môžeme hodnotu
pixelu spočítať. Ja som zvolil ako najjednoduchší spôsob uložiť si
hodnoty od druhého pixelu do queue
a prvé pixel
uložiť až nakoniec. Potom pri novom prekresľovanie ísť od prvého. Vo
výsledku bude starý prvý pixel novým posledným. Časť kódu, ktorá sa
stará o prekreslenie prechode, je tu.
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ýsledok si môžete pozrieť na tomto obrázku.
To je pre dnešný diel všetko. Zdrojové kódy sú ako obvykle pribalené pod článkom. V budúcom dieli sa pozrieme na zachytávanie + errorov, ktoré môže SDL vyvolať, a logovanie správ.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkamiStiahnuté 723x (9.6 MB)