Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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_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 - SDL

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 - SDL

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 - SDL

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 - SDL

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 - SDL

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ínkami

Staženo 712x (5.75 MB)

 

Předchozí článek
SDL - Základy vykreslování
Všechny články v sekci
SDL
Přeskočit článek
(nedoporučujeme)
SDL - Vykreslení textu
Článek pro vás napsal Patrik Valkovič
Avatar
Uživatelské hodnocení:
4 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity