V dnešní části seriálu, v němž se věnujeme programovacím jazykům a taktéž knihovnám, které mohou být použity pro výuku programování i základů počítačové grafiky, se seznámíme s některými dalšími možnostmi, které lze využít při kopírování a vykreslování rastrových obrázků. S problematikou vykreslování bitmap v knihovně Pygame úzce souvisí i práce s písmem a fonty, což je taktéž téma, kterému se dnes budeme věnovat.
Obsah
2. Konverze objektů typu Surface do takzvaného kompatibilního formátu
3. Zachování alfa kanálu při konverzi objektů typu Surface
4. Rastrové operace prováděné v průběhu BitBLT (Blit)
5. Vyžití TrueType fontů v knihovně Pygame
6. Vykreslení textu vybraným fontem do samostatné bitmapy
7. Ukázka vlivu použití antialiasingu na kvalitu vykresleného textu
8. Pozadí textu: využití průhlednosti či vybrané konstantní barvy
10. Repositář se zdrojovými kódy dnešních demonstračních příkladů
11. Funkce použité v dnešních demonstračních příkladech
1. Programovací jazyky a knihovny určené pro výuku základů počítačové grafiky: práce s bitmapami a TrueType fonty
Jednou z nejdůležitějších operací, kterou knihovna Pygame svým uživatelům nabízí, je minule popsaná operace typu BitBLT neboli též blit. Aby bylo možné tuto operaci provádět co nejrychleji, je nutné, aby zdrojové bitmapy používaly stejný formát uložení pixelů, jako bitmapy cílové, což je téma, kterému se budeme věnovat ve druhé a taktéž ve třetí kapitole. V kapitole čtvrté se zmíníme o rastrových operacích (raster ops, rops) aplikovaných při kopírování bitmap a v navazujících kapitolách se již budeme zabývat další důležitou součástí knihovny Pygame – podporou pro TrueType fonty.
Obrázek 1: Tento rastrový obrázek bude použit (podobně jako minule) ve všech demonstračních příkladech, v nichž bude použita funkce blit().
2. Konverze objektů typu Surface do takzvaného kompatibilního formátu
Při frekventovaném vykreslování mnoha bitmap do herní scény poměrně rychle zjistíme, že se v herním algoritmu vyskytl jeden dosti závažný problém: vykreslování bude pomalé ve chvíli, kdy formát uložení hodnot (barev) pixelů ve framebufferu (resp. přesněji řečeno v zadním bufferu) je odlišný od formátu uložení pixelů v načtených bitmapách. Ostatně stačí si vzpomenout na jeden z demonstračních příkladů, který jsme si popsali v předchozí části tohoto seriálu. V tomto příkladu se po jeho spuštění zjistily informace o použitém grafickém režimu; mezi tyto informace samozřejmě patří i způsob uložení hodnot (barev) pixelů v bufferu. Informace zjištěné o aktuálně nastaveném grafickém režimu vypadaly takto:
Desktop resolution: 1280 x 1024 pixels
Video memory size: 2560 MB
bits per pixel: 16
bytes per pixel: 2
windowed mode: yes
HW acceleration: yes
HW blitting: no
HW colorkey blitting: no
HW alpha blitting: no
SW blitting: no
SW colorkey blitting: no
SW alpha blitting: no
Masks: (63488, 2016, 31, 0)
Shifts: (11, 5, 0, 0)
Losses: (3, 2, 3, 8)
Ve chvíli, kdy načtené bitmapy mají odlišný formát uložení pixelů (což je v praxi více než pravděpodobné), bude muset knihovna Pygame při každém vykreslení takové bitmapy aplikovat konverzní funkci pro každý vykreslovaný pixel, což je samozřejmě časově náročné. Řešení tohoto problému je však velmi jednoduché – bitmapy je možné ihned po jejich načtení jednorázově konvertovat do formátu kompatibilního s bufferem:
<i># Načtení bitmapy a její okamžitá konverze do formátu kompatibilního s framebufferem</i>
image_surface = (pygame.image.load(os.path.join('images', 'gnome-globe.png')).convert())
A právě způsob konverze bitmapy do formátu kompatibilního s framebufferem je ukázána v dnešním prvním demonstračním příkladu:
<i>#!/usr/bin/python</i>
<i># vim: set fileencoding=utf-8</i>
<i># Demonstrační příklady využívající knihovnu Pygame</i>
<i># Příklad číslo 12: konverze objektů typu Surface.</i>
<strong>import</strong> pygame, sys, os, math
<i># Nutno importovat kvůli konstantám QUIT atd.</i>
<strong>from</strong> pygame.locals <strong>import</strong> *
<i># Velikost okna aplikace</i>
WIDTH = 320
HEIGHT = 240
<i># Inicializace knihovny Pygame</i>
pygame.init()
clock = pygame.time.Clock()
<i># Vytvoření okna pro vykreslování</i>
display = pygame.display.set_mode([WIDTH, HEIGHT])
<i># Nastavení titulku okna</i>
pygame.display.set_caption('Pygame test #12')
<i># Konstanty s n-ticemi představujícími základní barvy</i>
BLACK = ( 0, 0, 0)
BLUE = ( 0, 0, 255)
CYAN = ( 0, 255, 255)
GREEN = ( 0, 255, 0)
YELLOW = (255, 255, 0)
RED = (255, 0, 0)
MAGENTA = (255, 0, 255)
WHITE = (255, 255, 255)
<i># Vyplnění plochy okna černou barvou</i>
display.fill(BLACK)
<i># Vykreslení čar různou barvou</i>
pygame.draw.line(display, BLUE, (10, 10), (160, 10))
pygame.draw.line(display, CYAN, (10, 20), (160, 20))
pygame.draw.line(display, GREEN, (10, 30), (160, 30))
pygame.draw.line(display, YELLOW, (10, 40), (160, 40))
pygame.draw.line(display, RED, (10, 50), (160, 50))
pygame.draw.line(display, MAGENTA, (10, 60), (160, 60))
<i># Vykreslení čar s různým sklonem</i>
<strong>for</strong> i <strong>in</strong> range(1,90,5):
<i># převod ze stupňů na radiány</i>
angle = math.radians(i)
radius = 150
<i># výpočet koncových bodů úseček</i>
x = radius * math.sin(math.radians(i))
y = radius * math.cos(math.radians(i))
<strong>if</strong> display.get_bitsize() >= 24:
<i># vykreslení jedné antialiasované úsečky, blend je nastaveno na True</i>
pygame.draw.aaline(display, WHITE, (WIDTH-1, 0), (WIDTH-x, y), True)
<strong>else:</strong>
<i># vykreslení jedné úsečky</i>
pygame.draw.line(display, WHITE, (WIDTH-1, 0), (WIDTH-x, y))
<i># Vykreslení čar různou šířkou</i>
<strong>for</strong> i <strong>in</strong> range(1,10):
pygame.draw.line(display, WHITE, (10 + i*15, 90), (10 + i*15, 230), i)
<i># Načtení bitmapy a její okamžitá konverze do formátu kompatibilního s framebufferem</i>
image_surface = (pygame.image.load(os.path.join('images', 'gnome-globe.png')).convert())
<i># Výpočet souřadnic pro umístění obrázku přesně doprostřed okna</i>
center_x = (display.get_width() - image_surface.get_width()) / 2
center_y = (display.get_height() - image_surface.get_height()) / 2
<i># Vykreslení obrázku</i>
display.blit(image_surface, (center_x, center_y))
<i># Hlavní herní smyčka</i>
<strong>while</strong> True:
<i># Načtení a zpracování všech událostí z fronty</i>
<strong>for</strong> event <strong>in</strong> pygame.event.get():
<strong>if</strong> event.type == QUIT:
pygame.quit()
sys.exit()
<strong>if</strong> event.type == KEYDOWN <strong>and</strong> event.key == K_ESCAPE:
pygame.quit()
sys.exit()
pygame.display.update()
clock.tick(20)
<i># finito</i>
Obrázek 2: Obrazovka prvního demonstračního příkladu. Povšimněte si toho, že při konverzi bitmapy muselo dojít ke ztrátě informací uložených v alfa kanálu, protože celá bitmapa (planeta i její pozadí) je zcela neprůhledná.
3. Zachování alfa kanálu při konverzi objektů typu Surface
Pokud se podíváme na obrázek číslo 2 zobrazený před začátkem této kapitoly, ihned zjistíme, že konverze bitmapy do formátu kompatibilního s framebufferem nedopadla zcela korektně, a to z toho důvodu, že se ztratily informace o alfa kanálu. Ve zdrojové bitmapě totiž bylo pozadí planety průhledné, ovšem při konverzi došlo ke ztrátě této mnohdy velmi důležité informace. Tento problém je snadno řešitelný, pouze je nutné původní programový kód:
<i># Načtení bitmapy a její okamžitá konverze do formátu kompatibilního s framebufferem</i>
image_surface = (pygame.image.load(os.path.join('images', 'gnome-globe.png')).convert())
nahradit za:
<i># Načtení bitmapy a její okamžitá konverze do formátu kompatibilního s framebufferem</i>
<i># Při konverzi se zachová i alfa kanál</i>
image_surface = (pygame.image.load(os.path.join('images', 'gnome-globe.png')).convert_alpha())
V závislosti na tom, jaký formát má zdrojová bitmapa a jaký je formát framebufferu může dojít ke třem typům konverzí:
- Ve výsledné bitmapě bude plnohodnotný osmibitový alfa kanál (použito u 24bpp a 32bpp).
- Ve výsledné bitmapě bude jeden bit rezervovaný pro uložení informace o průhlednosti (použito u 15bpp a 16bpp).
- Ve výsledné bitmapě bude jeden index barvy rezervovaný pro uložení informace o průhlednosti (použito u 8bpp).
V dnešním druhém demonstračním příkladu je problém zachování alfa kanálu vyřešen:
<i>#!/usr/bin/python</i>
<i># vim: set fileencoding=utf-8</i>
<i># Demonstrační příklady využívající knihovnu Pygame</i>
<i># Příklad číslo 13: konverze objektů typu Surface.</i>
<i># Zachování alfa kanálu.</i>
<strong>import</strong> pygame, sys, os, math
<i># Nutno importovat kvůli konstantám QUIT atd.</i>
<strong>from</strong> pygame.locals <strong>import</strong> *
<i># Velikost okna aplikace</i>
WIDTH = 320
HEIGHT = 240
<i># Inicializace knihovny Pygame </i>
pygame.init()
clock = pygame.time.Clock()
<i># Vytvoření okna pro vykreslování</i>
display = pygame.display.set_mode([WIDTH, HEIGHT])
<i># Nastavení titulku okna</i>
pygame.display.set_caption('Pygame test #13')
<i># Konstanty s n-ticemi představujícími základní barvy</i>
BLACK = ( 0, 0, 0)
BLUE = ( 0, 0, 255)
CYAN = ( 0, 255, 255)
GREEN = ( 0, 255, 0)
YELLOW = (255, 255, 0)
RED = (255, 0, 0)
MAGENTA = (255, 0, 255)
WHITE = (255, 255, 255)
<i># Vyplnění plochy okna černou barvou</i>
display.fill(BLACK)
<i># Vykreslení čar různou barvou</i>
pygame.draw.line(display, BLUE, (10, 10), (160, 10))
pygame.draw.line(display, CYAN, (10, 20), (160, 20))
pygame.draw.line(display, GREEN, (10, 30), (160, 30))
pygame.draw.line(display, YELLOW, (10, 40), (160, 40))
pygame.draw.line(display, RED, (10, 50), (160, 50))
pygame.draw.line(display, MAGENTA, (10, 60), (160, 60))
<i># Vykreslení čar s různým sklonem</i>
<strong>for</strong> i <strong>in</strong> range(1,90,5):
<i># převod ze stupňů na radiány</i>
angle = math.radians(i)
radius = 150
<i># výpočet koncových bodů úseček</i>
x = radius * math.sin(math.radians(i))
y = radius * math.cos(math.radians(i))
<strong>if</strong> display.get_bitsize() >= 24:
<i># vykreslení jedné antialiasované úsečky, blend je nastaveno na True</i>
pygame.draw.aaline(display, WHITE, (WIDTH-1, 0), (WIDTH-x, y), True)
<strong>else:</strong>
<i># vykreslení jedné úsečky</i>
pygame.draw.line(display, WHITE, (WIDTH-1, 0), (WIDTH-x, y))
<i># Vykreslení čar různou šířkou</i>
<strong>for</strong> i <strong>in</strong> range(1,10):
pygame.draw.line(display, WHITE, (10 + i*15, 90), (10 + i*15, 230), i)
<i># Načtení bitmapy a její okamžitá konverze do formátu kompatibilního s framebufferem</i>
<i># Při konverzi se zachová i alfa kanál</i>
image_surface = (pygame.image.load(os.path.join('images', 'gnome-globe.png')).convert_alpha())
<i># Výpočet souřadnic pro umístění obrázku přesně doprostřed okna</i>
center_x = (display.get_width() - image_surface.get_width()) / 2
center_y = (display.get_height() - image_surface.get_height()) / 2
<i># Vykreslení obrázku</i>
display.blit(image_surface, (center_x, center_y))
<i># Hlavní herní smyčka</i>
<strong>while</strong> True:
<i># Načtení a zpracování všech událostí z fronty</i>
<strong>for</strong> event <strong>in</strong> pygame.event.get():
<strong>if</strong> event.type == QUIT:
pygame.quit()
sys.exit()
<strong>if</strong> event.type == KEYDOWN <strong>and</strong> event.key == K_ESCAPE:
pygame.quit()
sys.exit()
pygame.display.update()
clock.tick(20)
<i># finito</i>
Obrázek 3: Obrazovka prvního demonstračního příkladu. Zde je již zobrazení korektní, protože se informace uložené v alfa kanálu zdrojové bitmapy při konverzi zachovaly..
4. Rastrové operace prováděné v průběhu BitBLT (Blit)
Operace BitBLT (blit), jejímž popisem jsme se zabývali v předchozí části tohoto seriálu, je většinou používána „pouze“ pro kopii obsahu jedné bitmapy do bitmapy druhé. Ve skutečnosti jsou však možnosti této operace větší, a to z toho důvodu, že při přesunu jednotlivých pixelů je možné provádět takzvané „rastrové operace“, které jsou někdy zkráceně nazývány Raster Op, Raster Ops či dokonce jen ROPS. V minulosti byly tyto operace implementovány logickými funkcemi, ovšem v knihovně Pygame se setkáme spíše s funkcemi využívajícími alfa kanál či barvové složky jednotlivých pixelů. Všechny verze knihovny Pygame podporují tyto operace: BLEND_ADD, BLEND_SUB, BLEND_MULT, BLEND_MIN, BLEND_MAX. Od verze 1.8.1 jsou pak dostupné i složitější operace: BLEND_RGBA_ADD, BLEND_RGBA_SUB, BLEND_RGBA_MULT, BLEND_RGBA_MIN, BLEND_RGBA_MAX BLEND_RGB_ADD, BLEND_RGB_SUB, BLEND_RGB_MULT, BLEND_RGB_MIN a BLEND_RGB_MAX.
Obrázek 4: Příklad použití rastrové operace BLEND_ADD. Zajímavé je, že bitmapa je vykreslena až po vykreslení všech úseček a přesto úsečky přes bitmapu prosvítají.
V následujícím demonstračním příkladu je do framebufferu vykreslena jedna a tatáž bitmapa, ovšem pokaždé s použitím odlišné rastrové operace. Povšimněte si, že specifikace požadované rastrové operace se provádí přes nepovinný parametr special_flags:
<i># Vykreslení obrázku, použití různých operací při vykreslování</i>
display.blit(image_surface, ( 25, 25), special_flags = BLEND_ADD)
display.blit(image_surface, (125, 25), special_flags = BLEND_SUB)
display.blit(image_surface, (225, 25), special_flags = BLEND_MULT)
display.blit(image_surface, ( 25, 125), special_flags = BLEND_MIN)
display.blit(image_surface, (125, 125), special_flags = BLEND_MAX)
Následuje úplný zdrojový kód tohoto příkladu:
<i>#!/usr/bin/python</i>
<i># vim: set fileencoding=utf-8</i>
<i># Demonstrační příklady využívající knihovnu Pygame</i>
<i># Příklad číslo 14: konverze objektů typu Surface.</i>
<i># Operace s alfa kanálem.</i>
<strong>import</strong> pygame, sys, os, math
<i># Nutno importovat kvůli konstantám QUIT atd.</i>
<strong>from</strong> pygame.locals <strong>import</strong> *
<i># Velikost okna aplikace</i>
WIDTH = 320
HEIGHT = 240
<i># Inicializace knihovny Pygame </i>
pygame.init()
clock = pygame.time.Clock()
<i># Vytvoření okna pro vykreslování</i>
display = pygame.display.set_mode([WIDTH, HEIGHT])
<i># Nastavení titulku okna</i>
pygame.display.set_caption('Pygame test #14')
<i># Konstanty s n-ticemi představujícími základní barvy</i>
BLACK = ( 0, 0, 0)
BLUE = ( 0, 0, 255)
CYAN = ( 0, 255, 255)
GREEN = ( 0, 255, 0)
YELLOW = (255, 255, 0)
RED = (255, 0, 0)
MAGENTA = (255, 0, 255)
WHITE = (255, 255, 255)
<i># Vyplnění plochy okna černou barvou</i>
display.fill(BLACK)
<i># Vykreslení čar různou barvou</i>
pygame.draw.line(display, BLUE, (10, 10), (160, 10))
pygame.draw.line(display, CYAN, (10, 20), (160, 20))
pygame.draw.line(display, GREEN, (10, 30), (160, 30))
pygame.draw.line(display, YELLOW, (10, 40), (160, 40))
pygame.draw.line(display, RED, (10, 50), (160, 50))
pygame.draw.line(display, MAGENTA, (10, 60), (160, 60))
<i># Vykreslení čar s různým sklonem</i>
<strong>for</strong> i <strong>in</strong> range(1,90,5):
<i># převod ze stupňů na radiány</i>
angle = math.radians(i)
radius = 150
<i># výpočet koncových bodů úseček</i>
x = radius * math.sin(math.radians(i))
y = radius * math.cos(math.radians(i))
<strong>if</strong> display.get_bitsize() >= 24:
<i># vykreslení jedné antialiasované úsečky, blend je nastaveno na True</i>
pygame.draw.aaline(display, WHITE, (WIDTH-1, 0), (WIDTH-x, y), True)
<strong>else:</strong>
<i># vykreslení jedné úsečky</i>
pygame.draw.line(display, WHITE, (WIDTH-1, 0), (WIDTH-x, y))
<i># Vykreslení čar různou šířkou</i>
<strong>for</strong> i <strong>in</strong> range(1,12):
pygame.draw.line(display, WHITE, (10 + i*15, 90), (10 + i*15, 230), i)
<i># Načtení a konverze obrázku</i>
image_surface = (pygame.image.load(os.path.join('images', 'gnome-globe.png')).convert_alpha())
<i># Vykreslení obrázku, použití různých operací při vykreslování</i>
display.blit(image_surface, ( 25, 25), special_flags = BLEND_ADD)
display.blit(image_surface, (125, 25), special_flags = BLEND_SUB)
display.blit(image_surface, (225, 25), special_flags = BLEND_MULT)
display.blit(image_surface, ( 25, 125), special_flags = BLEND_MIN)
display.blit(image_surface, (125, 125), special_flags = BLEND_MAX)
display.blit(image_surface, (225, 125))
<i># Hlavní herní smyčka</i>
<strong>while</strong> True:
<i># Načtení a zpracování všech událostí z fronty</i>
<strong>for</strong> event <strong>in</strong> pygame.event.get():
<strong>if</strong> event.type == QUIT:
pygame.quit()
sys.exit()
<strong>if</strong> event.type == KEYDOWN <strong>and</strong> event.key == K_ESCAPE:
pygame.quit()
sys.exit()
pygame.display.update()
clock.tick(20)
<i># finito</i>
Obrázek 5: Výsledek běhu předchozího demonstračního příkladu.
5. Vyžití TrueType fontů v knihovně Pygame
Při programování grafických aplikací a samozřejmě i při vytváření počítačových her se v naprosté většině případů setkáme s nutností vykreslit na obrazovku nějaký text, ať již se jedná o pouhý titulek hry, zobrazení dosaženého skóre či o součást grafického uživatelského rozhraní aplikace (hlavní menu, nápověda atd.). V knihovně Pygame samozřejmě existuje podpora pro vykreslování znaků i delších textů, přičemž veškerá funkcionalita je ve skutečnosti zajištěna nativní knihovnou nazvanou SDL_ttf verze 1.2 či 2.0, která navíc při své činnosti využívá další nativní knihovnu pojmenovanou (lib)FreeType (http://www.freetype.org/) určenou pro práci s TrueType fonty. V praxi to znamená, že i když se pro vykreslování používá jiný driver než SDL, tak při práci s fonty by měla být knihovna SDL_ttf přítomná (alternativně lze použít knihovnu nazvanou pygame.freetype, která je vybrána automaticky ve chvíli, kdy SDL_ttf není v systému nalezena.
Jak jsme si již řekli v předchozím odstavci, dokáže knihovna Pygame pracovat s fonty typu TTF (True Type Font), v nichž jsou tvary jednotlivých znaků uloženy ve formě obrysů složených ze sekvence na sebe navazujících úseček a kvadratických Bézierových křivek. Ve skutečnosti se však v naprosté většině případů programátoři nemusí zabývat přesným algoritmem vykreslování jednotlivých znaků, což je ostatně dosti komplikované, především při menších velikostech znaků a použití takzvaného hintingu. Díky modulům nabízených knihovnou Pygame je možné jednoduše načíst vybraný soubor typu TTF s uvedením velikosti výsledného písma. Knihovna Pygame se s využitím nativních částí SDL_ttf a FreeType sama postará o takzvanou rasterizaci jednotlivých znaků, tj. o vykreslení a vyplnění již zmíněných úseček a Bézierových křivek představujících obrysy znaků obsažených v textu, který se má zobrazit. Základem při práci s písmy je třída pygame.font.Font. Inicializace písma se provede následujícím způsobem:
<i># Načtení fontu (zadává se soubor se jménem fontu a požadovaná velikost písma</i>
font = pygame.font.Font("fonts/FreeSans.ttf", 40)
Následně je možné využít instanci třídy pygame.font.Font pro vykreslení řetězce do samostatné automaticky vytvořené bitmapy (Surface), se kterou je možné pracovat jako s každou jinou bitmapou. Velikost bitmapy je vypočtena na základě tvaru znaků v použitém fontu, zadané velikosti textu a samozřejmě i na základě toho, jaký text se má vykreslit. Samotné vykreslení, tj. již výše zmíněná „rasterizace“ je provedena metodou pygame.font.Font.render(), vykreslení pak zajišťuje nám již dobře známá metoda pygame.Surface.blit():
<i># Vytvoření obrázku s vykresleným textem</i>
<i># - první parametr obsahuje řetězec, který se má vykreslit</i>
<i># - druhý parametr řídí použití antialiasingu</i>
<i># - třetí parametr volí barvu fontu</i>
font_surface = font.render("mojefedora.cz", False, WHITE)
<i># Vykreslení obrázku s nápisem do bufferu</i>
display.blit(font_surface, ( 25, 125))
Pro běh dále popsaných demonstračních příkladů je taktéž nutné mít v pracovním adresáři vytvořený podadresář nazvaný „fonts“, v němž je umístěn soubor „FreeSans.ttf“ s fontem. Tento soubor je dostupný na adrese http://www.fontspace.com/gnu-freefont/freesans. Ze staženého archivu ve skutečnosti postačuje rozbalit pouze jediný uvedený soubor „freesans.ttf“ či „FreeSans.ttf“ – pozor na správné použití velkých a malých písmen v demonstračních příkladech!).
Důležité upozornění: pokud se má do herní scény vykreslit jiný text, je nutné postup pro vytvoření nové bitmapy a jejího následného přenosu do zadního bufferu s využitím metody pygame.Surface.blit() opakovat. Samotná rasterizace je relativně složitá a tím pádem i pomalá operace, proto se vyplatí si bitmapy s texty připravit již ve fázi inicializace hry.
6. Vykreslení textu vybraným fontem do samostatné bitmapy
Výše zmíněný postup pro načtení fontu, rasterizaci textu do bitmapy a přenos bitmapy do zadního bufferu s využitím funkce blit() je implementován v dalším demonstračním příkladu, jehož úplný zdrojový kód naleznete na adrese https://github.com/tisnik/presentations/blob/master/pygame/pygame15.py. V tomto příkladu se po inicializaci grafického režimu či po otevření okna nejprve vykreslí řada úseček (což již známe z předchozích příkladů) a následně se do vytvořené scény vypíše i text fontem vysokým 40 pixelů. Při rasterizaci textu se používá bílá barva. Na řádku:
font_surface = font.render("mojefedora.cz", False, WHITE)
je možné druhým parametrem povolit či zakázat použití antialiasingu, což je problematika, které se budeme podrobněji věnovat v navazující kapitole. Následuje výpis zdrojového kódu demonstračního příkladu:
<i>#!/usr/bin/python</i>
<i># vim: set fileencoding=utf-8</i>
<i># Demonstrační příklady využívající knihovnu Pygame</i>
<i># Příklad číslo 15: použití TrueType fontů</i>
<strong>import</strong> pygame, sys, os, math
<i># Nutno importovat kvůli konstantám QUIT atd.</i>
<strong>from</strong> pygame.locals <strong>import</strong> *
<i># Velikost okna aplikace</i>
WIDTH = 320
HEIGHT = 240
<i># Inicializace knihovny Pygame </i>
pygame.init()
clock = pygame.time.Clock()
<i># Vytvoření okna pro vykreslování</i>
display = pygame.display.set_mode([WIDTH, HEIGHT])
<i># Nastavení titulku okna</i>
pygame.display.set_caption('Pygame test #15')
<i># Konstanty s n-ticemi představujícími základní barvy</i>
BLACK = ( 0, 0, 0)
BLUE = ( 0, 0, 255)
CYAN = ( 0, 255, 255)
GREEN = ( 0, 255, 0)
YELLOW = (255, 255, 0)
RED = (255, 0, 0)
MAGENTA = (255, 0, 255)
GRAY = (128, 128, 128)
WHITE = (255, 255, 255)
<i># Vyplnění plochy okna černou barvou</i>
display.fill(BLACK)
<i># Vykreslení čar různou barvou</i>
pygame.draw.line(display, BLUE, (10, 10), (160, 10))
pygame.draw.line(display, CYAN, (10, 20), (160, 20))
pygame.draw.line(display, GREEN, (10, 30), (160, 30))
pygame.draw.line(display, YELLOW, (10, 40), (160, 40))
pygame.draw.line(display, RED, (10, 50), (160, 50))
pygame.draw.line(display, MAGENTA, (10, 60), (160, 60))
<i># Vykreslení čar s různým sklonem</i>
<strong>for</strong> i in range(1,90,5):
<i># převod ze stupňů na radiány</i>
angle = math.radians(i)
radius = 150
<i># výpočet koncových bodů úseček</i>
x = radius * math.sin(math.radians(i))
y = radius * math.cos(math.radians(i))
<strong>if</strong> display.get_bitsize() >= 24:
<i># vykreslení jedné antialiasované úsečky, blend je nastaveno na True</i>
pygame.draw.aaline(display, WHITE, (WIDTH-1, 0), (WIDTH-x, y), True)
<strong>else</strong>:
<i># vykreslení jedné úsečky</i>
pygame.draw.line(display, WHITE, (WIDTH-1, 0), (WIDTH-x, y))
<i># Vykreslení čar různou šířkou</i>
<strong>for</strong> i <strong>in</strong> range(1,12):
pygame.draw.line(display, GRAY, (10 + i*15, 90), (10 + i*15, 230), i)
<i># Načtení fontu (zadává se soubor se jménem fontu a velikost</i>
font = pygame.font.Font("fonts/FreeSans.ttf", 40)
<i># Vytvoření obrázku s vykresleným textem</i>
<i># - první parametr obsahuje řetězec, který se má vykreslit</i>
<i># - druhý parametr řídí použití antialiasingu</i>
<i># - třetí parametr volí barvu fontu</i>
font_surface = font.render("mojefedora.cz", False, WHITE)
<i># Vykreslení obrázku s nápisem do bufferu</i>
display.blit(font_surface, ( 25, 125))
<i># Hlavní herní smyčka</i>
<strong>while</strong> True:
<i># Načtení a zpracování všech událostí z fronty</i>
<strong>for</strong> event <strong>in</strong> pygame.event.get():
<strong>if</strong> event.type == QUIT:
pygame.quit()
sys.exit()
<strong>if</strong> event.type == KEYDOWN <strong>and</strong> event.key == K_ESCAPE:
pygame.quit()
sys.exit()
pygame.display.update()
clock.tick(20)
<i># finito</i>
Obrázek 6: Výpis textu bez použití antialiasingu.
Obrázek 7: Výpis textu s využitím antialiasingu.
7. Ukázka vlivu použití antialiasingu na kvalitu vykresleného textu
Při vykreslování textu s využitím výše popsaných metod se většinou vytvoří bitmapa s bitovou hloubkou 8bpp. Samotné vykreslování takového bitmapy s použitím nám již známé metody pygame.Surface.blit() je velmi rychlé a i samotný způsob uložení bitmapy v paměti je poměrně efektivní, ovšem na druhou stranu zde neexistuje podpora pro skutečný antialiasing textů, takže šikmé tahy znaků budou vykazovat typické „schody“. V případě, že je antialiasing nutné použít (což nemusí být případ hry, kde se většinou upřednostňuje rychlejší vykreslování před kvalitou!), je možné v metodě pygame.font.Font.render() pomocí hodnoty druhého parametru specifikovat, aby se při rasterizaci vytvořila bitmapa s bitovou hloubkou 32bpp a tím pádem i s plnohodnotným osmibitovým alfa kanálem. A právě alfa kanál je využit při antialiasingu, zejména při rasterizaci okrajů písma (zde budou použity pixely s variabilní průhledností).
Podívejme se na rozdíl mezi vykreslením textu bez použití antialiasingu a s použitím antialiasingu:
Obrázek 8: Detailní pohled na scénu vykreslenou v předchozím demonstračním příkladu zavoláním metody font_surface = font.render("mojefedora.cz", False, WHITE) (zákaz antialiasingu).
Obrázek 9: Detailní pohled na scénu vykreslenou v předchozím demonstračním příkladu zavoláním metody font_surface = font.render("mojefedora.cz", True, WHITE) (povolení antialiasingu).
Nevýhody použití antialiasingu při práci s texty jsou tři:
- Pomalejší vykreslování výsledné bitmapy, neboť je nutné provádět výpočet průhlednosti s každým pixelem (násobení!)
- Větší paměťové nároky (projeví se při meziukládání bitmap a větším množství textů)
- Problematická práce s těmito bitmapami ve chvíli, kdy není nastaven celoobrazovkový grafický režim s hloubkou 24bpp či 32bpp – a tyto režimy se v mnoha hrách nepoužívají, právě z důvodu větších nároků při práci s grafikou (přesun větších bitmap při operaci blit() atd.)
Je tedy nutné pečlivě zvážit, kterou metodou se budou texty do (herních) scén vykreslovat a mnohdy převáží sice méně kvalitní, ale zato rychlejší rendering textů bez antialiasingu.
8. Pozadí textu: využití průhlednosti či vybrané konstantní barvy
Bitmapa (resp. přesněji řečeno objekt typu Surface), která je automaticky vytvořena při vykreslení fontu, může obsahovat buď průhlednou barvu pozadí (kde je průhlednost nastavena na 100%) nebo naopak libovolnou zvolenou barvu. To, jaká možnost se při vykreslování využije, závisí na tom, zda se při volání metody pygame.font.Font.render() použije čtvrtý nepovinný parametr obsahující zvolenou barvu pozadí. V prvním případě (= čtvrtý nepovinný parametr není použit) je vytvořena bitmapa s bitovou hloubkou 8bpp obsahující v místech, kde se nenachází žádný znak, zcela průhledné pixely. Pokud je zapnut antialiasing, je použita bitmapa s hloubkou 32bpp a průhlednost je tedy reprezentována plnohodnotným osmibitovým alfa kanálem, barva pozadí tedy nemusí být na hranách písma přesně 100%, ale i méně. Při použití druhé metody jsou průhledné či poloprůhledné pixely vyplněny zadanou barvou.
V dnešním posledním demonstračním příkladu je tato možnost ukázána. Na screenshotu zobrazeném pod výpisem zdrojového kódu si povšimněte vlivu použití antialiasingu na kvalitu zobrazeného textu i vliv rastrové operace BLEND_ADD:
<i>#!/usr/bin/python</i>
<i># vim: set fileencoding=utf-8</i>
<i># Demonstrační příklady využívající knihovnu Pygame</i>
<i># Příklad číslo 16: použití TrueType fontů</i>
<strong>import</strong> pygame, sys, os, math
<i># Nutno importovat kvůli konstantám QUIT atd.</i>
<strong>from</strong> pygame.locals <strong>import</strong> *
<i># Velikost okna aplikace</i>
WIDTH = 320
HEIGHT = 240
<i># Inicializace knihovny Pygame </i>
pygame.init()
clock = pygame.time.Clock()
<i># Vytvoření okna pro vykreslování</i>
display = pygame.display.set_mode([WIDTH, HEIGHT])
<i># Nastavení titulku okna</i>
pygame.display.set_caption('Pygame test <i>#16')</i>
<i># Konstanty s n-ticemi představujícími základní barvy</i>
BLACK = ( 0, 0, 0)
BLUE = ( 0, 0, 255)
CYAN = ( 0, 255, 255)
GREEN = ( 0, 255, 0)
YELLOW = (255, 255, 0)
RED = (255, 0, 0)
MAGENTA = (255, 0, 255)
GRAY = (128, 128, 128)
WHITE = (255, 255, 255)
<i># Vyplnění plochy okna černou barvou</i>
display.fill(BLACK)
<i># Vykreslení čar různou barvou</i>
pygame.draw.line(display, BLUE, (10, 10), (160, 10))
pygame.draw.line(display, CYAN, (10, 20), (160, 20))
pygame.draw.line(display, GREEN, (10, 30), (160, 30))
pygame.draw.line(display, YELLOW, (10, 40), (160, 40))
pygame.draw.line(display, RED, (10, 50), (160, 50))
pygame.draw.line(display, MAGENTA, (10, 60), (160, 60))
<i># Vykreslení čar s různým sklonem</i>
<strong>for</strong> i in range(1,90,5):
<i># převod ze stupňů na radiány</i>
angle = math.radians(i)
radius = 150
<i># výpočet koncových bodů úseček</i>
x = radius * math.sin(math.radians(i))
y = radius * math.cos(math.radians(i))
<strong>if</strong> display.get_bitsize() >= 24:
<i># vykreslení jedné antialiasované úsečky, blend je nastaveno na True</i>
pygame.draw.aaline(display, WHITE, (WIDTH-1, 0), (WIDTH-x, y), True)
<strong>else</strong>:
<i># vykreslení jedné úsečky</i>
pygame.draw.line(display, WHITE, (WIDTH-1, 0), (WIDTH-x, y))
<i># Vykreslení čar různou šířkou</i>
<strong>for</strong> i <strong>in</strong> range(1,12):
pygame.draw.line(display, GRAY, (10 + i*15, 90), (10 + i*15, 230), i)
<i># Načtení fontu (zadává se soubor se jménem fontu a velikost</i>
font = pygame.font.Font("fonts/FreeSans.ttf", 40)
<i># Vytvoření obrázku s vykresleným textem</i>
<i># - první parametr obsahuje řetězec, který se má vykreslit</i>
<i># - druhý parametr řídí použití antialiasingu</i>
<i># - třetí parametr volí barvu fontu</i>
font_surface1 = font.render("mojefedora.cz", True, WHITE, RED)
<i># Vykreslení obrázku s nápisem do bufferu</i>
display.blit(font_surface1, ( 25, 90), special_flags = BLEND_ADD)
display.blit(font_surface1, ( 25, 150))
<i># Hlavní herní smyčka</i>
<strong>while</strong> True:
<i># Načtení a zpracování všech událostí z fronty</i>
<strong>for</strong> event <strong>in</strong> pygame.event.get():
<strong>if</strong> event.type == QUIT:
pygame.quit()
sys.exit()
<strong>if</strong> event.type == KEYDOWN <strong>and</strong> event.key == K_ESCAPE:
pygame.quit()
sys.exit()
pygame.display.update()
clock.tick(20)
<i># finito</i>
Obrázek 10: První text byl do své bitmapy vykreslen bez použití antialiasingu, při operaci blit() se ovšem použila rastrová operace BLEND_ADD. Druhý text byl do své bitmapy vykreslen s použitím antialiasingu a při přenosu bitmapy se použila běžná rastrová operace COPY.
9. Obsah další části seriálu
Prakticky žádná hra odehrávající se v 2D prostoru se neobejde bez množství objektů, které se pohybují v herním světě. Tyto objekty spolu mohou kolidovat (narážet do sebe atd.), popř. mohou nastat kolize mezi pohyblivým objektem a nepohyblivým pozadím herní scény. Aby nebylo nutné výpočet kolizí pro každou vznikající hru znovu programovat, nabízí knihovna Pygame svým uživatelům řešení ve formě modulu pygame.sprite. A právě popisem některých možností nabízených tímto modulem se budeme zabývat v navazující části tohoto seriálu.
10. Repositář se zdrojovými kódy dnešních demonstračních příkladů
Všechny demonstrační příklady, s nimiž jsme se v dnešním článku seznámili, byly, podobně jako v předchozích částech tohoto seriálu, uloženy do Git repositáře umístěného na GitHubu (https://github.com/tisnik/presentations):
# | Příklad | Zdrojový kód |
---|---|---|
1 | pygame12.py | https://github.com/tisnik/presentations/blob/master/pygame/pygame12.py |
2 | pygame13.py | https://github.com/tisnik/presentations/blob/master/pygame/pygame13.py |
3 | pygame14.py | https://github.com/tisnik/presentations/blob/master/pygame/pygame14.py |
4 | pygame15.py | https://github.com/tisnik/presentations/blob/master/pygame/pygame15.py |
5 | pygame16.py | https://github.com/tisnik/presentations/blob/master/pygame/pygame16.py |
6 | images/gnome-globe.png | https://github.com/tisnik/presentations/tree/master/pygame/images |
7 | fonts/FreeSans.ttf | https://github.com/tisnik/presentations/blob/master/pygame/fonts/FreeSans.ttf |
Poznámka: soubor nazvaný „gnome-globe.png“ musí být uložen v podadresáři „images“, protože právě z tohoto podadresáře je načítán demonstračními příklady. Podobné pravidlo je nutné dodržet i pro soubor nazvaný „FreeSans.ttf“, který je očekáván v podadresáři „fonts“.
11. Funkce použité v dnešních demonstračních příkladech
- pygame.init()
http://www.pygame.org/docs/ref/pygame.html#pygame.init - pygame.quit()
http://www.pygame.org/docs/ref/pygame.html#pygame.quit - pygame.display.set_mode()
http://www.pygame.org/docs/ref/display.html#pygame.display.set_mode - pygame.display.set_caption()
http://www.pygame.org/docs/ref/display.html#pygame.display.set_caption - pygame.display.quit()
http://www.pygame.org/docs/ref/display.html#pygame.display.quit - pygame.display.update()
http://www.pygame.org/docs/ref/display.html#pygame.display.update - pygame.Surface.blit()
http://www.pygame.org/docs/ref/surface.html#pygame.Surface.blit - pygame.Surface.convert()
http://www.pygame.org/docs/ref/surface.html#pygame.Surface.convert - pygame.Surface.convert_alpha()
http://www.pygame.org/docs/ref/surface.html#pygame.Surface.convert_alpha - pygame.event.get()
http://www.pygame.org/docs/ref/event.html#pygame.event.get - pygame.time.Clock.tick()
http://www.pygame.org/docs/ref/time.html#pygame.time.Clock.tick - pygame.draw.line()
http://www.pygame.org/docs/ref/draw.html#pygame.draw.line - pygame.draw.aaline()
http://www.pygame.org/docs/ref/draw.html#pygame.draw.aaline - pygame.image.load()
http://www.pygame.org/docs/ref/image.html#pygame.image.load - pygame.font.Font()
http://www.pygame.org/docs/ref/font.html#pygame.font.Font - pygame.font.Font.render()
http://www.pygame.org/docs/ref/font.html#pygame.font.Font.render
12. Odkazy na Internetu
- Pygame.org
http://pygame.org/hifi.html - Pygame - instalační soubory pro různé operační systémy
http://pygame.org/download.shtml - Pygame: documentation
http://www.pygame.org/docs/ - Pygame Wiki: Getting Started
http://www.pygame.org/wiki/GettingStarted - Pygame Tutorials: Tutorials Basic
http://pygametutorials.wikidot.com/tutorials-basic - Pygame: Font class
http://www.pygame.org/docs/ref/font.html - Python.org (dokumentace k jazyku, odkazy na instalační soubory atd.)
https://www.python.org/ - How to Draw with Pygame on Your Raspberry Pi
http://www.dummies.com/how-to/content/how-to-draw-with-pygame-on-your-raspberry-pi.html - Bresenham's line algorithm
https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm - Xiaolin Wu's line algorithm
https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm - Pyglet
https://pypi.python.org/pypi/pyglet/1.2.4 - Pyglet documentation
http://pythonhosted.org/pyglet/ - PyOpenGL
https://pypi.python.org/pypi/PyOpenGL/ - Computer font (Wikipedia)
https://en.wikipedia.org/wiki/Computer_font - TrueType (Wikipedia)
https://en.wikipedia.org/wiki/TrueType - SDL and Fonts
http://www.gamedev.net/page/resources/_/technical/game-programming/sdl--fonts-r1953 - SDL_ttf Documentation
http://www.libsdl.org/projects/SDL_ttf/docs/ - SDL_ttf 2.0 (není prozatím součástí SDLJava)
http://www.libsdl.org/projects/SDL_ttf/ - SDL_ttf doc
http://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf_frame.html - SDL 1.2 Documentation: SDL_Surface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlsurface.html - SDL 1.2 Documentation: SDL_PixelFormat
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlpixelformat.html - SDL 1.2 Documentation: SDL_LockSurface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdllocksurface.html - SDL 1.2 Documentation: SDL_UnlockSurface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlunlocksurface.html - SDL 1.2 Documentation: SDL_LoadBMP
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlloadbmp.html - SDL 1.2 Documentation: SDL_SaveBMP
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlsavebmp.html - SDL 1.2 Documentation: SDL_BlitSurface
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlblitsurface.html - SDL 1.2 Documentation: SDL_VideoInfo
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlvideoinfo.html - SDL 1.2 Documentation: SDL_GetVideoInfo
http://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlgetvideoinfo.html - Graphical user interface (Wikipedia)
http://en.wikipedia.org/wiki/Graphical_user_interface - The Real History of the GUI
http://articles.sitepoint.com/article/real-history-gui - Computer-History: Xerox Alto
http://www.mr-gadget.de/apple/2004-01-15/computer-history-xerox-alto - bitsavers.org
http://www.bitsavers.org/ - Dokumenty k počítači Xerox Alto na bitsavers.org
http://www.bitsavers.org/pdf/xerox/alto/ - The ALTO Computer
http://www.maniacworld.com/alto-computer-video.html - Xerox Alto Operating System and Alto Applications
http://www.digibarn.com/collections/software/alto/index.html - Xerox Alto (Wikipedia)
http://en.wikipedia.org/wiki/Xerox_Alto - BitBLT routines (1975)
http://www.bitsavers.org/pdf/xerox/alto/BitBLT_Nov1975.pdf - BitBlt in Squeak
http://wiki.squeak.org/squeak/189 - Bitmaps, Device Contexts and BitBlt
http://www.winprog.org/tutorial/bitmaps.html - BitBlt Game Programming Tutorial
http://www.freevbcode.com/ShowCode.asp?ID=3677 - Bit blit (Wikipedia)
http://en.wikipedia.org/wiki/BitBLT - The Xerox Alto
http://toastytech.com/guis/alto3.html - History of the graphical user interface
http://en.wikipedia.org/wiki/History_of_the_graphical_user_interface - Domovská stránka systému LÖVE
http://love2d.org/ - Dokumentace k systému LÖVE
http://love2d.org/wiki/love - Domovská stránka programovacího jazyka Lua
http://www.lua.org/ - Seriál o programovacím jazyku Lua (root.cz):
http://www.root.cz/serialy/programovaci-jazyk-lua/ - Domovská stránka systému LÖVE
http://love2d.org/ - Domovská stránka programovacího jazyka Lua
http://www.lua.org/ - Web o Lieru, Gusanos, GeneRally, Atari atd.
http://karelik.wz.cz/ - Web o Lieru, Gusanos
http://karelik.wz.cz/gusanos.php - GUSANOS
http://gusanos.sourceforge.net/ - GUSANOS Download
http://sourceforge.net/projects/gusanos/ - Lua
http://www.linuxexpres.cz/praxe/lua - Lua
http://cs.wikipedia.org/wiki/Lua - Lua (programming language)
http://en.wikipedia.org/wiki/Lua_(programming_language) - The Lua Programming Language
http://www.tiobe.com/index.php/paperinfo/tpci/Lua.html - Lua Programming Gems
http://www.lua.org/gems/ - LuaForge
http://luaforge.net/ - Forge project tree
http://luaforge.net/softwaremap/trove_list.php - SdlBasic home page
http://www.sdlbasic.altervista.org/main/ - SdlBasic examples
http://nitrofurano.linuxkafe.com/sdlbasic/ - SdlBasic na Wikipedii
http://en.wikipedia.org/wiki/SdlBasic - Simple DirectMedia Layer
http://en.wikipedia.org/wiki/Simple_DirectMedia_Layer - SDLBASIC – The high-level interpreter for all?
http://openbytes.wordpress.com/2008/11/08/sdlbasic-the-high-level-interpreter-for-all/ - FreeBasic home page
http://www.freebasic.net/ - FreeBASIC (Wikipedia EN)
https://en.wikipedia.org/wiki/FreeBASIC - FreeBASIC Wiki
http://www.freebasic.net/wiki/wikka.php?wakka=FBWiki - FreeBASIC Manual
http://www.freebasic.net/wiki/wikka.php?wakka=DocToc - FreeBASIC (Wikipedia CZ)
http://cs.wikipedia.org/wiki/FreeBASIC - The Griffon Legend
http://syn9.thingie.net/?table=griffonlegend - Seriál Letní škola programovacího jazyka Logo
http://www.root.cz/serialy/letni-skola-programovaciho-jazyka-logo/ - Scratch: oficiální stránka projektu
http://scratch.mit.edu/ - Scratch: galerie projektů vytvořených ve Scratchi
http://scratch.mit.edu/galleries/browse/newest - Scratch: nápověda
file:///usr/share/scratch/Help/en/index.html - Scratch: obrazovky nápovědy
file:///usr/share/scratch/Help/en/allscreens.html - Scratch (Wikipedie CZ)
http://cs.wikipedia.org/wiki/Scratch - Scratch (programming language)
http://en.wikipedia.org/wiki/Scratch_(programming_language) - Scratch Modification
http://wiki.scratch.mit.edu/wiki/Scratch_Modification - Scratch Lowers Resistance to Programming
http://www.wired.com/gadgetlab/2009/03/scratch-lowers/ - Snap!
http://snap.berkeley.edu/ - Prostředí Snap!
http://snap.berkeley.edu/snapsource/snap.html - Alternatives to Scratch
http://wiki.scratch.mit.edu/wiki/Alternatives_to_Scratch - Basic-256 home page
http://www.basic256.org/index_en - Basic-256 Language Documentation
http://doc.basic256.org/doku.php - Basic-256 Art Gallery
http://www.basic256.org/artgallery - Basic-256 Tutorial
http://www.basic256.org/tutorials - Why BASIC?
http://www.basic256.org/whybasic - A book to teach ANYBODY how to program a computer (using BASIC)
http://www.basicbook.org/ - BASIC Computer Games (published 1978) - Hammurabi
http://atariarchives.org/basicgames/showpage.php?page=78 - Hamurabi - zdrojový kód v BASICu
http://www.dunnington.u-net.com/public/basicgames/HMRABI