4. diel - SDĽ - Práca s obrázkami
V dnešnom diele si povieme niečo o obrázkoch a ako s nimi pracovať.
Pretože sa obrázok nahráva najprv do SDL_Surface
, budú podobné
techniky použité pre všetky vykresľovanie, ktoré prebieha najprv na
SDL_Surface
, a až potom na obrazovku. Tiež si ukážeme funkcie
pre kopírovanie blokov pamäte, ktoré budeme využívať najčastejšie.
Načítanie a vykreslenie .bmp obrázku
Najskôr si v zložke projektu vytvoríme novú zložku a pomenujeme ju
Pictures
. Túto zložku budeme tiež kopírovať do výstupnej
zložky, preto pridáme príkaz pre jej skopírovanie do Pre-build eventu -
xcopy "$(ProjectDir)Pictures" "$(TargetDir)Pictures"/e /i /f /y
.
V aplikácii budeme potrebovať obrázok, ktorý vykreslíme. Využijeme
zdroj z minulého dielu. V zazipované zložke sú rôzne PNG súbory, jeden z
nich si vyberieme a vložíme ho do zložky Pictures
. Najprv ale
budeme potrebovať obrázok vo formáte BMP, preto tento obrázok skonvertuje -
napríklad v obyčajnom maľovaní. Teraz máme v priečinku
Pictures
dva rovnaké obrázky, jeden vo formáte PNG a druhý vo
formáte BMP.
Teraz si tento obrázok vykreslíme na obrazovku. Najprv budeme potrebovať
obrázok načítať, k tomu slúži funkcia SDL_LoadBMP. Tá načíta
obrázok zo súboru do SDL_Surface
. Povedali sme si však, že
SDL_Renderer
dokáže pracovať iba s SDL_Texture
.
Musíme vytvoriť SDL_Texture
z SDL_Surface
, k tomu
slúži funkcia SDL_CreateTextureFromSurface. Teraz máme uložený
obrázok v pamäti grafickej karty a teda máme k dispozícii
hardvérovo-akcelerované vykresľovanie.
SDL_Surface* SurfaceWithPicture = SDL_LoadBMP("Pictures/healer_f.bmp");
SDL_Texture* TextureWithPicture = SDL_CreateTextureFromSurface(renderer,SurfaceWithPicture);
Vykreslení obrázka
K samotnému vykreslenie na obrazovku budeme potrebovať iba jedinú funkciu
- SDL_RenderCopy. Položky SDL_Renderer
a
SDL_Texture
sú povinné. V našom prípade sa bude vykresľovať
TextureWithPicture
do SDL_Renderer
okna. Ak jeden z
posledných dvoch parametrov vynecháme, znamená to, že sa má použiť celá
plocha. Podrobnejšie, keď vynecháme parameter zdroje, použije sa celá
textúra, ak vynecháme parameter ciele, vykreslí sa obraz cez celú obrazovku.
Ak budeme chcieť vykresliť len časť obrazu, musíme funkciu dodať tretí
parameter (ukazovateľ na SDL_Rect
). Naopak ak chceme, aby sa
obrázok vykreslil na určité miesto, musíme dodať posledný parameter
(opäť ukazovateľ na SDL_Rect
). Najlepšie o efekte vypovedá
nasledujúci obrázok.
Všimnime si, že SDL automaticky zväčšuje obrázok tak, aby sa celý vošiel do pripraveného miesta. Vo výsledku teda môžeme byť veľkosť zdrojového obdĺžnika iná ako veľkosť obdĺžnika cieľového. Nemusí byť dokonca zachované ani proporcie (rozmery strán), SDL obrázok automaticky transformuje podľa potreby.
Tiež je potrebné spomenúť situáciu, kedy sa obdĺžnik dostane mimo rozsahu plochy. Ak sa to stane u ciele, nič zásadné sa nedeje. Jednoducho sa len obrázok oreže a vizuálny efekt vyzerá, ako by bol obrázok mimo okno (schovaný za rámom). Avšak ak táto situácia nastane pre zdrojový obraz, obdĺžnik sa oreže podľa okraja plochy. Ak bol obdĺžnik široký 50 bodov a 10 bodov presahuje pravý okraj obrázku, bude výsledok rovnaký, ako keby sme mali obdĺžnik na rovnakých súradniciach, ale o 10 bodov užší. Tento jav je ukázaný na nasledujúcom obrázku. Vidíme, že sa obrázok roztiahol do šírky, ale pritom stále zaberá celé okno.
Načítanie iných formátov obrázkov
SDL v základe podporuje obrázky iba formáte BMP kvôli ich jednoduché kompresiu a formátu. Pokiaľ budeme chcieť použiť aj iné obrázky, budeme potrebovať rozšírenie k SDL, knižnicu SDL_image. Stiahneme si vývojársku verziu (Development Libraries) pre Visual Studio tu. Rovnako ako pre samotné SDL pridáme zložku include do "* Include Directories " a zložku lib / x64 do " Library Directories ". Přilinkujeme ďalšie knižnicu SDL2_image.lib v nastavení Linker - Input - Additional Dependencies. Ešte nezabudneme skopírovať všetky dynamické knižnice (.dll súbory) do zložky Output v projekte. Tentoraz okrem *SDL2_image.dll máme aj ďalšie. SDL2 musí vedieť pracovať rôznymi formátmi súborov a pre každý formát je pre lepšiu prehľadnosť a oddelenie kódu vytvorená samostatná dynamická knižnica.
Dokumentácia SDL2_image je pomerne jednoduchá, rovnako ako jej použitie. Tak ako u samotného SDL najskôr zavoláme metódu IMG_Init a IMG_Quit na konci programu.
Najčastejšie budeme nahrávať nový súbor. K tomu nám stačí
jednoduchá funkcie IMG_Load. Funkcia sama rozozná, o aký formát
súboru ide, a automaticky sa postará o práca s otvorením a zatvorením
súboru. Nám sa potom vráti iba ukazovateľ na SDL_Surface
, v
ktorom je nahraný obrázok. Všetky ostatné funkcie prijímajú ako parameter
ukazovateľ na SDL_RWops
, ktorý v SDL slúži pre abstrakciu
práce so súbormi. Dostaneme sa k nemu v jednom z ďalších dielov.
Posledná skupina funkcií, ktoré knižnica SDL_Image obsahuje, sú funkcie
pre validáciu
formátu. Pre každý typ existuje samostatná funkcia, ktorá vždy končí
koncovkou súboru (IMG_isPNG
, IMG_isJPG
,
IMG_isGIF
). Všetky tieto funkcie prijímajú ako parameter
SDL_RWops
, preto sa nimi nebudeme zaoberať.
Teraz už by sme mali byť schopní načítať obrázok akéhokoľvek
formátu a vykresliť ho na obrazovku. Najskôr knižnicu SDL_Image
inicializujeme (IMG_Init
), potom načítame obrázok do
SDL_Surface
(IMG_Load
). Ďalej je postup rovnaký, ako
sme použili pre BMP obrázok.
Vykresľovanie na vrstvy
V dnešnej ukážke spojíme všetky znalosti dohromady. Využijeme obrázku postavy, ktorý už máme, a pridáme k tomu obrázok pozadia - pre ukážku použijem tento. Stiahnem ho a pridám do zložky Pictures a aj do projektu (pre pohodlie).
Povedzme, že máme túto situáciu: Chceme vykresliť postavu (druhú
zľava, tretí odhora) a pritom za túto postavu vykresliť pozadie, ktoré sme
si stiahli. Najprv si obaja obrázky načítame do SDL_Surface
funkcií IMG_Load
. Pretože budeme chcieť zachovať proporcie
pozadie, musíme zistiť veľkosť obrázka. Vieme, že obrázok je postavený
na výšku, preto dlhšia hrana bude vertikálne. Tiež vieme, že sú štyri
postavy nad sebou, teda výšku jednej postavy zistíme vydelením výšky
celého obrázka štyrmi. To isté vykonáme pre šírku. Pretože už postava
zostane tak, ako sme ju vybrali (nebude sa meniť postava ani pozadia),
uložíme ju do SDL_Texture
. Najskôr ale vytvoríme novú
SDL_Surface
s rozmermi dlhšej strany postavy. Potom využijeme
funkcie SDL_BlitSurface,
pomocou ktorej prekopíruje obsah z oboch SDL_Surface
do
novovytvorenej. Nesmieme zabudnúť, že chceme skopírovať iba jednu postavu.
Zároveň teda budeme musieť vytvoriť obdĺžnik, z ktorého sa bude
kopírovať. Po kopírovanie novú SDL_Surface
prevedieme 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 sa nám zároveň s obrázkom vykreslilo i biele pozadie. Keby
sme nahrali obrázok vo formáte PNG s priehľadným pozadím, tak bude aj
vykreslené pozadie priehľadné. Prečo sme teda nahrali BMP obrázok?
Ukážeme si dve funkcie, už spomínanou SDL_SetColorKey, ktoré
nastaví určitú farbu ako priehľadnú. K tomu budeme potrebovať túto farbu
definovať. O to sa nám postará funkcia SDL_MapRGB. Nesmieme
zabúdať, že obrázok môže byť uložený v niekoľkých formátoch, preto
ako parameter odovzdáme formát SDL_Surface
. Nasledujúci kód
pridáme pred časť kódu, kde sa kopíruje SDL_Surface
do
finálnej SDL_Surface
.
Uint32 WhiteColor = SDL_MapRGB(SurfaceWithPicture->format, 255, 255, 255); SDL_SetColorKey(SurfaceWithPicture, SDL_ENABLE, WhiteColor);
Teraz opravíme ešte posledné dva detaily. Jednak vidíme, že pozadie
postavy je čierne, ale my sme chceli použiť celý obrázok. Príčina je vo
funkcii SDL_BlitSurface, ktorá zachováva proporcie - skopírovala
teda len ľavý horný roh obrázku. Ak budeme chcieť obrázok zmenšiť,
musíme použiť funkciu SDL_BlitScaled, ktorá obrázok zmenší do
požadovanej veľkosti. Chová sa vlastne ako SDL_RenderCopy
.
Prečo teda existujú dve metódy? Ako som už spomenul, ak sa obrázok
transformuje na novú veľkosť, vyžaduje to nejaký výkon. Z toho vyplýva,
že funkcia SDL_BlitSurface
bude rýchlejší než
SDL_BlitScaled
, pretože nevykonáva toľko operácií.
Predposledný vec je pozícia postavy. Nie je úplne vycentrovaná
doprostred. Tu si vystačíme s SDL_Rect
a jednoduchými
matematickými počty. Podľa šírky postavy a celkovej šírky nové
SDL_Surface
určíme nové súradnice postavy.
Posledná vec nie je vidieť, ale je tiež veľmi dôležitá. Do doby, než
získame SDL_Texture s obrázkom, sme vytvorili dva SDL_Rect
a tri
SDL_Surface
. Tie už ďalej v programe potrebovať nebudeme, preto
ich musíme uvoľniť funkcií SDL_FreeSurface. Teraz už máme všetko
hotové. Prikladám finálna časť 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;
Teraz už sme schopní vykresliť ľubovoľný obrázok na obrazovku. V budúcom dieli sa pozrieme, akým spôsobom budeme vykresľovať text. Využijeme to k vypísanie FPS (Frames per second - počet snímok za sekundu) na obrazovku.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkamiStiahnuté 756x (5.75 MB)