7. díl - SDL - Práce s 16 a 32bitovou grafikou

C++ SDL SDL - Práce s 16 a 32bitovou grafikou

V minulém díle 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 zprava 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.

Výsledek

To je pro dnešní díl vše. Zdrojové kódy jsou jako obvykle přibaleny pod článkem. V příštím díle se podíváme na zachytávání errorů, které může SDL vyvolat, a logování zpráv.


 

Stáhnout

Staženo 132x (9.6 MB)

 

  Aktivity (1)

Článek pro vás napsal patrik.valkovic
Avatar
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu.

Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!


 


Miniatura
Předchozí článek
SDL - Práce s 8bitovou grafikou
Miniatura
Všechny články v sekci
SDL
Miniatura
Následující článek
SDL - Errory a logování

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!