4. díl - SDL - Práce s obrázky

C++ SDL SDL - Práce s obrázky

V dnešním díle si povíme něco o obrázcích a jak s nimi pracovat. Protože se obrázek nahrává nejprve do SDL_Surface, budou obdobné techniky použity pro veškeré vykreslování, které probíhá nejprve na SDL_Surface, a až poté na obrazovku. Také si ukážeme funkce pro kopírování bloků paměti, které budeme využívat nejčastěji.

Načtení a vykreslení .bmp obrázku

Nejdříve si ve složce projektu vytvoříme novou složku a pojmenujeme ji Pictures. Tuto složku budeme také kopírovat do výstupní složky, proto přidáme příkaz pro její zkopírování do Pre-build eventu - xcopy "$(ProjectDir)Pictures" "$(TargetDir)Pictures"/e /i /f /y.

V aplikaci budeme potřebovat obrázek, který vykreslíme. Využijeme zdroj z minulého dílu. V zazipované složce jsou různé PNG soubory, jeden z nich si vybereme a vložíme jej do složky Pictures. Prvně ale budeme potřebovat obrázek ve formátu BMP, proto tento obrázek zkonvertujeme – například v obyčejném malování. Nyní máme ve složce Pictures dva stejné obrázky, jeden ve formátu PNG a druhý ve formátu BMP.

Teď si tento obrázek vykreslíme na obrazovku. Nejprve budeme potřebovat obrázek načíst, k tomu slouží funkce SDL_LoadBMP. Ta načte obrázek ze souboru do SDL_Surface. Řekli jsme si ale, že SDL_Renderer dokáže pracovat pouze s SDL_Texture. Musíme vytvořit SDL_Texture z SDL_Surface, k tomu slouží funkce SDL_CreateTex­tureFromSurfa­ce. Nyní máme uložený obrázek v paměti grafické karty a tedy máme k dispozici hardwarově-akcelerované vykreslování.

SDL_Surface* SurfaceWithPicture = SDL_LoadBMP("Pictures/healer_f.bmp");
SDL_Texture* TextureWithPicture = SDL_CreateTextureFromSurface(renderer,SurfaceWithPicture);

Vykresleni obrázku

K samotnému vykreslení na obrazovku budeme potřebovat pouze jedinou funkci – SDL_RenderCopy. Položky SDL_Renderer a SDL_Texture jsou povinné. V našem případě se bude vykreslovat TextureWithPicture do SDL_Renderer okna. Pokud jeden z posledních dvou parametrů vynecháme, znamená to, že se má použít celá plocha. Podrobněji, když vynecháme parametr zdroje, použije se celá textura, pokud vynecháme parametr cíle, vykreslí se obraz přes celou obrazovku. Budeme-li chtít vykreslit jen část obrazu, musíme funkci dodat třetí parametr (ukazatel na SDL_Rect). Naopak pokud chceme, aby se obrázek vykreslil na určité místo, musíme dodat poslední parametr (opět ukazatel na SDL_Rect). Nejlépe o efektu vypovídá následující obrázek.

Render copy demo

Všimněme si, že SDL automaticky zvětšuje obrázek tak, aby se celý vešel do připraveného místa. Ve výsledku tedy můžeme být velikost zdrojového obdélníku jiná než velikost obdélníku cílového. Nemusí být dokonce zachovány ani proporce (rozměry stran), SDL obrázek automaticky transformuje podle potřeby.

Také je potřeba zmínit situaci, kdy se obdélník dostane mimo rozsah plochy. Jestliže se to stane u cíle, nic zásadního se neděje. Jednoduše se jen obrázek ořeže a vizuální efekt vypadá, jako by byl obrázek mimo okno (schovaný za rámem). Ovšem jestliže tato situace nastane pro zdrojový obraz, obdélník se ořeže podle okraje plochy. Jestliže byl obdélník široký 50 bodů a 10 bodů přesahuje pravý okraj obrázku, bude výsledek stejný, jako kdybychom měli obdélník na stejných souřadnicích, ale o 10 bodů užší. Tento jev je ukázán na následujícím obrázku. Vidíme, že se obrázek roztáhnul do šířky, ale přitom stále zabírá celé okno.

Přesahující zdrojový obdelník

Načítání jiných formátů obrázků

SDL v základu podporuje obrázky pouze formátu BMP kvůli jejich jednoduché kompresi a formátu. Pokud budeme chtít použít i jiné obrázky, budeme potřebovat rozšíření k SDL, knihovnu SDL_image. Stáhneme si vývojářskou verzi (Development Libraries) pro Visual Studio zde. Stejně jako pro samotné SDL přidáme složku include do „*Include Directories*“ a složku lib/x64 do „*Library Directories*“. Přilinkujeme další knihovnu SDL2_image.lib v nastavení Linker – Input – Additional Dependencies. Ještě nezapomeneme zkopírovat všechny dynamické knihovny (.dll soubory) do složky Output v projektu. Tentokrát kromě SDL2_image.dll máme i další. SDL2 musí umět pracovat různými formáty souborů a pro každý formát je pro lepší přehlednost a oddělení kódu vytvořena samostatná dynamická knihovna.

Dokumentace SDL2_image je poměrně jednoduchá, stejně jako její použití. Tak jako u samotného SDL nejdříve zavoláme metodu IMG_Init a IMG_Quit na konci programu.

Nejčastěji budeme nahrávat nový soubor. K tomu nám stačí jednoduchá funkce IMG_Load. Funkce sama rozezná, o jaký formát souboru jde, a automaticky se postará o práce s otevřením a zavřením souboru. Nám se poté vrátí pouze ukazatel na SDL_Surface, ve kterém je nahraný obrázek. Všechny ostatní funkce přijímají jako parametr ukazatel na SDL_RWops, který v SDL slouží pro abstrakci práce se soubory. Dostaneme se k němu v jednom z dalších dílů.

Poslední skupina funkcí, které knihovna SDL_Image obsahuje, jsou funkce pro validaci formátu . Pro každý typ existuje samostatná funkce, která vždy končí koncovkou souboru (IMG_isPNG, IMG_isJPG, IMG_isGIF). Všechny tyto funkce přijímají jako parametr SDL_RWops, proto se jimi nebudeme zabývat.

Nyní už bychom měli být schopní načíst obrázek jakéhokoliv formátu a vykreslit jej na obrazovku. Nejdřív knihovnu SDL_Image inicializujeme (IMG_Init), poté načteme obrázek do SDL_Surface (IMG_Load). Dále je postup stejný, jako jsme použili pro BMP obrázek.

Vykreslování na vrstvy

V dnešní ukázce spojíme všechny znalosti dohromady. Využijeme obrázku postavy, který už máme, a přidáme k tomu obrázek pozadí – pro ukázku použiji tento. Stáhnu jej a přidám do složky Pictures a i do projektu (pro pohodlí).

Řekněme, že máme tuto situaci: Chceme vykreslit postavu (druhou zleva, třetí odshora) a přitom za tuto postavu vykreslit pozadí, které jsme si stáhli. Nejprve si oba obrázky načteme do SDL_Surface funkcí IMG_Load. Protože budeme chtít zachovat proporce pozadí, musíme zjistit velikost obrázku. Víme, že obrázek je postaven na výšku, proto delší hrana bude vertikální. Také víme, že jsou čtyři postavy nad sebou, tedy výšku jedné postavy zjistíme vydělením výšky celého obrázku čtyřmi. To stejné provedeme pro šířku. Protože už postava zůstane tak, jak jsme ji vybrali (nebude se měnit postava ani pozadí), uložíme ji do SDL_Texture. Nejdříve ale vytvoříme novou SDL_Surface o rozměrech delší strany postavy. Poté využijeme funkce SDL_BlitSurface, pomocí které překopírujeme obsah z obou SDL_Surface do nově vytvořené. Nesmíme zapomenout, že chceme zkopírovat pouze jednu postavu. Zároveň tedy budeme muset vytvořit obdélník, ze kterého se bude kopírovat. Po kopírování novou SDL_Surface převedeme na SDL_Texture a vykreslíme.

//do kterých míst obrazovky se bude vykreslovat
SDL_Rect* TargetRectangle = new SDL_Rect;
TargetRectangle->x = TargetRectangle->y = 0;

//načtení obrázků
SDL_Surface* SurfaceWithPicture = SDL_LoadBMP("Pictures/healer_f.bmp");
SDL_Surface* SurfaceWithBackground = IMG_Load("Pictures/GreenBlackBG_0.png");

//zjištění rozměrů postavy
int CharacterHeight = SurfaceWithPicture->h / 4;
int CharacterWidth = SurfaceWithPicture->w / 3;

//nastavení rozměrů obdélníku podle velikosti postavy, pro lepší viditelnost bude 5x větší.

//nastavení obdelníku, ze kterého se bude vykreslovat
SDL_Rect* CharacterSourceRect = new SDL_Rect;
CharacterSourceRect->x = 1 * CharacterWidth;
CharacterSourceRect->y = 2 * CharacterHeight;
CharacterSourceRect->w = CharacterWidth;
CharacterSourceRect->h = CharacterHeight;

//vytvoření konečné Surface, která bdue převedena na SDL_Texture
SDL_Surface* FinalSurface=SDL_CreateRGBSurface(NULL,
                                 CharacterHeight,CharacterHeight,
                                 32,
                                 0,0,0,0);

//zkopírování obrazů do finální surface
SDL_BlitSurface(SurfaceWithBackground, NULL, FinalSurface, NULL);
SDL_BlitSurface(SurfaceWithPicture,CharacterSourceRect,FinalSurface,NULL);

//vytvoření textury, která se bude vykreslovat na obrazovku
SDL_Texture* TextureWithCharacterAndBackground = SDL_CreateTextureFromSurface(renderer,FinalSurface);
první pokus

Vidíme, že se nám zároveň s obrázkem vykreslilo i bílé pozadí. Kdybychom nahráli obrázek ve formátu PNG s průhledným pozadím, tak bude i vykreslené pozadí průhledné. Proč jsme tedy nahráli BMP obrázek? Ukážeme si dvě funkce, již zmiňovanou SDL_SetColorKey, které nastaví určitou barvu jako průhlednou. K tomu budeme potřebovat tuto barvu definovat. O to se nám postará funkce SDL_MapRGB. Nesmíme zapomínat, že obrázek může být uložen v několika formátech, proto jako parametr předáme formát SDL_Surface. Následující kód přidáme před část kódu, kde se kopíruje SDL_Surface do finální SDL_Surface.

Uint32 WhiteColor = SDL_MapRGB(SurfaceWithPicture->format, 255, 255, 255);
SDL_SetColorKey(SurfaceWithPicture, SDL_ENABLE, WhiteColor);
Druhy pokus

Nyní opravíme ještě poslední dva detaily. Jednak vidíme, že pozadí postavy je černé, ale my jsme chtěli použít celý obrázek. Příčina je ve funkci SDL_BlitSurface, která zachovává proporce – zkopírovala tedy jenom levý horní roh obrázku. Pokud budeme chtít obrázek zmenšit, musíme použít funkci SDL_BlitScaled, která obrázek zmenší do požadované velikosti. Chová se vlastně jako SDL_RenderCopy. Proč tedy existují dvě metody? Jak jsem už zmínil, jestliže se obrázek transformuje na novou velikost, vyžaduje to nějaký výkon. Z toho plyne, že funkce SDL_BlitSurface bude rychlejší než SDL_BlitScaled, protože nevykonává tolik operací.

Předposlední věc je pozice postavy. Není úplně vycentrovaná doprostřed. Tady si vystačíme s SDL_Rect a jednoduchými matematickými počty. Podle šířky postavy a celkové šířky nové SDL_Surface určíme nové souřadnice postavy.

Poslední věc není vidět, ale je také velmi důležitá. Do doby, než získáme SDL_Texture s obrázkem, jsme vytvořili dva SDL_Rect a tři SDL_Surface. Ty už dále v programu potřebovat nebudeme, proto je musíme uvolnit funkcí SDL_FreeSurface. Nyní již máme vše hotovo. Přikládám finální část kódu a výstup.

//načtení obrázků
SDL_Surface* SurfaceWithPicture = SDL_LoadBMP("Pictures/healer_f.bmp");
SDL_Surface* SurfaceWithBackground = IMG_Load("Pictures/GreenBlackBG_0.png");

//nastavení barvy, která bude průhledná
Uint32 WhiteColor = SDL_MapRGB(SurfaceWithPicture->format, 255, 255, 255);
SDL_SetColorKey(SurfaceWithPicture, SDL_ENABLE, WhiteColor);

//zjištění rozměrů postavy
int CharacterHeight = SurfaceWithPicture->h / 4;
int CharacterWidth = SurfaceWithPicture->w / 3;

//vytvoření obdelníka, na který se bude vykreslovat na obrazovku
SDL_Rect* TargetRectangle = new SDL_Rect;
TargetRectangle->x = TargetRectangle->y = 0;
TargetRectangle->w = TargetRectangle->h = CharacterHeight * 5;

//obdelník, ve kterém je naše postavave na zdrojovém obrázku
SDL_Rect* CharacterSourceRect = new SDL_Rect;
CharacterSourceRect->x = 1 * CharacterWidth;
CharacterSourceRect->y = 2 * CharacterHeight;
CharacterSourceRect->w = CharacterWidth;
CharacterSourceRect->h = CharacterHeight;

//vytvoření SDL_Surface, která se poté změní na SDL_Texture
SDL_Surface* FinalSurface=SDL_CreateRGBSurface(NULL, CharacterHeight,CharacterHeight, 32, 0,0,0,0);

//Vytvoření obdelníku, na který se bude malovat tak, aby byla postava uprostřed
SDL_Rect* CharacterTargetRect = new SDL_Rect;
CharacterTargetRect->h = CharacterHeight;
CharacterTargetRect->w = CharacterWidth;
CharacterTargetRect->y = 0;
CharacterTargetRect->x = (FinalSurface->w – CharacterWidth) / 2;

//překreslení - nejprve pozadí, potom postava
SDL_BlitScaled(SurfaceWithBackground, NULL, FinalSurface, NULL);
SDL_BlitSurface(SurfaceWithPicture,CharacterSourceRect,FinalSurface,CharacterTargetRect);

//vytvoření SDL_Texture z SDL_Surface
SDL_Texture* TextureWithCharacterAndBackground = SDL_CreateTextureFromSurface(renderer,FinalSurface);

//Uvolnění prostředků
SDL_FreeSurface(SurfaceWithPicture);
SDL_FreeSurface(SurfaceWithBackground);
SDL_FreeSurface(FinalSurface);
delete CharacterSourceRect;
delete CharacterTargetRect;
Finalni pokus

Nyní už jsme schopni vykreslit libovolný obrázek na obrazovku. V příštím díle se podíváme, jakým způsobem budeme vykreslovat text. Využijeme to k vypsání FPS (Frames per second – počet snímků za sekundu) na obrazovku.


 

Stáhnout

Staženo 177x (5.75 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?
Celkem (2 hlasů) :
55555


 


Miniatura
Předchozí článek
SDL - Základy vykreslování
Miniatura
Všechny články v sekci
SDL
Miniatura
Následující článek
SDL - Vykreslení textu

 

 

Komentáře

Avatar
Michal Žůrek (misaz):

:D FPS není fraps per second, ale Frames Per Second - https://en.wikipedia.org/wiki/Frame_rate

Odpovědět  +3 31.8.2015 15:48
Nesnáším {}, proto se jim vyhýbám.
Avatar
David Novák
Tým ITnetwork
Avatar
Odpovídá na Michal Žůrek (misaz)
David Novák:

Jaj to už jsem opravoval v jednom předchozím článku.. :D

Musím se přiznat, že jsem ale v začátcích měl taky zažité zajímavé významy zkratek..

Odpovědět  +1 31.8.2015 15:50
Chyba je mezi klávesnicí a židlí.
Avatar
patrik.valkovic
Šéfredaktor
Avatar
Odpovídá na Michal Žůrek (misaz)
patrik.valkovic:

Jo já to vím, nevím proč jsem psal fraps :D slabá chvilka :D

Odpovědět  +1 31.8.2015 16:00
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
David Novák
Tým ITnetwork
Avatar
Odpovídá na patrik.valkovic
David Novák:

Nepřemýšlel jsi zrovna hrách a co tak použít k záznamu? :D

Odpovědět  +1 31.8.2015 16:12
Chyba je mezi klávesnicí a židlí.
Avatar
rikenbekr
Člen
Avatar
rikenbekr:

Na procvičení jsem si dělal program který pracoval s vykreslováním obrázků.
Fungoval, ale ve chvíli kdy jsem obrázek (.bmp) upravil v GIMPu (původní obrázek byl také tvořen v GIMPu) tak
program při pokusu o přístup do promněné surface->w (surface s obrázkem) vyhodil chybu SIGSEGV neoprávněný přístup do pamněti. Poté co jsem obrázek nahradil původním vše fungovalo jak má.
Bylo to dáno tím že se obrázek nenačetl?
PS: Chápu správně že SDL_LoadBMP() vrací NULL když se obrázek nenačte správně?

Odpovědět 28.9.2015 10:51
In world without fences and walls, who needs Gates and Windows?
Avatar
patrik.valkovic
Šéfredaktor
Avatar
Odpovídá na rikenbekr
patrik.valkovic:

Ano, pokud obrázek není správně načtený, vrací funkce NULL (nebo také 0). Zřejmě to GIMP uložil v nějakém formátu, kterému SDL nerozumí. Jednou se mi stalo, že jsem si stáhl BPM obrázek z internetu a potom hodinu zjišťoval, proč se nechce načíst. Problém byl samozřejmě s obrázkem. Zkus ho uložit v jiném formátu.
Jinak pokud chceš zjistit, jaká je hodnota surface, můžeš použít debugovací nástroje.

Odpovědět 28.9.2015 11:18
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
rikenbekr
Člen
Avatar
Odpovědět 28.9.2015 11:24
In world without fences and walls, who needs Gates and Windows?
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.

Zobrazeno 7 zpráv z 7.