Lekce 4 - SDL - Práce s obrázky
V minulé lekci, SDL - Základy vykreslování, jsme si na obrazovku vykreslili obdélník a podle akcí uživatele jím pohybovali.
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_CreateTextureFromSurface.
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.
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.
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);
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);
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;
Nyní už jsme schopni vykreslit libovolný obrázek na obrazovku.
V příští lekce, SDL - Vykreslení textu, se podíváme, jakým způsobem můžeme vykreslovat text.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkamiStaženo 719x (5.75 MB)