IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

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.

výsledok - SDĽ

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 podmienkami

Stiahnuté 723x (9.6 MB)

 

Predchádzajúci článok
SDĽ - Práca s 8bitovou grafikou
Všetky články v sekcii
SDĽ
Preskočiť článok
(neodporúčame)
SDL - ERROR a logovanie
Článok pre vás napísal Patrik Valkovič
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity