Obsah
1. Základní vlastnosti grafického formátu GIF
2. Logická obrazovka, rámce a barvová paleta
3. Export rastrových obrázků do formátu GIF
4. Rozdíl při exportu obrázků s barvovým prostorem RGBA a obrázků s barvovou paletou
5. Nastavení vlastností výsledného GIFu
6. Omezení počtu barev ve výsledném GIFu
8. Vytvoření jednoduchého animovaného GIFu – blikající čtvereček
9. Složitější animace – pohybující se šachovnice
10. Volba metody přechodu mezi jednotlivými snímky animace
11. Použití nastaveného pozadí GIFu
12. Přechod na další snímek bez smazání snímku předchozího
13. Vytvoření GIFu s animovaným progress barem
14. Tvorba 2D grafiky s využitím knihovny GG
15. Parametry kreslení zapamatované v kontextu
16. Kreslení otevřených tvarů (úseček) pomocí příkazu Stroke
17. Vyplnění uzavřené křivky příkazem Fill
18. Nastavení základních parametrů cest: průhlednost, šířka tahu a styl vykreslení
19. Repositář s demonstračními příklady
1. Základní vlastnosti grafického formátu GIF
„This format has the unique quality that, like the AK47, in a lot of circumstances, it just works, unlike the more complex alternatives.“
V úvodní části dnešního článku o programovacím jazyku Go si ukážeme práci s rastrovým grafickým formátem GIF. První varianta grafického formátu GIF vznikla již v roce 1987 (ostatně proto se taky tato verze nazývá GIF87a), takže se jedná o souborový formát, který se v oboru IT udržel již neskutečných 32 let. Dnes se ovšem budeme zabývat převážně novější verzí GIFu pocházející z roku 1989. Tato varianta se jmenuje GIF89a a jedná se o formát, jenž je význačný především tím, že podporuje animace a v neposlední řadě i tím, že GIF je prakticky univerzálně akceptovaný napříč většinou počítačových architektur a platforem, a to i těch (pre)historických. Pokud je tedy zapotřebí vložit na webovou stránku nějakou jednoduchou animaci (ideálně v případě, že se jedná o animaci s objekty ostrých tvarů a s malým počtem barev), je u GIFu prakticky zaručeno, že příjemce bude schopen si animaci prohlédnout, a to bez nutnosti instalace kodeků či nějakých pluginů do prohlížečů.
V dalších odstavcích se zmíníme o některých omezeních GIFu. Jedno zásadní omezení spočívající v patentovaném algoritmu už sice není relevantní, ale technická omezení způsobila, že vzniklo několik alternativ, které měly GIF v oblasti jednoduchých animací nahradit. V první řadě se jednalo o formát MNG (Multiple-image Network Graphics), jenž je založený na populárním PNG. Na rozdíl od PNG (pouze statické obrázky) se však MNG příliš nerozšířil a nakonec byla jeho podpora odstraněna i z webových prohlížečů, s tím, že by měl být MNG nahrazen jiným formátem nazvaným APNG neboli Animated Portable Network Graphics. Tento formát je podporován ve většině významných prohlížečů, viz též Can I use: Animated PNG (APNG), ovšem s tím varováním, že podpora chybí pro IE a Edge: Edge platform status. I přes relativně dobrou podporu jak na straně prohlížečů, tak i na straně software pro výrobu animací, však není APNG zdaleka tak rozšířen, jako „starý dobrý“ GIF a proto je znalost práce s GIFem stále relevantní.
Obrázek 1: Takřka ideální kandidát na použití animovaného GIFu – výsledek jednoduché simulace.
2. Logická obrazovka, rámce a barvová paleta
Grafický formát GIF primárně slouží pro záznam a přenos grafických informací uložených ve formě bitmapy, tj. obecně vícebarevného rastrového obrázku. Většina z dnes používaných grafických formátů, například JPEG, PNG, TGA či BMP obsahuje popis rastrového obrázku jako celku, přičemž se (buď přímo nebo v komprimované podobě) ukládají barvové hodnoty všech pixelů, ze kterých se rastrový obrázek skládá.
V grafickém formátu GIF je však použit odlišný přístup. Celý obrázek, který je zde nazývaný logická obrazovka (logical screen), není v souboru uložen jako jeden celek, ale skládá se z několika takzvaných rámců (frame), což jsou obdélníkové oblasti umístěné uvnitř logické obrazovky. Minimálně musí být vždy přítomen jeden rámec, jejich maximální množství však není omezeno. Každý rámec je možné chápat jako rastrový obrázek, který je celou svou plochou umístěn v logické obrazovce – podle specifikace nesmí žádný pixel z rámce padnout mimo logickou obrazovku. Pozice rámce v logické obrazovce je určena souřadnicí jeho horního levého rohu a velikostí (šířkou, výškou) zadanou v pixelech. Dále může být k rámci přiřazena lokální barvová paleta, pokud však není přítomna, použije se globální barvová paleta (viz další text).
Obrázek 2: Vztah mezi logickou obrazovkou a rámci.
Na předchozím obrázku je naznačené, jakým způsobem může být logická obrazovka popsána pomocí rámců. Vidíme, že není nutné, aby rámce pokryly celou logickou obrazovku, rámce se dokonce mohou v případě potřeby navzájem překrývat. Jak uvidíme dále, je při překryvu rámců možné použít transparentní pixely, takže překryv může být ve skutečnosti mnohem složitější, než pouhé přepsání barev pixelů v zadané obdélníkové oblasti.
Rámce nachází v grafickém formátu GIF mnoho uplatnění. Na dalších dvou obrázcích je naznačeno, jakým způsobem je možné použít rámce pro redukci celkové velikosti obrázku. Prázdné bílé plochy není nutné explicitně ukládat, může se zde pouze vykreslit barva pozadí. Algoritmus LZW je, podobně jako některé další algoritmy použité pro komprimaci obrázků (kromě primitivního RLE), poměrně hloupý a zbytečně by si sekvencemi stejných bytů zaplnil hešovací tabulku a tím by byl nucen ji buď rozšířit, nebo uvést do původní podoby (bez zapamatovaných sekvencí delších než jeden pixel). Obojí však vede ke zvětšení výsledného souboru. Mimochodem, na obrázcích 3 a 4 je ukázána úvodní stránka z první kapitoly TEXbooku.
Obrázek 3: Obrázek uložený v jednom rámci.
Obrázek 4: Obrázek rozdělený na tři rámce s využitím barvy pozadí (ta je pro ilustraci zobrazena šedě).
Vzhledem k tomu, že každý pixel umístěný v rámci může být popsán maximálně osmi bity, znamená to, že jeho barvu je možné vybrat z barvové palety obsahující nejvýše 256 barev. Každá barva je v barvové paletě popsána trojicí hodnot – barvových složek označených písmeny R (red), G (green) a B (blue), přičemž každá barvová složka je uložena v jednom bytu. Minimální velikost barvové palety (se dvěma barvami) je proto rovna šesti bytům (2×3), maximální velikost barvové palety s 256 barvami pak 768 bytům (256×3). U mnoha obrázků se proto může stát, že barvová paleta tvoří nezanedbatelnou část jejich celkové velikosti.
V grafickém formátu GIF rozlišujeme dvě barvové palety: globální a lokální. Globální barvová paleta (global color table) může v souboru typu GIF existovat maximálně jednou a její velikost (tj. počet barev) je zadána v hlavičce popisující logickou obrazovku. V některých případech nemusí být globální barvová paleta přítomna vůbec. Lokální barvová paleta (local color table) může být přiřazena ke každému rámci, opět se však nejedná o povinnou součást rámce. V případě, že není přítomna ani globální ani lokální barvová paleta, měl by prohlížecí program použít systémovou paletu, v případě webových prohlížečů takzvanou web-safe paletu složenou z 216 barev rovnoměrně umístěných v jednotkové RGB krychli; zbylé barvy tvoří odstíny šedi. Absence obou barvových palet sice může zmenšit celkovou velikost souboru, způsob zobrazení se však může v různých prohlížečích lišit, proto se většinou (99% případů) alespoň jedna barvová paleta používá.
Důležitou součástí popisu logické obrazovky je index jedné barvy v globální barvové paletě, která bude použita pro vykreslení pozadí. Dodnes sice většina programů při ukládání GIFů každý obrázek uloží do jednoho velikého rámce, ale při optimalizaci na velikost je možné (a některé sofistikovanější programy to provádí), aby některé pixely logické obrazovky nebyly pokryty žádným z rámců. Nepokryté pixely by poté měly být vybarveny právě barvou pozadí. Tímto způsobem, jak si ostatně ukážeme v další části, je možné vytvořit optimalizované obrázky, či dokonce obrázky o prakticky jakékoli velikosti, které však stále budou zabírat malé místo na disku (díky tomu, že je uložen například pouze malý rámec uprostřed obrovské volné plochy).
Verze GIF89a přinesla oproti původní verzi GIF87a jednu podstatnou novinku – pomocí rozšiřujícího řídicího bloku (graphics control extension) je možné v každém rámci specifikovat jeden index do barvové palety, který představuje průhledné pixely. Díky tomu je možné, aby optický tvar rámce nebyl pouze obdélníkový, ale v podstatě libovolný. Vzhledem k tomu, že je možné „průhlednými“ pixely překreslit i původně neprůhlednou barvu pozadí, může mít i obrázek jako celek neobdélníkový tvar, což je velmi často využíváno zejména při tvorbě různých log na webových stránkách a v neposlední řadě i v řadě animací. Na třetím obrázku je ukázka GIFu s průhledností.
Obrázek 5: GIF s průhlednými pixely.
3. Export rastrových obrázků do formátu GIF
Nejprve si ukážeme ten nejjednodušší způsob exportu rastrových obrázků do formátu GIF. Obrázek je samozřejmě nutné nejdříve vytvořit, například funkcí CreateChessboard, kterou jsme si ukázali minule:
img := CreateChessboard(256, 256, BoardSize)
Dále vytvoříme nový soubor pro uložení obrázku:
outfile, err := os.Create("02.gif") if err != nil { panic(err) } defer outfile.Close()
A následně obrázek nechápe zakódovat pomocí funkce Encode, podobně jako jsme to udělali v případě formátu PNG:
err = gif.Encode(outfile, img, nil) if err != nil { panic(err) }
Výsledek:
Obrázek 6: Výsledek činnosti prvního demonstračního příkladu.
Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/go-fedora/blob/master/article15/02_gif_export_rgba.go:
package main import ( "image" "image/color" "image/draw" "image/gif" "os" ) const BoardSize = 8 func CreateChessboard(width int, height int, board_size int) *image.RGBA { var palette = []color.Color{ color.RGBA{150, 205, 50, 255}, color.RGBA{0, 100, 0, 255}, } img := image.NewRGBA(image.Rect(0, 0, width, height)) index_color := 0 hor_block := int(width / board_size) ver_block := int(height / board_size) x_from := 0 x_to := hor_block for x := 0; x < board_size; x++ { y_from := 0 y_to := ver_block for y := 0; y < board_size; y++ { r := image.Rect(x_from, y_from, x_to, y_to) draw.Draw(img, r, &image.Uniform{palette[index_color]}, image.ZP, draw.Src) y_from = y_to y_to += ver_block index_color = 1 - index_color } x_from = x_to x_to += hor_block index_color = 1 - index_color } return img } func main() { img := CreateChessboard(256, 256, BoardSize) outfile, err := os.Create("02.gif") if err != nil { panic(err) } defer outfile.Close() err = gif.Encode(outfile, img, nil) if err != nil { panic(err) } }
4. Rozdíl při exportu obrázků s barvovým prostorem RGBA a obrázků s barvovou paletou
Pokusme se nyní vytvořit jednopixelový obrázek (o rozměrech 1×1 pixel) v barvovém prostoru RGBA nebo NRGBA:
c := color.RGBA{255, 0, 0, 255} img := image.NewRGBA(image.Rect(0, 0, 1, 1)) img.Set(0, 0, c)
Po exportu do GIFu vznikne tento soubor:
Obrázek 7: Jednopixelový obrázek, který však obsahuje paletu s 256 barvami.
Pokud takový obrázek otevřeme v nějakém grafickém editoru, můžeme se přesvědčit o tom, že obsahuje úplnou barvovou paletu s 256 barvami:
Obrázek 8: Zobrazení barvové palety v grafickém editoru.
Celková velikost souboru s obrázkem je taktéž až enormně velká v poměru k obrazovým informacím, které obsahuje: celých 798 bajtů!
Úplný výpis zdrojového kódu tohoto demonstračního příkladu je umístěn na adrese https://github.com/tisnik/go-fedora/blob/master/article15/03_gif_1×1_rgba.go:
package main import ( "image" "image/color" "image/gif" "os" ) func CreateImage(width int, height int) *image.RGBA { c := color.RGBA{255, 0, 0, 255} img := image.NewRGBA(image.Rect(0, 0, width, height)) img.Set(0, 0, c) return img } func main() { img := CreateImage(1, 1) outfile, err := os.Create("03.gif") if err != nil { panic(err) } defer outfile.Close() err = gif.Encode(outfile, img, nil) if err != nil { panic(err) } }
Tento příklad ovšem můžeme modifikovat, a to konkrétně takovým způsobem, že se namísto obrázku s barvovým prostorem RGBA (16 milionů barvových odstínů) použije obrázek s paletou. Minimální velikost palety je ve formátu GIF rovna dvěma položkám, takže si takovou paletu vytvoříme:
var palette = []color.Color{ color.RGBA{255, 128, 128, 255}, color.RGBA{255, 255, 255, 255}, }
Následně je možné vytvořit rastrový obrázek používající tuto paletu a nastavit jeho (jediný) pixel na zvolenou barvu vybranou z palety:
c := color.RGBA{255, 128, 128, 255} img := image.NewPaletted(image.Rect(0, 0, 1, 1), palette) img.Set(0, 0, c)
Po uložení získáme tento soubor:
Obrázek 9: Jednopixelový obrázek, který obsahuje paletu se dvěma barvami.
Jeho velikost je nyní pouhých 34 bajtů, což je současně nejmenší možná velikost zcela validního obrázku uloženého ve formátu GIF.
Po otevření vytvořeného obrázku v grafickém editoru je patrné, že paleta se rapidně zmenšila na dvě položky:
Obrázek 10: Zobrazení barvové palety v grafickém editoru.
Upravený demonstrační příklad, který vytvoří obrázek s naprosto stejnou obrazovou informací jako obrázek předchozí, může vypadat následovně:
package main import ( "image" "image/color" "image/gif" "os" ) func CreateImage(width int, height int) *image.Paletted { var palette = []color.Color{ color.RGBA{255, 128, 128, 255}, color.RGBA{255, 255, 255, 255}, } c := color.RGBA{255, 128, 128, 255} img := image.NewPaletted(image.Rect(0, 0, width, height), palette) img.Set(0, 0, c) return img } func main() { img := CreateImage(1, 1) outfile, err := os.Create("04.gif") if err != nil { panic(err) } defer outfile.Close() err = gif.Encode(outfile, img, nil) if err != nil { panic(err) } }
5. Nastavení vlastností výsledného GIFu
Při exportu rastrových obrázků do formátu GIF je možné přes poslední parametr Encode specifikovat ukazatel na strukturu typu Options, která obsahuje doplňující informace o vlastnostech výsledného GIFu. Můžeme například explicitně nastavit počet barev:
options := gif.Options{NumColors: 255, Quantizer: nil, Drawer: nil}
Ukazatel na nově vytvořenou proměnnou se předá do funkce Encode:
err = gif.Encode(outfile, img, &options) if err != nil { panic(err) }
package main import ( "image" "image/color" "image/draw" "image/gif" "os" ) const BoardSize = 8 func CreateChessboard(width int, height int, board_size int) *image.RGBA { var palette = []color.Color{ color.RGBA{150, 205, 50, 255}, color.RGBA{0, 100, 0, 255}, } img := image.NewRGBA(image.Rect(0, 0, width, height)) index_color := 0 hor_block := int(width / board_size) ver_block := int(height / board_size) x_from := 0 x_to := hor_block for x := 0; x < board_size; x++ { y_from := 0 y_to := ver_block for y := 0; y < board_size; y++ { r := image.Rect(x_from, y_from, x_to, y_to) draw.Draw(img, r, &image.Uniform{palette[index_color]}, image.ZP, draw.Src) y_from = y_to y_to += ver_block index_color = 1 - index_color } x_from = x_to x_to += hor_block index_color = 1 - index_color } return img } func main() { img := CreateChessboard(256, 256, BoardSize) outfile, err := os.Create("05.gif") if err != nil { panic(err) } defer outfile.Close() options := gif.Options{NumColors: 255, Quantizer: nil, Drawer: nil} err = gif.Encode(outfile, img, &options) if err != nil { panic(err) } }
Export šachovnice do obrázku typu GIF s dvoubarevnou barvovou paletou:
Obrázek 11: Šachovnice, barvová paleta je snížena na dvě barvy, celková velikost obrázku je nyní pouze 2667 bajtů.
Předchozí obrázek byl vytvořen takto upraveným příkladem:
package main import ( "image" "image/color" "image/draw" "image/gif" "os" ) const BoardSize = 8 func CreateChessboard(width int, height int, board_size int) *image.Paletted { var palette = []color.Color{ color.RGBA{150, 205, 50, 255}, color.RGBA{0, 100, 0, 255}, } img := image.NewPaletted(image.Rect(0, 0, width, height), palette) index_color := 0 hor_block := int(width / board_size) ver_block := int(height / board_size) x_from := 0 x_to := hor_block for x := 0; x < board_size; x++ { y_from := 0 y_to := ver_block for y := 0; y < board_size; y++ { r := image.Rect(x_from, y_from, x_to, y_to) draw.Draw(img, r, &image.Uniform{palette[index_color]}, image.ZP, draw.Src) y_from = y_to y_to += ver_block index_color = 1 - index_color } x_from = x_to x_to += hor_block index_color = 1 - index_color } return img } func main() { img := CreateChessboard(256, 256, BoardSize) outfile, err := os.Create("06.gif") if err != nil { panic(err) } defer outfile.Close() err = gif.Encode(outfile, img, nil) if err != nil { panic(err) } }
6. Omezení počtu barev ve výsledném GIFu
S barvovými prostory RGBA a NRGBA jsme se již setkali v předchozím článku. V obou případech se barva pixelu zadává čtyřmi složkami – červenou, zelenou, modrou a průhledností. Každá z těchto složek je uložena v bajtu (jedná se tedy o hodnotu v rozsahu 0 až 255). Celkový počet barev ve spektru dosahuje hodnoty 2563 = 16777216 barev (více než šestnáct milionů). Pokud by tento rozsah nedostačoval, například při provádění některých editačních operací (vícenásobná filtrace atd.), lze použít prostory RGBA64 a NRGBA64. V případě prostoru NRGBA se při ukládání barev neprovádí žádné další operace, u RGBA se však nejprve barvové složky přednásobí průhledností. Ukažme si, jak se v tomto modelu vytvoří gradientní přechody:
img := image.NewNRGBA(image.Rect(0, 0, width, height)) for x := 0; x < 256; x++ { for y := 0; y < 256; y++ { c := color.NRGBA{0, byte(x), byte(y), 255} img.SetNRGBA(x, y, c) c = color.NRGBA{85, byte(x), byte(y), 255} img.SetNRGBA(x+256, y, c) c = color.NRGBA{170, byte(x), byte(y), 255} img.SetNRGBA(x, y+256, c) c = color.NRGBA{255, byte(x), byte(y), 255} img.SetNRGBA(x+256, y+256, c) } }
Takto vytvořený obrázek uložíme do formátu GIF, ovšem postupně budeme měnit počet barev v paletě od 1 do 256 (bude se jednat o mocniny dvou, což vyplývá z omezení samotného GIFu):
for colors := 1; colors <= 256; colors <<= 1 { options := gif.Options{NumColors: colors, Quantizer: nil, Drawer: nil} gif.Encode(outfile, img, &options) }
Výsledky ukazují, jak se s omezením počtu barev dobře či špatně vypořádal interní algoritmus využívající dithering:
Obrázek 12: Použití ditheringu při cílovém počtu 256 barev.
Obrázek 13: Použití ditheringu při cílovém počtu 128 barev.
Obrázek 14: Použití ditheringu při cílovém počtu 64 barev.
Obrázek 15: Použití ditheringu při cílovém počtu 32 barev.
Obrázek 16: Použití ditheringu při cílovém počtu 16 barev.
Obrázek 17: Použití ditheringu při cílovém počtu 8 barev.
Obrázek 18: Použití ditheringu při cílovém počtu 4 barev.
Obrázek 19: Použití ditheringu při cílovém počtu 2 barev.
Pochopitelně si opět ukážeme úplné znění tohoto demonstračního příkladu:
package main import ( "fmt" "image" "image/color" "image/gif" "os" ) const width = 512 const height = 512 func main() { img := image.NewNRGBA(image.Rect(0, 0, width, height)) for x := 0; x < 256; x++ { for y := 0; y < 256; y++ { c := color.NRGBA{0, byte(x), byte(y), 255} img.SetNRGBA(x, y, c) c = color.NRGBA{85, byte(x), byte(y), 255} img.SetNRGBA(x+256, y, c) c = color.NRGBA{170, byte(x), byte(y), 255} img.SetNRGBA(x, y+256, c) c = color.NRGBA{255, byte(x), byte(y), 255} img.SetNRGBA(x+256, y+256, c) } } for colors := 1; colors <= 256; colors <<= 1 { filename := fmt.Sprintf("07_%03d.gif", colors) outfile, err := os.Create(filename) if err != nil { panic(err) } defer outfile.Close() options := gif.Options{NumColors: colors, Quantizer: nil, Drawer: nil} gif.Encode(outfile, img, &options) } }
7. Animované GIFy
Grafický formát GIF je na Internetu stále oblíbený zejména z toho důvodu, že podporuje animace – a to bez nutnosti instalace pluginů na straně prohlížeče nebo použití speciálních technik (JavaScript, streamované video apod.). Animace byly na Internetu zavedeny s příchodem Netscape 2 a posléze Internet Exploreru 3. Ve skutečnosti nejsou animace nic jiného, než sled po sobě jdoucích rámců (které mohou ležet na sobě), mezi jejichž zobrazením je vložena krátká či delší časová prodleva. Pokud jsou rámce překreslovány rychlostí cca 10 snímků za sekundu, vzniká dojem více či méně plynulé animace.
V některých případech, například při požadavcích na prezentaci krátkého videa na webových stránkách, musí tvůrce stránek porovnat možnosti grafického formátu GIF se souborovými či streamovými formáty určenými pro ukládání videa a zjistit, který formát je pro jeho aplikaci výhodnější. GIF není v žádném případě vhodný pro ukládání ani přehrávání větších animací, protože osmibitová paleta je mnohdy nedostačující a mezisnímková komprimace je obecně nedostatečná, zejména při práci s „reálným“, tj. typicky zašuměným videem. GIF také nepodporuje ukládání zvukových stop ani přesné časování snímků (o tom jsme se mohli předminule přesvědčit při práci s „true color“ GIFy).
Pro filmy je použita větší snímková frekvence, typicky 24 či 25 snímků za sekundu, musíme si však uvědomit, že filmové políčko má většinou mnohem větší rozlišení, než animovaný GIF, takže případné „trhání“ není u GIFu tak patrné. Animace byly zavedeny až ve verzi GIF89a.
Pro export/vytvoření animovaných GIFů slouží funkce nazvaná EncodeAll, kterou pochopitelně nalezneme v balíčku image/gif. Příklady použití této užitečné funkce naleznete v navazujících kapitolách:
func EncodeAll(w io.Writer, g *GIF) error
8. Vytvoření jednoduchého animovaného GIFu – blikající čtvereček
V dalším příkladu je ukázáno, jak snadno je možné s použitím základních balíčků programovacího jazyka Go vytvořit jednoduchý animovaný GIF. Celá animace bude obsahovat pouhé dva snímky, každý o rozměrech 32×32 pixelů, které budou vyplněny konstantní barvou. Snímky se budou opakovat ve smyčce (což je výchozí nastavení řízené jedním bitem hlavičky GIFu) a bude mezi nimi prodleva přibližně jedné sekundy.
Obrázky nejdříve musíme uložit do pole ukazatelů na strukturu Paletted (rastrový obrázek s barvovou paletou):
images := []*image.Paletted{ CreateImage(32, 32, 0), CreateImage(32, 32, 1), }
Dále je nutné vytvořit pole obsahující prodlevy mezi jednotlivými snímky. Prodlevy jsou reprezentovány celými čísly, přičemž základní jednotkou je 10ms (setina sekundy):
delays := []int{100, 100}
Pokud počet obrázků odpovídá počtu prodlev, je vytvoření výsledného animovaného GIFu již snadné:
gif.EncodeAll(outfile, &gif.GIF{ Image: images, Delay: delays, })
Velikost výsledného animovaného GIFu s dvojicí snímků je rovna pouhým 147 bajtům, což je prakticky stejná velikost, jakou zabírá tato věta:
Obrázek 20: Animovaný GIF se dvěma snímky.
Opět si pochopitelně ukážeme úplný zdrojový kód tohoto jednoduchého demonstračního příkladu:
package main import ( "image" "image/color" "image/draw" "image/gif" "os" ) const BoardSize = 8 func CreateImage(width int, height int, paletteIndex int) *image.Paletted { var palette []color.Color if paletteIndex == 0 { palette = []color.Color{ color.RGBA{150, 150, 150, 255}, } } else { palette = []color.Color{ color.RGBA{250, 0, 0, 255}, } } img := image.NewPaletted(image.Rect(0, 0, width, height), palette) r := image.Rect(0, 0, width, height) draw.Draw(img, r, &image.Uniform{palette[0]}, image.ZP, draw.Src) return img } func main() { images := []*image.Paletted{ CreateImage(32, 32, 0), CreateImage(32, 32, 1), } delays := []int{100, 100} outfile, err := os.Create("08.gif") if err != nil { panic(err) } defer outfile.Close() gif.EncodeAll(outfile, &gif.GIF{ Image: images, Delay: delays, }) }
9. Složitější animace – pohybující se šachovnice
Nyní nám již nic nebrání si vyzkoušet vytvořit složitější animace. Můžeme například začít se šachovnicí, kterou jsme si ukázali minule. Funkci pro vykreslení šachovnice do obrázku je samozřejmě možné modifikovat tak, aby se celá šachnovnice pohybovala (scrollovala) v horizontálním i vertikálním směru. Postačuje přidat parametry xoffset, yoffset a step_size pro specifikaci maximálního posunu. Posuny musí být součástí smyček pro vykreslení jednotlivých políček šachovnice:
x_from := 0 x_to := hor_block for x := 0; x < board_size+step_size; x++ { y_from := 0 y_to := ver_block for y := 0; y < board_size+step_size; y++ { r := image.Rect(x_from+xoffset, y_from+yoffset, x_to+xoffset, y_to+yoffset) draw.Draw(img, r, &image.Uniform{palette[index_color]}, image.ZP, draw.Src) y_from = y_to y_to += ver_block index_color = 1 - index_color } x_from = x_to x_to += hor_block index_color = 1 - index_color }
Samotnou animaci můžeme vytvořit například takto:
steps := 2 * 256 / BoardSize for step := 0; step < steps; step++ { img := CreateChessboard(256, 256, BoardSize, step*2-steps*2, step-steps, steps*2) images = append(images, img) delays = append(delays, 10) }
Obrázek 21: Pohybující se šachovnice uložená do formátu GIF.
Celý demonstrační příklad nyní vypadá takto:
package main import ( "image" "image/color" "image/draw" "image/gif" "os" ) const BoardSize = 8 func CreateChessboard(width int, height int, board_size int, xoffset int, yoffset int, step_size int) *image.Paletted { var palette = []color.Color{ color.RGBA{150, 205, 50, 255}, color.RGBA{0, 100, 0, 255}, } img := image.NewPaletted(image.Rect(0, 0, width, height), palette) index_color := 0 hor_block := int(width / board_size) ver_block := int(height / board_size) x_from := 0 x_to := hor_block for x := 0; x < board_size+step_size; x++ { y_from := 0 y_to := ver_block for y := 0; y < board_size+step_size; y++ { r := image.Rect(x_from+xoffset, y_from+yoffset, x_to+xoffset, y_to+yoffset) draw.Draw(img, r, &image.Uniform{palette[index_color]}, image.ZP, draw.Src) y_from = y_to y_to += ver_block index_color = 1 - index_color } x_from = x_to x_to += hor_block index_color = 1 - index_color } return img } func main() { var images []*image.Paletted var delays []int steps := 2 * 256 / BoardSize for step := 0; step < steps; step++ { img := CreateChessboard(256, 256, BoardSize, step*2-steps*2, step-steps, steps*2) images = append(images, img) delays = append(delays, 10) } outfile, err := os.Create("09.gif") if err != nil { panic(err) } defer outfile.Close() gif.EncodeAll(outfile, &gif.GIF{ Image: images, Delay: delays, }) }
10. Volba metody přechodu mezi jednotlivými snímky animace
V grafickém formátu GIF je možné u každého snímku v animaci nastavit, jakým způsobem se tento snímek vlastně vykreslí přes snímek předchozí. Pro tuto volbu byly rezervovány tři bity; hodnota tohoto bitového pole potom určuje provedenou operaci:
Hodnota | Význam |
---|---|
0 | žádná operace není specifikována, prohlížeč si může vybrat, jak příkaz interpretovat |
1 | použije se původní snímek, nový přes něj bude překreslen |
2 | snímek se vymaže barvou pozadí |
3 | obnoví se původní obsah snímku |
4–7 | prozatím (téměř třicet let) není definováno |
V dalším příkladu vytvoříme GIF se šesti snímky, přičemž v každém snímku se do animace přidá další barevný čtverec:
Obrázek 22: Výchozí parametr disposal a jeho vliv na výslednou animaci.
Přitom žádným způsobem nenastavujeme metodu přechodu mezi snímky:
package main import ( "image" "image/color" "image/draw" "image/gif" "os" ) func CreateImage(width int, height int, imageIndex int) *image.Paletted { var palette = []color.Color{ color.RGBA{150, 150, 150, 255}, color.RGBA{250, 0, 0, 255}, } img := image.NewPaletted(image.Rect(0, 0, width, height), palette) r := image.Rect(0, 0, width, height) draw.Draw(img, r, &image.Uniform{palette[0]}, image.ZP, draw.Src) r2 := image.Rect(imageIndex*60, 0, imageIndex*60+50, height) draw.Draw(img, r2, &image.Uniform{palette[1]}, image.ZP, draw.Src) return img } func main() { var images []*image.Paletted var delays []int for i := 0; i < 6; i++ { img := CreateImage(350, 50, i) images = append(images, img) delays = append(delays, 100) } outfile, err := os.Create("10.gif") if err != nil { panic(err) } defer outfile.Close() gif.EncodeAll(outfile, &gif.GIF{ Image: images, Delay: delays, }) }
11. Použití nastaveného pozadí GIFu
Příklad ovšem můžeme snadno upravit takovým způsobem, že jednotlivé snímky (kromě prvního) budou menší, než snímky předchozí, což je díky koncepci rámců zcela legální. Následně zvolíme, že se v průběhu animace má snímek na začátku vždy překreslit barvou pozadí:
var disposals []byte for i := 0; i < 6; i++ { ... ... ... disposals = append(disposals, gif.DisposalBackground) }
Způsob vytvoření výsledného GIFu:
gif.EncodeAll(outfile, &gif.GIF{ Image: images, Delay: delays, Disposal: disposals, BackgroundIndex: 0, })
Výsledná animace:
Obrázek 23: Parametr disposal=DisposalBackground a jeho vliv na výslednou animaci.
Jaký má tato programová komplikace význam? Výsledný soubor s animací je výrazně menší – proti původním 1908 bajtům jsme dosáhli velikosti pouhých 734 bajtů.
Samozřejmě nezapomeneme ani na výpis celého zdrojového kódu příkladu:
package main import ( "image" "image/color" "image/draw" "image/gif" "os" ) func CreateImage(width int, height int, imageIndex int) *image.Paletted { var palette = []color.Color{ color.RGBA{150, 150, 150, 0}, color.RGBA{250, 0, 0, 255}, } if imageIndex == 0 { img := image.NewPaletted(image.Rect(0, 0, width, height), palette) r := image.Rect(0, 0, width, height) draw.Draw(img, r, &image.Uniform{palette[0]}, image.ZP, draw.Src) r2 := image.Rect(0, 0, 50, height) draw.Draw(img, r2, &image.Uniform{palette[1]}, image.ZP, draw.Src) return img } else { img := image.NewPaletted(image.Rect(imageIndex*60, 0, imageIndex*60+50, height), palette) r2 := image.Rect(imageIndex*60, 0, imageIndex*60+50, height) draw.Draw(img, r2, &image.Uniform{palette[1]}, image.ZP, draw.Src) return img } } func main() { var images []*image.Paletted var delays []int var disposals []byte for i := 0; i < 6; i++ { img := CreateImage(350, 50, i) images = append(images, img) delays = append(delays, 100) disposals = append(disposals, gif.DisposalBackground) } outfile, err := os.Create("11.gif") if err != nil { panic(err) } defer outfile.Close() gif.EncodeAll(outfile, &gif.GIF{ Image: images, Delay: delays, Disposal: disposals, BackgroundIndex: 0, }) }
12. Přechod na další snímek bez smazání snímku předchozího
A konečně se podívejme, co se stane v případě, kdy se oblast s animací nebude mezi snímky nijak měnit. Nový snímek se tedy vykreslí do (nesmazané) oblasti. Pokud je nový snímek menší, než celá plocha s animací, lze takto prakticky zadarmo dosáhnout zajímavých efektů:
Obrázek 24: Parametr disposal=DisposalNone a jeho vliv na výslednou animaci.
Samozřejmě si opět ukážeme úplný zdrojový kód tohoto příkladu, který nyní vypadá následovně:
package main import ( "image" "image/color" "image/draw" "image/gif" "os" ) func CreateImage(width int, height int, imageIndex int) *image.Paletted { var palette = []color.Color{ color.RGBA{150, 150, 150, 255}, color.RGBA{250, 0, 0, 255}, } if imageIndex == 0 { img := image.NewPaletted(image.Rect(0, 0, width, height), palette) r := image.Rect(0, 0, width, height) draw.Draw(img, r, &image.Uniform{palette[0]}, image.ZP, draw.Src) r2 := image.Rect(0, 0, 50, height) draw.Draw(img, r2, &image.Uniform{palette[1]}, image.ZP, draw.Src) return img } else { img := image.NewPaletted(image.Rect(imageIndex*60, 0, imageIndex*60+50, height), palette) r2 := image.Rect(imageIndex*60, 0, imageIndex*60+50, height) draw.Draw(img, r2, &image.Uniform{palette[1]}, image.ZP, draw.Src) return img } } func main() { var images []*image.Paletted var delays []int var disposals []byte for i := 0; i < 6; i++ { img := CreateImage(350, 50, i) images = append(images, img) delays = append(delays, 100) disposals = append(disposals, gif.DisposalNone) } outfile, err := os.Create("12.gif") if err != nil { panic(err) } defer outfile.Close() gif.EncodeAll(outfile, &gif.GIF{ Image: images, Delay: delays, Disposal: disposals, }) }
13. Vytvoření GIFu s animovaným progress barem
Se znalostmi, které jsme získali v předchozích kapitolách, si nyní můžeme ukázat vytvoření GIFu s animovaným progress barem. Celá animace obsahuje celých 200 snímků a přitom má velikost pouze 6074 bajtů, tedy přibližně pouze 30 bajtů na snímek:
Obrázek 25: Animace progress baru uložená jako obrázek typu GIF.
package main import ( "image" "image/color" "image/draw" "image/gif" "os" ) func CreateImage(width int, height int, imageIndex int) *image.Paletted { var palette = []color.Color{ color.RGBA{50, 50, 50, 255}, color.RGBA{0, 200, 200, 255}, } if imageIndex == 0 { img := image.NewPaletted(image.Rect(0, 0, width, height), palette) r := image.Rect(0, 0, width, height) draw.Draw(img, r, &image.Uniform{palette[0]}, image.ZP, draw.Src) return img } else { img := image.NewPaletted(image.Rect(imageIndex, 0, imageIndex+1, height), palette) r := image.Rect(imageIndex, 0, imageIndex+1, height) draw.Draw(img, r, &image.Uniform{palette[1]}, image.ZP, draw.Src) return img } } func main() { var images []*image.Paletted var delays []int var disposals []byte for i := 0; i < 200; i++ { img := CreateImage(200, 10, i) images = append(images, img) delays = append(delays, 2) disposals = append(disposals, gif.DisposalNone) } outfile, err := os.Create("13.gif") if err != nil { panic(err) } defer outfile.Close() gif.EncodeAll(outfile, &gif.GIF{ Image: images, Delay: delays, Disposal: disposals, }) }
14. Tvorba 2D grafiky s využitím knihovny GG
V další části článku se – prozatím ovšem pouze v rychlosti – seznámíme s některými možnostmi, které nám v oblasti 2D grafiky poskytuje knihovna nazvaná GG. Možnosti poskytované touto knihovnou připomínají Cairo popř. OpenVG a EGL: 2D scéna je vykreslována v immediate (přímém) režimu, přičemž se používá koncept takzvaných cest (path).
Samotná instalace knihovny GG je stejná, jako v případě jiných externích balíčků, tedy:
$ go get -u github.com/fogleman/gg
Po spuštění tohoto příkazu v adresáři, na nějž ukazuje proměnná prostředí GOPATH by měla vzniknout tato relativně složitá adresářová struktura:
├── bin ├── pkg │ └── linux_amd64 │ └── github.com │ └── fogleman └── src ├── github.com │ ├── fogleman │ │ └── gg │ │ └── examples │ └── golang │ └── freetype │ ├── cmd │ │ └── print-glyph-points │ ├── example │ │ ├── capjoin │ │ ├── drawer │ │ ├── freetype │ │ ├── gamma │ │ ├── genbasicfont │ │ ├── raster │ │ ├── round │ │ └── truetype │ ├── licenses │ ├── raster │ ├── testdata │ └── truetype ├── golang.org │ └── x │ └── image │ ├── bmp │ ├── cmd │ │ └── webp-manual-test │ ├── colornames │ ├── draw │ ├── example │ │ └── font │ ├── font │ │ ├── basicfont │ │ ├── gofont │ │ │ ├── gobold │ │ │ ├── gobolditalic │ │ │ ├── goitalic │ │ │ ├── gomedium │ │ │ ├── gomediumitalic │ │ │ ├── gomono │ │ │ ├── gomonobold │ │ │ ├── gomonobolditalic │ │ │ ├── gomonoitalic │ │ │ ├── goregular │ │ │ ├── gosmallcaps │ │ │ ├── gosmallcapsitalic │ │ │ └── ttfs │ │ ├── inconsolata │ │ ├── opentype │ │ ├── plan9font │ │ ├── sfnt │ │ └── testdata │ │ └── fixed │ ├── math │ │ ├── f32 │ │ ├── f64 │ │ └── fixed │ ├── riff │ ├── testdata │ ├── tiff │ │ └── lzw │ ├── vector │ ├── vp8 │ ├── vp8l │ └── webp
15. Parametry kreslení zapamatované v kontextu
Podívejme se nejprve na velmi jednoduchý příklad, který po svém spuštění vytvoří rastrový obrázek obsahující kružnici o poloměru 100 délkových jednotek. Ve výchozím nastavení odpovídá 100 délkových jednotek jednomu stu pixelů:
package main import "github.com/fogleman/gg" func main() { const width = 320 const height = 240 dc := gg.NewContext(width, height) dc.SetRGB(0.2, 0.0, 0.8) dc.DrawCircle(width/2, height/2, 100) dc.Fill() dc.SavePNG("14.png") }
Obrázek 26: Kružnice vytvořená předchozím příkladem.
Povšimněte si, že parametry vykreslování (zde barva) se stává součástí takzvaného kontextu (context). Tyto parametry se použijí až tehdy, kdy zavoláme metodu Stroke nebo Fill. To znamená, že nezáleží na tom, zda nejprve vykreslíme kružnici a poté nastavíme její barvu či tyto dvě operace prohodíme:
package main import "github.com/fogleman/gg" func main() { const width = 320 const height = 240 dc := gg.NewContext(width, height) dc.DrawCircle(width/2, height/2, 100) dc.SetRGB(0.2, 0.0, 0.8) dc.Fill() dc.SavePNG("15.png") }
Obrázek 27: Naprosto stejná kružnice vytvořená předchozím příkladem.
16. Kreslení otevřených tvarů (úseček) pomocí příkazu Stroke
V případě, že budeme potřebovat vykreslit dvě úsečky, každou s odlišnou barvou, je tedy nutné postupovat následovně: použijeme vždy trojici metod SetRGB, DrawLine a Stroke. Pokud v prvním případě na Stroke zapomeneme, bude sice úsečka vykreslena, ale druhou specifikovanou barvou:
package main import "github.com/fogleman/gg" func main() { const width = 320 const height = 240 dc := gg.NewContext(width, height) dc.SetRGB(0.2, 0.0, 0.8) dc.DrawLine(10, 10, width-10, height-10) dc.Stroke() dc.SetRGB(0.8, 0.0, 0.2) dc.DrawLine(10, height-10, width-10, 10) dc.Stroke() dc.SavePNG("16.png") }
Obrázek 28: Dvojice úseček na průhledném pozadí, každá je vykreslena odlišnou barvou (jako samostatná cesta).
17. Vyplnění uzavřené křivky příkazem Fill
Velmi často se setkáme s potřebou vykreslit uzavřené tvary. K tomuto účelu slouží metoda nazvaná Fill. My ji dnes konkrétně použijeme pro vyplnění pozadí obrázku konstantní barvou. Pro jednoduchost bude použita bílá barva:
package main import "github.com/fogleman/gg" func main() { const width = 320 const height = 240 dc := gg.NewContext(width, height) dc.DrawRectangle(0, 0, width, height) dc.SetRGB(1.0, 1.0, 1.0) dc.Fill() dc.DrawLine(10, 10, width-10, height-10) dc.SetRGB(0.2, 0.0, 0.8) dc.Stroke() dc.DrawLine(10, height-10, width-10, 10) dc.SetRGB(0.8, 0.0, 0.2) dc.Stroke() dc.SavePNG("17.png") }
Obrázek 29: Dvojice úseček na bílém pozadí, každá je vykreslena odlišnou barvou (jako samostatná cesta).
18. Nastavení základních parametrů cest: průhlednost, šířka tahu a styl vykreslení
Nastavit je možné i další parametry vykreslování. Ty si podrobně popíšeme příště, takže následující příklady považujte za ukázku některých možností nabízených knihovnou GG.
Změna průhlednosti úseček
package main import "github.com/fogleman/gg" func main() { const width = 320 const height = 240 dc := gg.NewContext(width, height) dc.DrawRectangle(0, 0, width, height) dc.SetRGB(1.0, 1.0, 1.0) dc.Fill() for i := 0; i < 256; i += 8 { alpha := float64(i) / 256.0 dc.SetRGBA(0.0, 0.0, 0.0, alpha) x := float64(i + 32) dc.DrawLine(x, 20, x, height-20) dc.Stroke() } dc.SavePNG("18.png") }
Obrázek 30: Postupná změna průhlednosti úseček.
Změna šířky pera při kreslení úseček
package main import "github.com/fogleman/gg" func main() { const width = 320 const height = 240 dc := gg.NewContext(width, height) dc.DrawRectangle(0, 0, width, height) dc.SetRGB(1.0, 1.0, 1.0) dc.Fill() dc.SetRGBA(0.0, 0.0, 0.0, 1) for i := 0; i < 256; i += 16 { width := float64(i) / 20 dc.SetLineWidth(width) x := float64(i + 32) dc.DrawLine(x, 20, x, height-20) dc.Stroke() } dc.SavePNG("19.png") }
Obrázek 31: Postupná změna šířky pera při kreslení úseček.
Specifikace vzorku vykreslování
package main import "github.com/fogleman/gg" func main() { const width = 320 const height = 240 dc := gg.NewContext(width, height) dc.DrawRectangle(0, 0, width, height) dc.SetRGB(1.0, 1.0, 1.0) dc.Fill() dc.SetRGBA(0.0, 0.0, 0.0, 1) dc.DrawLine(32, 20, 288, 20) dc.Stroke() dc.SetDash(10) dc.DrawLine(32, 40, 288, 40) dc.Stroke() dc.SetDash(10, 10) dc.DrawLine(32, 60, 288, 60) dc.Stroke() dc.SetDash(10, 5) dc.DrawLine(32, 80, 288, 80) dc.Stroke() dc.SetDash(10, 5, 2, 5) dc.DrawLine(32, 100, 288, 100) dc.Stroke() dc.SetLineWidth(4.0) dc.SetLineCap(gg.LineCapButt) dc.DrawLine(32, 140, 288, 140) dc.Stroke() dc.SetDash(10) dc.DrawLine(32, 160, 288, 160) dc.Stroke() dc.SetDash(10, 10) dc.DrawLine(32, 180, 288, 180) dc.Stroke() dc.SetDash(10, 5) dc.DrawLine(32, 200, 288, 200) dc.Stroke() dc.SetDash(10, 5, 2, 5) dc.DrawLine(32, 220, 288, 220) dc.Stroke() dc.SavePNG("20.png") }
Obrázek 32: Úsečky vytvořené s různými vzorky (čárkovaná a čerchovaná čára atd.).
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes popsaných demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně jeden megabajt), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation