SDL: Hry nejen pro Linux (7)

5. 4. 2005
Doba čtení: 7 minut

Sdílet

Tentokrát se ponoříme trochu více do hloubky, popíšeme si SDL grafické struktury a tyto znalosti následně využijeme k přímému přístupu k pixelům obrázku. V závěru budeme také měnit kurzor myši.

Struktura SDL_Surface

Každý už jistě ví, že základem veškeré grafiky, kterou poskytuje knihovna SDL, je struktura SDL_Surface. Poprvé jsme se s ní setkali už u funkce SDL_SetVideoMode(), kde představovala framebuffer okna, a následně u všech kreslících funkcí. Obecně může být jakýmkoli úložištěm pixelů. Pro vlastní programování není znalost jejího vnitřního formátu většinou nijak zásadní, nicméně alespoň všeobecná představa se může hodit.

typedef struct SDL_Surface
{
    Uint32 flags;
    SDL_PixelFormat *format;
    int w, h;
    Uint16 pitch;
    void *pixels;

    SDL_Rect clip_rect;
    int refcount;

    // + další privátní složky (viz SDL_video.h)
} SDL_Surface;

Položka flag může u obecného surface nabývat pouze kombinací hodnot SDL_SWSURFACE, SDL_HWSURFACE a SDL_ASYNCBLIT. Flagy fullscreenu, změny velikosti a podobné jsou dostupné pouze u surface okna.

Hardwarový surface a tedy i hardwarovou akceleraci bývá vhodné používat při blittingu, který se tím výrazně urychlí, naopak při častých modifikacích pixelů (oheň v ukázkovém programu k tomuto článku je typickým příkladem) není jeho použití zrovna nejvhodnější, protože by pixely neustále kolovaly ke grafické kartě a zpět. V podobných případech je vhodnější uložit surface do systémové paměti.

Druhá položka struktury představuje formát pixelů (více níže), w a h specifikují rozměry obrázku v pixelech a pitch je délka jednoho řádku v bytech, ten může být zarovnán na určitou velikost. Pointer pixels ukazuje na grafická data obrázku (levý horní roh), jedná se buď o pixely, nebo v případě barevné hloubky osm bitů a menší o indexy do palety.

Clip_rect je ořezávací obdélník, díky kterému je možné obrázek pro některé funkce „imaginárně zmenšit“, setkali jsme se s ním už minule u funkce SDL_SetClipRect(). Konečně refcount je počet referencí, který se používá při uvolňování obrázku z paměti. Kromě těchto parametrů obsahuje SDL_Surface ještě další privátní složky.

Pozn.: Žádný z těchto parametrů, vyjma ruční modifikace pixelů, by neměl být zadáván explicitně. Pro tyto činnosti slouží standardní funkce, které byly probrány v minulých článcích.

Struktura SDL_PixelFormat

Tato struktura popisuje formát pixelů uložených v surface, její podrobná znalost je nutná jen při požadavku přímého přístupu k pixelům. V ostatních případech by mělo stačit pouze vědět, že existuje a že ji lze najít v surface->format.

typedef struct
{
    SDL_Palette *palette;
    Uint8  BitsPerPixel;
    Uint8  BytesPerPixel;
    Uint32 Rmask, Gmask, Bmask, Amask;
    Uint8  Rloss, Gloss, Bloss, Aloss;
    Uint8  Rshift, Gshift, Bshift, Ashift;
    Uint32 colorkey;
    Uint8  alpha;
} SDL_PixelFormat;

Palette buď ukazuje na paletu, nebo je (u barevné hloubky větší než 8 bitů) nastaveno na NULL. Barevná hloubka je specifikována hned ve dvou položkách. V první z nich je uložena v bitech a u druhé jsou jednotkami byty.

Bity RGBA masky jsou na pozici dané složky v jedničce, RGBA loss určuje ztrátu přesnosti barevné složky – 2[RGBA]loss. RGBA shift označuje počet bitů zprava v hodnotě pixelu k dané komponentě. Colorkey určuje transparentní barvu a alpha „globální hodnotu alfa kanálu“surface.

Struktury SDL_Palette a SDL_Color

Struktura SDL_Palette obsahuje ukazatele na barvy palety a SDL_Color je tvořena jednotlivými RGB složkami barvy.

typedef struct
{
    int ncolors;
    SDL_Color *colors;
} SDL_Palette;

typedef struct
{
    Uint8 r;
    Uint8 g;
    Uint8 b;
    Uint8 unused;
} SDL_Color;

Adresace pixelů a získání barevných komponent

Pixely jsou v surface uloženy do jednorozměrného pole, a tudíž může vyvstat otázka, jak je adresovat při použití dvourozměrných x, y koordinátů. Požadovaná adresa pixelu se získá vynásobením šířky řádku y-ovou pozicí a přičtením x-ové pozice k výsledku. Je nutné vzít v úvahu ještě barevnou hloubku, ale jinak se nejedná o nic složitého.

Příklad bude možná názornější. Na obrázku níže je vidět mřížka, ve které každý čtvereček symbolizuje jeden pixel. Šedý okraj vpravo představuje nevyužitou část paměti (parametr pitch). Adresa zvýrazněných pixelů (indexů do palety) se bude rovnat (měřeno v bytech):

Uint8 *adr;
int bypp = s->format->BytesPerPixel;

// ADRESA = POČÁTEK + ŘÁDEK*ŠÍŘKA ŘÁDKU
//          + SLOUPEC*ŠÍŘKA PIXELU;

// Zelený
adr = (Uint8 *)s->pixels + 2*s->pitch + 3*bypp;
// Červený
adr = (Uint8 *)s->pixels + 3*s->pitch + 1*bypp;
Adresace pixelů

Je-li pixel načtený, je většinou potřeba získat hodnoty jednotlivých RGB(A) složek. Žádné pevně dané pořadí (RGB, BGR apod.) není v SDL obecným pravidlem. Jak tedy na to? Pixel se binárně ANDuje s maskou barvy, čímž se vynulují hodnoty všech ostatních komponent, poté se aplikují dva binární posuny, nejprve o shift doprava a následně o loss doleva.

Po průchodu následujícím kódem bude proměnná red obsahovat červenou složku barvy v pixelu. Získání modré, zelené nebo alfy je analogické.

Uint8 red;
Uint32 tmp, pixel;
// fmt je ukazatel na formát pixelů

tmp = pixel & fmt->Rmask;        // Maskování
tmp = tmp >> fmt->Rshift;        // Posun na pravý okraj
tmp = tmp << fmt->Rloss;         // Expanze na 8 bitů
red = (Uint8)tmp;                // "Ořeže" nuly vlevo

Druhou možností by bylo použít standardní funkci SDL_GetRGB(), která byla popsána v minulém dílu.

Mimochodem, vždy je možné si zavést konvenci, že všechny surface v programu budou například ve formátu RGB(A), a tím tyto komplikace obejít. Na druhou stranu, program bude méně univerzální a při skládání dvou kódů vyvíjených nezávisle na sobě mohou vzniknout zbytečné komplikace.

Zamknutí surface

V případě, že chce programátor přistupovat přes ukazatel surface->pixels přímo k jednotlivým pixelům, měl by nejdříve surface uzamknout. Jedinou výjimkou jsou takové surface, u kterých makro SDL_MUSTLOCK() vrátí nulu, pak je přístup k pixelům možný kdykoli.

Za „práci s pixely“ se považuje ruční přístup k datům přes ukazatel ve struktuře. Naopak u kreslících funkcí, které jsou poskytovány SDL (SDL_BlitSurface() apod.), by surface nikdy být zamknut neměl!

int SDL_LockSurface(SDL_Surface *surface);
void SDL_UnlockSurface(SDL_Surface *surface);

Po ukončení úprav pixelů by mělo vždy následovat odemknutí, a jelikož jsou zámky vícenásobné, mělo by ke každému zamknutí existovat odpovídající odemknutí. To znamená, že pokud je surface zamknut dvakrát, měl by být také dvakrát odemknut.

Mezi těmito funkcemi by se také nemělo vyskytnout žádné systémové nebo knihovní volání. V obecném případě by zamykání a odemykání mohlo vypadat např. takto:

if(SDL_MUSTLOCK(screen))
{
    if(SDL_LockSurface(screen) < 0)
    {
        return;
    }
}

// Práce s pixely

if(SDL_MUSTLOCK(screen))
{
    SDL_UnlockSurface(screen);
}

Kurzor myši

Na závěr výkladu o SDL grafice bude probráno téma změny kurzoru myši. K jeho vytvoření slouží funkce SDL_CreateCursor(), která vrací ukazatel na nově vytvořenou strukturu SDL_Cursor.

SDL_Cursor *SDL_CreateCursor(Uint8 *data, Uint8 *mask,
        int w, int h, int hot_x, int hot_y);

První dva parametry jsou bitovými mapami a určují, jak bude výsledný kurzor vypadat (viz dále). Další dva označují šířku a výšku, obě hodnoty musí být násobkem čísla osm a poslední dva parametry specifikují aktivní bod kurzoru.

Kurzor vytvořený pomocí SDL může být pouze černobílý, takže by mělo stačit pouze jedno bitové pole. Nesmí se však zapomenout ještě na průhlednou a případně invertovanou barvu, což dává celkem čtyři možné kombinace, jejichž význam vysvětluje následující tabulka.

Data Maska Výsledný pixel kurzoru
0 1 Bílý
1 1 Černý
0 0 Průhledný
1 0 Je-li dostupný, tak invertovaný, jinak černý

Po skončení práce s kurzorem by měla být vždy zavolána funkce SDL_FreeCursor(), která se postará o jeho uvolnění z paměti.

void SDL_FreeCursor(SDL_Cursor *cursor);

Kurzor lze nastavit jako aktivní voláním funkce SDL_SetCursor(). Naopak aktuálně aktivní kurzor lze získat funkcí SDL_GetCursor().

void SDL_SetCursor(SDL_Cursor *cursor);
SDL_Cursor *SDL_GetCursor(void);

Poslední operací, která se dá provést s kurzorem myši, je jeho zobrazení popř. skrytí. Po startu aplikace je implicitně zobrazen.

int SDL_ShowCursor(int toggle);

Symbolická konstanta SDL_DISABLE kurzor skryje, naopak SDL_ENABLE ho zobrazí. Pomocí SDL_QUERY bude vrácen aktuální stav.

V následujícím příkladu se vytvoří kurzor ve tvaru bílého čtverce o velikosti 8×8 pixelů, za aktivní bod je definován levý horní roh.

// Globální proměnná
SDL_Cursor *g_cursor;

// Inicializace
Uint8 data[] = { 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00 };
Uint8 mask[] = { 0xFF, 0xFF, 0xFF, 0xFF,
                0xFF, 0xFF, 0xFF, 0xFF };

g_cursor = SDL_CreateCursor(data, mask, 8, 8, 0, 0);
SDL_SetCursor(g_cursor);

// Deinicializace (většinou konec aplikace)
SDL_FreeCursor(g_cursor);

Pozn.: SDL sice umožňuje vytvářet pouze černobílé kurzory, ale to neznamená, že nelze používat i barevné. Vždy je možné pomocí SDL_ShowCursor(SDL_DI­SABLE) standardní kurzor skrýt a místo něho při každém vykreslení zobrazit libovolný obrázek nebo dokonce spritovou animaci (animovaný kurzor).

Ukázkové programy

Přímý přístup k pixelům surface

Při ruční modifikaci pixelů bývá největším problémem adresovat místo v paměti, na které se má zapisovat. O tuto činnost se stará funkce DrawPixel(), která byla převzata ze SDL intro a trochu upravena. Demonstrační program touto technikou vykreslí tři čtverce a linku palety šedi. (Zdrojový kód se zvýrazněním syntaxe.)

bitcoin_skoleni

Přímý přístup k pixelům surface

Oheň

Druhý příklad simuluje hořící oheň. Na nejnižším řádku se generují náhodné pixely z palety barev ohně, které se s rostoucí výškou postupně rozmazávají. V programu je dále definován kurzor myši ve tvaru „zaměřovače“ (černé kolečko s bílým středem; na screenshotu není vidět), kterým je možné do ohně přidávat bílé pixely. (Zdrojový kód se zvýrazněním syntaxe.)

Oheň

Download

Pokračování

Tímto článkem jsme SDL grafiku ukončili, příště se budeme věnovat OpenGL.

Autor článku

Backend programátor ve společnosti Avast, kde vyvíjí a spravuje BigData systém pro příjem a analýzu událostí odesílaných z klientských aplikací založený na Apache Kafka a Apache Hadoop.