Obsah
1. Programovací jazyk Go a počítačová grafika
2. Základní rozhraní pro rastrovou grafiku:
3. Inicializace prázdného rastrového obrázku s jeho uložením na disk
4. Vyplnění všech pixelů rastrového obrázku konstantní barvou
5. Alfa kanál: rozdíl mezi barvovými prostory RGBA a NRGBA
6. Interní struktura záznamu s rastrovým obrázkem
7. Přímý přístup k poli s pixely rastrového obrázku
8. Třetí varianta využívající řez polem
9. Struktury Point a Rectangle
10. Operace, které jsou strukturami Point a Rectangle podporovány
11. Barvové prostory RGBA a NRGBA
13. Význam složky K v barvovém prostoru CMYK
15. Trik – vyplnění obrázku konstantní barvou s využitím obrazových operací
17. Vykreslení složitějších tvarů do rastrových obrázků
18. Implementace Bresenhamova algoritmu pro vykreslování úseček
19. Repositář s demonstračními příklady
1. Programovací jazyk Go a počítačová grafika
Programovací jazyk Go se sice primárně (minimálně v současnosti) používá pro tvorbu síťových aplikRGBA, popř. utilit a služeb, v nichž je možné využít možností poskytovaných gorutinami a kanály, ovšem můžeme ho poměrně elegantně použít i při tvorbě (renderingu) a úpravách rastrových obrázků. Jazyk Go již ve své základní knihovně totiž obsahuje balíček nazvaný image, který je možné pro tyto účely využít. Navíc mají vývojáři k dispozici i podbalíčky určené pro kódování a dekódování rastrových obrázků z/do formátů GIF (Graphics Interchange Format), PNG (Portable Network Graphics) a JPEG (Joint Photographic Experts Group, přesněji však JFIF, JPEG File Interchange Format), tj. přesně těch formátů, které jsou používány na webu.
Podrobnější informace o rastrových formátech GIF, PNG a JPEG již byly (nutno dodat, že v dosti dávné minulosti :-) na Rootu zveřejněny:
- Případ GIF
https://www.root.cz/clanky/pripad-gif/ - Pravda a mýty o GIFu
https://www.root.cz/clanky/pravda-a-myty-o-gifu/ - Anatomie grafického formátu GIF
https://www.root.cz/clanky/anatomie-grafickeho-formatu-gif/ - GIF: animace a konkurence
https://www.root.cz/clanky/gif-animace-a-konkurence/ - PNG is Not GIF
https://www.root.cz/clanky/png-is-not-gif/ - Anatomie grafického formátu PNG
https://www.root.cz/clanky/anatomie-grafickeho-formatu-png/ - PNG – bity, byty, chunky
https://www.root.cz/clanky/png-bity-byty-chunky/ - Řádkové filtry v PNG
https://www.root.cz/clanky/radkove-filtry-v-png/ - Nepovinné chunky v PNG a kontrola pomocí CRC
https://www.root.cz/clanky/nepovinne-chunky-v-png-a-kontrola-pomoci-crc/ - Finišujeme s PNG – Textové metainformace a kalibrační data
https://www.root.cz/clanky/finisujeme-s-png-textove-metainformace-a-kalibracni-data/ - JPEG – král rastrových grafických formátů?
https://www.root.cz/clanky/jpeg-kral-rastrovych-grafickych-formatu/ - Ztrátová komprese obrazových dat pomocí JPEG
https://www.root.cz/clanky/ztratova-komprese-obrazovych-dat-pomoci-jpeg/ - Programujeme JPEG: transformace a podvzorkování barev
https://www.root.cz/clanky/programujeme-jpeg-transformace-a-podvzorkovani-barev/ - Programujeme JPEG: diskrétní kosinová transformace (DCT)
https://www.root.cz/clanky/programujeme-jpeg-diskretni-kosinova-transformace-dct/ - Programujeme JPEG: Kvantizace DCT koeficientů
https://www.root.cz/clanky/programujeme-jpeg-kvantizace-dct-koeficientu/ - Programujeme JPEG: Huffmanovo kódování kvantovaných DCT složek
https://www.root.cz/clanky/programujeme-jpeg-huffmanovo-kodovani-kvantovanych-dct-slozek/ - Programujeme JPEG: Interní struktura souborů typu JFIF/JPEG
https://www.root.cz/clanky/programujeme-jpeg-interni-struktura-souboru-typu-jfifjpeg/ - Programujeme JPEG: Načtení informací ze souborů typu JFIF/JPEG
https://www.root.cz/clanky/programujeme-jpeg-nacteni-informaci-ze-souboru-typu-jfifjpeg/ - Programujeme JPEG: Progresivní JPEG a informace EXIF
https://www.root.cz/clanky/programujeme-jpeg-progresivni-jpeg-a-informace-exif/
Ve druhé části článku si popíšeme i základní možnosti poskytované module draw, který umožňuje provádění rastrových operací nad dvojicí či trojicí obrázků. Ovšem tento modul není určen pro kreslení složitějších grafických tvarů – k tomuto účelu je nutné použít nějakou externí knihovnu (o nichž se samozřejmě taktéž zmíníme).
2. Inicializace prázdného rastrového obrázku s jeho uložením na disk
V demonstračních příkladech popsaných v navazujících kapitolách budeme používat takové reprezentace rastrových obrázků, které jsou plně kompatibilní se základní knihovnou (přesněji řečeno s balíčky základní knihovny) programovacího jazyka Go. Při použití této knihovny může být samotný rastrový obrázek reprezentován různými datovými strukturami, například RGBA, NRGBA, CMYK, Gray atd. – podle toho, jaký barvový model je použit. Ovšem nezávisle na tom, o jakou konkrétní datovou strukturu (a tím pádem o jaký barvový model) se jedná, bude vždy implementováno rozhraní nazvané Image, pro nějž jsou v balíčku image předepsány pouhé tři metody:
type Image interface { ColorModel() color.Model Bounds() Rectangle At(x, y int) color.Color }
První z těchto metod vrací barvový model použitý pro reprezentaci barev jednotlivých pixelů v obrázku. Druhá metoda vrací obdélník (rectangle), kterým se určují okraje (meze) obrázku, přičemž je nutné upozornit na to, že levý horní roh obdélníku obecně nemusí začínat na souřadnicích [0, 0], což se týká například jednotlivých snímků použitých v grafickém formátu GIF ve chvíli, kdy jsou použity animace (jednotlivé rozdílové snímky jsou obecně menší, než celý obrázek). A konečně poslední metoda vrací barvu pixelu na souřadnicích [x, y]. Tato metoda je pochopitelně velmi pomalá, zejména v porovnání s přímým přístupem k polím bajtů s obsahem jednotlivých pixelů, ovšem na druhou stranu je tato metoda zcela univerzální a programátor se nemusí zabývat tím, jakým způsobem je vlastně bitmapa obrázku interně reprezentována.
$ godoc image Image
Dalším důležitým rozhraním, s nímž se velmi často setkáme, je rozhraní nazvané Color, které je deklarováno v balíčku image/color. Toto rozhraní předepisuje jedinou metodu RGBA, která vrací barvu ve formátu čtveřice R (red), G (green), B (blue) a A (alpha, průhlednost):
type Color interface { RGBA() (r, g, b, a uint32) }
Zajímavé je, že všechny tři barvové složky i průhlednost jsou vráceny jako hodnoty typu uint32, i když se v běžných rastrových formátech každá složka ukládá do jediného bajtu. Je tomu tak z toho důvodu, že barvové složky jsou vynásobeny hodnotou alfa (průhledností) a jejich hodnoty tedy leží v rozsahu 0 až 65025. Pokud se na dva obrázky aplikují rastrové operace popsané ve druhé části článku, je zaručeno, že díky rozsahu datového typu uint32 (0 až 232-1) nikdy nedojde k přetečení hodnot barvových složek.
3. Inicializace prázdného rastrového obrázku s jeho uložením na disk
V praktické části článku se nejprve podívejme na způsob inicializace prázdného rastrového obrázku. Rozlišení obrázku, přesněji řečeno počet obrazových řádků a počet pixelů na každém obrazovém řádku, bude pro jednoduchost uloženo v konstantách se jmény width a height:
const width = 256 const height = 256
Dále budeme muset vytvořit a inicializovat datovou strukturu typu Rectangle, v níž bude uložen obdélník určující rozměry obrázku i jeho umístění v rámci roviny. Pro jednoduchost bude levý horní roh obdélníku ležet na souřadnicích [0, 0] a pravý dolní roh na souřadnicích [width, heigh]. Struktura typu Rectangle sice obsahuje dva prvky typu Point, takže je inicializace takové struktury relativně složitá, ovšem namísto toho můžeme použít pomocnou funkci Rect(), které se předají čtyři celočíselné hodnoty – souřadnice levého horního rohu a souřadnice pravého dolního rohu. Inicializace obdélníku tedy bude vypadat takto:
r := image.Rect(0, 0, width, height))
Nyní již máme připraveno vše potřebné pro vytvoření prázdného obrázku, resp. přesněji řečeno takového obrázku, v němž budou jednotlivé pixely obsahovat barvu (0, 0, 0, 0), protože kromě barvových složek R, G, B musíme správně nastavit i průhlednost, neboli alfa kanál. Prázdný obrázek vytvoříme jednoduše:
img := image.NewRGBA(r)
Prozatím sice neumíme obrázek přímo zobrazit na displeji, ale dokážeme ho velmi snadno uložit například do souboru typu PNG. Nejprve otevřeme soubor pojmenovaný „01.png“ pro zápis a poté využijeme možnosti poskytované balíčkem image/png pro zakódování obrázku do formátu PNG::
outfile, err := os.Create("01.png") if err != nil { panic(err) } defer outfile.Close() png.Encode(outfile, img)
To je vše – úplný zdrojový kód dnešního prvního demonstračního příkladu naleznete na adrese https://github.com/tisnik/go-fedora/blob/master/article14/01_empty_image.go:
package main import ( "image" "image/png" "os" ) const width = 256 const height = 256 func main() { img := image.NewRGBA(image.Rect(0, 0, width, height)) outfile, err := os.Create("01.png") if err != nil { panic(err) } defer outfile.Close() png.Encode(outfile, img) }
Povšimněte si, že obrázek obsahuje bitmapu o rozměrech 256×256 pixelů, které jsou průhledné (a mají černou barvu, to však kvůli průhlednosti nevidíme). To je výchozí chování datového typu NewRGBA.
![](https://i.iinfo.cz/images/410/golang-14-1.png)
Obrázek 1: Výsledek prvního demonstračního příkladu. Všechny pixely jsou ve skutečnosti průhledné – šachovnice je výsledkem zobrazení bitmapy v prohlížeči obrázků.
Alternativně samozřejmě můžeme příklad přepsat takovým způsobem, aby se datová struktura Rectangle vytvořila přímo „konstruktorem“ struktur (zápis se složenými závorkami):
package main import ( "image" "image/png" "os" ) const width = 256 const height = 256 func main() { rect := image.Rectangle{image.Point{0, 0}, image.Point{width, height}} img := image.NewNRGBA(rect) outfile, err := os.Create("02.png") if err != nil { panic(err) } defer outfile.Close() png.Encode(outfile, img) }
4. Vyplnění všech pixelů rastrového obrázku konstantní barvou
První manipulací s obsahem rastrového obrázku, s níž se dnes seznámíme, bude vyplnění všech pixelů bitmapy nějakou barvou. Prozatím budeme používat barvový model RGBA. Pro tento datový typ existuje metoda určená pro změnu barvy pixelu. Tato metoda se jmenuje příznačně SetRGBA a její hlavička vypadá následovně:
func (p *RGBA) SetRGBA(x, y int, c color.RGBA)
Této metodě se předávají tři parametry:
- x-ová souřadnice pixelu
- y-ová souřadnice pixelu
- barva pixelu, což je struktura typu color.RGBA (color je neúplné jméno balíčku image/color), v němž je každá barvová komponenta reprezentována jediným bajtem (tak, jak jsme zvyklí z většiny grafických knihoven)
Tato struktura je deklarována následujícím způsobem:
type RGBA struct { R, G, B, A uint8 }
Strukturu RGBA naplníme naprosto stejným způsobem, jako jakýkoli jiný záznam. Nastavíme přitom čistě zelenou barvu, která bude neprůhledná (poslední hodnota 255 určuje neprůhlednost pixelů):
c := color.RGBA{0, 255, 0, 255}
Jakmile máme připravenou barvu pixelů, můžeme jí vyplnit všechny pixely tvořící rastrový obrázek:
for x := 0; x < width; x++ { for y := 0; y < height; y++ { img.SetRGBA(x, y, c) } }
Úplný zdrojový kód tohoto příkladu vypadá následovně:
package main import ( "image" "image/color" "image/png" "os" ) const width = 256 const height = 256 func main() { img := image.NewRGBA(image.Rect(0, 0, width, height)) outfile, err := os.Create("03.png") if err != nil { panic(err) } defer outfile.Close() c := color.RGBA{0, 255, 0, 255} for x := 0; x < width; x++ { for y := 0; y < height; y++ { img.SetRGBA(x, y, c) } } png.Encode(outfile, img) }
![](https://i.iinfo.cz/images/410/golang-14-2.png)
Obrázek 2: Výsledek běhu demonstračního příkladu, v němž jsou pixely vybarveny metodou SetRGBA.
I tento příklad je možné nepatrně modifikovat, a to takovým způsobem, že se namísto metody SetRGBA, která je přímo navázána na barvový prostor RGBA, použije univerzální metoda Set, jíž se opět předají souřadnice pixelu a jeho barva. Výsledkem je program, v němž je změna barvového prostoru přeci jen snazší:
package main import ( "image" "image/color" "image/png" "os" ) const width = 256 const height = 256 func main() { img := image.NewRGBA(image.Rect(0, 0, width, height)) outfile, err := os.Create("04.png") if err != nil { panic(err) } defer outfile.Close() c := color.RGBA{0, 255, 0, 255} for x := 0; x < width; x++ { for y := 0; y < height; y++ { img.Set(x, y, c) } } png.Encode(outfile, img) }
5. Alfa kanál: rozdíl mezi barvovými prostory RGBA a NRGBA
Nyní si vyzkoušejme zdrojový kód předchozích příkladů nepatrně upravit, a to takovým způsobem, že barva všech pixelů sice bude stále čistě zelená, ovšem průhlednost se bude měnit v horizontálním směru od 0 (zcela průhledná) do 255 (zcela neprůhledná). Barva tedy bude vytvářena uvnitř vnější programové smyčky:
for x := 0; x < width; x++ { alpha := byte(x) c := color.RGBA{0, 255, 0, alpha} for y := 0; y < height; y++ { img.SetRGBA(x, y, c) } }
Opět si samozřejmě ukážeme, jak byla tato změna zakomponována do úplného demonstračního příkladu:
package main import ( "image" "image/color" "image/png" "os" ) const width = 256 const height = 256 func main() { img := image.NewRGBA(image.Rect(0, 0, width, height)) outfile, err := os.Create("05.png") if err != nil { panic(err) } defer outfile.Close() for x := 0; x < width; x++ { alpha := byte(x) c := color.RGBA{0, 255, 0, alpha} for y := 0; y < height; y++ { img.SetRGBA(x, y, c) } } png.Encode(outfile, img) }
Pokud se však podíváte na vytvořený rastrový obrázek, je patrné, že neodpovídá očekávanému výsledku:
![](https://i.iinfo.cz/images/410/golang-14-3.png)
Obrázek 3: Výsledek činnosti předchozího příkladu. Povšimněte si, že průhlednost – oproti očekávání – neroste lineárně od levého okraje k okraji pravému.
Proč tomu tak je? V barvovém prostoru RGBA (přesněji řečeno ve verzi podporované knihovnami jazyka Go) jsou jednotlivé barvové složky (R, G, B) přednásobeny hodnotou uloženou do alfa kanálu, což například umožňuje efektivní provádění operace blendingu (zjednodušeně řečeno – násobení se provede pouze jednou při změně hodnot pixelů, ovšem nikoli při provádění blendingu). Ovšem kvůli přednásobení dvou hodnot v rozsahu 0..255 dochází k přetečení přes rozsah typu byte/uint8, což je v našem případě patrné na hodnotách zelené barvové složky (další dvě složky jsou nulové, takže tam při přednásobení k žádnému přetečení samozřejmě nedochází).
Řešení tohoto problému je ve skutečnosti velmi snadné – namísto barvového prostoru RGBA použijte barvový prostor NRGBA, což vyžaduje jen nepatrné změny ve zdrojovém kódu:
package main import ( "image" "image/color" "image/png" "os" ) const width = 256 const height = 256 func main() { img := image.NewNRGBA(image.Rect(0, 0, width, height)) outfile, err := os.Create("06.png") if err != nil { panic(err) } defer outfile.Close() for x := 0; x < width; x++ { alpha := byte(x) c := color.NRGBA{0, 255, 0, alpha} for y := 0; y < height; y++ { img.SetNRGBA(x, y, c) } } png.Encode(outfile, img) }
Výsledek již bude v tuto chvíli v pořádku, o čemž se můžeme snadno přesvědčit:
![](https://i.iinfo.cz/images/410/golang-14-4.png)
Obrázek 4: Upravený příklad využívající barvový model NRGBA již dává korektní výsledky.
Jen pro úplnost si ukažme, jak vypadá stejný příklad, ovšem využívající metodu Set pro změnu barev pixelů v rastrovém obrázku:
package main import ( "image" "image/color" "image/png" "os" ) const width = 256 const height = 256 func main() { img := image.NewNRGBA(image.Rect(0, 0, width, height)) outfile, err := os.Create("07.png") if err != nil { panic(err) } defer outfile.Close() for x := 0; x < width; x++ { alpha := byte(x) c := color.NRGBA{0, 255, 0, alpha} for y := 0; y < height; y++ { img.Set(x, y, c) } } png.Encode(outfile, img) }
6. Interní struktura záznamu s rastrovým obrázkem
Interní struktura záznamu, který nese informace o rastrovém obrázku, nejsou ve skutečnosti nijak tajné. Jedná se o běžný záznam s viditelnými prvky (jsou pojmenovány s velkým písmenem na začátku). Pro obrázky využívající barvový prostor RGBA vypadá záznam následovně:
type RGBA struct { Pix []uint8 Stride int Rect Rectangle }
Kupodivu není žádný rozdíl mezi obrázky s barvovým modelem RGBA a jinými barvovými modely. Jen pro představu se podívejme na prvky záznamu nesoucího informace o obrázcích s barvovým prostorem NRGBA:
type NRGBA struct { Pix []uint8 Stride int Rect Rectangle
Či RGBA64, v němž jsou barvové složky reprezentovány 16bitovými hodnotami:
type RGBA64 struct { Pix []uint8 Stride int Rect Rectangle }
Co to pro nás znamená z praktického hlediska? Implementace některých nízkoúrovňových operací může být unifikována a nemusíme se starat o to, jak jsou vlastně interpretovány barvy jednotlivých pixelů.
Jednotlivé prvky struktury mají tento význam:
Prvek | Stručný popis |
---|---|
Pix | hodnoty jednotlivých pixelů uložené za sebou (řádky shora dolů, pixely zleva doprava) |
Stride | offset reprezentovaný v bajtech mezi dvěma pixely, které leží pod sebou |
Rect | obdélník určující pozici a rozměry obrázku v rámci 2D roviny |
Prvek Stride je poměrně důležitý, protože nám umožňuje relativně snadnou interpretaci pole bajtů Pix tak, aby byl umožněn „2D přístup“ k jednotlivým pixelům. Pokud například budeme mít obrázek s rozlišením 1024×768 pixelů a použijeme barvový model RGBA, může být hodnota Stride nastavena na 1024×4=4096, popř. na vyšší hodnotu v případě, že mezi obrazovými řádky budou umístěny výplňové bajty (což se teoreticky může stát, například pro snadnější vykreslování, použití obrázku jako textury atd. atd.).
V dalším demonstračním příkladu je ukázáno, jak lze přistupovat k jednotlivým prvkům struktury typu NRGBA:
package main import ( "image" ) const width = 256 const height = 256 func main() { img := image.NewNRGBA(image.Rect(0, 0, width, height)) println("Stride: ", img.Stride) println("[]byte: ", len(img.Pix)) r := img.Rect println("Rectangle:") println(" point 1: ", r.Min.X, r.Min.Y) println(" point 2: ", r.Max.X, r.Max.Y) }
Výsledky jsou následující:
Stride: 1024 []byte: 262144 Rectangle: point 1: 0 0 point 2: 256 256
Jejich význam je tento:
- Stride obsahuje hodnotu 1024, což odpovídá horizontálnímu rozlišení 256 pixelů a čtyřem bajtům na pixel.
- Celková velikost pole s pixely je také snadno odvoditelná: 256×256×4 se skutečně rovná 262144.
- U obdélníku s rozměry a umístěním obrázku je patrné, že souřadnice pravého dolního rohu již leží mimo vlastní obrázek.
7. Přímý přístup k poli s pixely rastrového obrázku
Znalost interní struktury záznamů typu RGBA, NRGBA atd. nám umožní přímý přístup k poli Pix, které obsahuje hodnoty jednotlivých pixelů rastrového obrázku. V následujícím demonstračním příkladu se obrázek vyplní konstantní barvou, u níž se však postupně (ve směru x-ové osy) mění hodnota průhlednosti. Ve vnější programové smyčce vypočteme index prvního bajtu na y-ovém obrazovém řádku:
index := img.Stride * y
Ve vnitřní smyčce se postupně vyplní (nastaví) barvy všech pixelů na jednom obrazovém řádku – každý pixel je přitom reprezentován čtyřmi bajty:
for x := 0; x < width; x++ { img.Pix[index] = 0 index++ img.Pix[index] = 0 index++ img.Pix[index] = 255 index++ img.Pix[index] = byte(x) index++ }
Výsledkem bude tento obrázek:
![](https://i.iinfo.cz/images/410/golang-14-5.png)
Obrázek 5: Výsledek získaný předchozím demonstračním příkladem.
Následuje výpis úplného zdrojového kódu tohoto demonstračního příkladu:
package main import ( "image" "image/png" "os" ) const width = 256 const height = 256 func main() { img := image.NewNRGBA(image.Rect(0, 0, width, height)) outfile, err := os.Create("06.png") if err != nil { panic(err) } defer outfile.Close() for y := 0; y < height; y++ { index := img.Stride * y for x := 0; x < width; x++ { img.Pix[index] = 0 index++ img.Pix[index] = 0 index++ img.Pix[index] = 255 index++ img.Pix[index] = byte(x) index++ } } png.Encode(outfile, img) }
Pokud vám připadá výpočet indexu prvního pixelu na určitém obrazovém řádku příliš nízkoúrovňový, je možné řádek:
index := img.Stride * y
nahradit za:
index := img.PixOffset(0, y)
Zbytek příkladu by mohl zůstat nezměněný:
package main import ( "image" "image/png" "os" ) const width = 256 const height = 256 func main() { img := image.NewNRGBA(image.Rect(0, 0, width, height)) outfile, err := os.Create("07.png") if err != nil { panic(err) } defer outfile.Close() for y := 0; y < height; y++ { index := img.PixOffset(0, y) for x := 0; x < width; x++ { img.Pix[index] = 0 index++ img.Pix[index] = 0 index++ img.Pix[index] = 255 index++ img.Pix[index] = byte(x) index++ } } png.Encode(outfile, img) }
![](https://i.iinfo.cz/images/410/golang-14-7.png)
Obrázek 6: Výsledek získaný upraveným demonstračním příkladem.
8. Třetí varianta využívající řez polem
Z předchozích článků s popisem vlastností programovacího jazyka Go již víme, že nemůžeme použít ukazatelovou aritmetiku, takže například není možné získat ukazatel na první pixel na řádku a poté pouze zvyšovat hodnotu tohoto ukazatele. Ovšem získat můžeme řez polem, takže se možnostem ukazatelové aritmetiky lze alespoň nepatrně přiblížit. V dalším příkladu se pro každý obrazový řádek získá řez, který začíná prvním pixelem na tomto řádku:
scanline := img.Pix[img.Stride*y:]
V interní smyčce se pak použije tento řez prakticky stejným způsobem, jako by se jednalo o pole:
for x := 0; x < width; x++ { scanline[i] = 0 i++ scanline[i] = byte(y) i++ scanline[i] = 255 i++ scanline[i] = byte(x) i++ }
Podívejme se na úplný zdrojový kód tohoto příkladu:
package main import ( "image" "image/png" "os" ) const width = 256 const height = 256 func main() { img := image.NewNRGBA(image.Rect(0, 0, width, height)) outfile, err := os.Create("08.png") if err != nil { panic(err) } defer outfile.Close() for y := 0; y < height; y++ { scanline := img.Pix[img.Stride*y:] i := 0 for x := 0; x < width; x++ { scanline[i] = 0 i++ scanline[i] = byte(y) i++ scanline[i] = 255 i++ scanline[i] = byte(x) i++ } } png.Encode(outfile, img) }
![](https://i.iinfo.cz/images/410/golang-14-6.png)
Obrázek 7: Výsledek získaný upraveným demonstračním příkladem.
9. Struktury Point a Rectangle
Při používání balíčků image a image/draw se velmi často setkáme se strukturami pojmenovanými Point a Rectangle. Struktura Point, jak již ostatně její název napovídá, obsahuje informaci o bodu v rovině, přičemž souřadnice bodu jsou reprezentovány celými čísly se znaménkem:
type Point struct { X, Y int }
Naproti tomu struktura Rectangle slouží ke specifikaci osově orientovaného obdélníka, u něhož je nutné si pamatovat dva protilehlé body. Tyto body jsou reprezentovány výše zmíněnými strukturami Point:
type Rectangle struct { Min, Max Point }
Tyto struktury samozřejmě můžeme inicializovat přímo, jako jakoukoli jinou datovou strukturu v Go (povšimněte si použití složených závorek):
point2 := image.Point{10, 10} rectangle2 := image.Rectangle{image.Point{0, 0}, image.Point{320, 240}}
Ovšem můžeme – a děje se to velmi často – použít i konstruktory Pt a Rect, které se volají jako běžné funkce:
point1 := image.Pt(10, 10) rectangle1 := image.Rect(0, 0, 320, 240)
Právě s tímto přístupem při konstrukci struktur Point a Rectangle se setkáme nejčastěji:
package main import ( "image" ) func main() { point1 := image.Pt(10, 10) point2 := image.Point{10, 10} rectangle1 := image.Rect(0, 0, 320, 240) rectangle2 := image.Rectangle{image.Point{0, 0}, image.Point{320, 240}} println(point1.String()) println(point2.String()) println(rectangle1.String()) println(rectangle2.String()) }
Výsledek předchozího příkladu ukazuje ekvivalenci mezi přímou inicializací struktur a použitím konstruktorů:
(10,10) (10,10) (0,0)-(320,240) (0,0)-(320,240)
10. Operace, které jsou strukturami Point a Rectangle podporovány
V balíčku image taktéž existuje několik metod určených pro datové struktury Point a Rectangle. Jedná se zejména o test, zda nějaký bod leží v obdélníku či mimo něj:
point1 := image.Pt(10, 10) rectangle1 := image.Rect(0, 0, 200, 200) println(point1.In(rectangle1))
Dále lze zjistit, zda se dva obdélníky překrývají či nikoli:
rectangle1 := image.Rect(0, 0, 200, 200) rectangle2 := image.Rect(100, 100, 300, 300) rectangle3 := image.Rect(300, 300, 400, 400) println(rectangle1.Overlaps(rectangle2)) println(rectangle1.Overlaps(rectangle3))
A konečně existují operace pro sjednocení dvou obdélníků (vznikne nový obdélník obsahující oba zdrojové obdélníky) a pro průnik dvou obdélníků (výsledkem je nový obdélník):
rectangle1 := image.Rect(0, 0, 200, 200) rectangle2 := image.Rect(100, 100, 300, 300) rectangle3 := image.Rect(300, 300, 400, 400) println(rectangle1.Union(rectangle2).String()) println(rectangle1.Intersect(rectangle2).String())
Tyto operace jsou ukázány ve třináctém demonstračním příkladu:
package main import ( "image" ) func main() { point1 := image.Pt(10, 10) point2 := image.Pt(1000, 10) rectangle1 := image.Rect(0, 0, 200, 200) rectangle2 := image.Rect(100, 100, 300, 300) rectangle3 := image.Rect(300, 300, 400, 400) println(point1.In(rectangle1)) println(point2.In(rectangle1)) println(rectangle1.Union(rectangle2).String()) println(rectangle1.Intersect(rectangle2).String()) println(rectangle1.Overlaps(rectangle2)) println(rectangle1.Overlaps(rectangle3)) }
Výsledky:
true false (0,0)-(300,300) (100,100)-(200,200) true false
11. Barvové prostory RGBA a NRGBA
S barvovými prostory RGBA a NRGBA jsme se již setkali v předchozích kapitolách. 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í. Pro úplnost je pod tímto odstavcem vypsán zdrojový kód příkladu, který vytvoří obrázek se čtyřmi gradientními přechody:
package main import ( "image" "image/color" "image/png" "os" ) const width = 512 const height = 512 func main() { img := image.NewNRGBA(image.Rect(0, 0, width, height)) outfile, err := os.Create("10.png") if err != nil { panic(err) } defer outfile.Close() 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) } } png.Encode(outfile, img) }
![](https://i.iinfo.cz/images/410/golang-14-8-prev.png)
Obrázek 8: Čtveřice gradientních přechodů, všechny pixely jsou neprůhledné.
12. Barvový prostor CMYK
V předchozích demonstračních příkladech jsme používali barvový prostor RGBA, popř. NRGBA. Tento barvový prostor je v praxi používán velmi často, mj. i proto, že jednotlivé barvové složky R, G, B přímo odpovídají způsobům zobrazení pixelů na displejích (hodnoty barvových složek se sčítají). Ovšem při přípravě barevných obrázků pro tisk je prostor RGBA či NRGBA nevýhodný, neboť barevný soutisk je prováděn zcela odlišnou technikou – odečítáním barev od barvy papíru (každá natištěná barva funguje jako filtr určité části barvového spektra). Z tohoto důvodu se při přípravě grafiky pro tisk používá barvový model CMY (Cyan, Magenta, Yellow) a tiskárny (většinou) obsahují tonery či inkousty právě těchto barev. Z praktických důvodů jsou tyto tři barvy doplněny ještě o barvu černou – vede to jak k ušetření barev při tisku textů a obrázků v odstínech šedi, tak i k možnosti tisku skutečně černou barvou. Výsledkem je barvový model či barvový prostor CMYK (Cyan, Magenta, Yellow, blacK).
I s tímto barvovým prostorem je možné v programovacím jazyku Go pracovat a to prakticky stejným způsobem, jako tomu bylo u modelů RGBA a NRGBA. Obrázek s barvami pixelů reprezentovaných v modelu CMYK je představován strukturou/záznamem se jménem CMYK:
type CMYK struct { Pix []uint8 Stride int Rect Rectangle }
Pro vytvoření nového obrázku, který odpovídá tomuto barvovému modelu, je možné použít konstruktor NewCMYK(), který akceptuje stejné parametry jako nám již známé konstruktory NewRGBA() a NewNRGBA():
img := image.NewCMYK(image.Rect(0, 0, width, height))
Barva v barvovém modelu CMYK je reprezentována strukturou color.CMYK, kterou je možné nastavit zcela běžným způsobem:
c := color.CMYK{c, m, y, k}
Další práce s obrázkem, například jeho vyplnění pixely s různou barvou, je již prakticky stejné, jako ve všech předchozích příkladech. V dalším úryvku kódu vyplníme obrázek o rozlišení 512×512 pixelů čtyřmi podoblastmi, v nichž každé bude jiný barvový přechod (gradient); to vše vypočtené v barvovém prostoru CMYK:
for x := 0; x < 256; x++ { for y := 0; y < 256; y++ { c := color.CMYK{byte(x), 0, 0, 0} img.SetCMYK(x, y, c) c = color.CMYK{0, byte(x), 0, 0} img.SetCMYK(x+256, y, c) c = color.CMYK{0, 0, byte(x), 0} img.SetCMYK(x, y+256, c) c = color.CMYK{0, 0, 0, byte(x)} img.SetCMYK(x+256, y+256, c) } }
13. Význam složky K v barvovém prostoru CMYK
Poslední složka K (blackK) v barvovém prostoru CMYK určuje podíl černé barvy pixelu. Pokud bude tato hodnota nulová, získáme odstíny získané rozdílem mezi složkami cyan, magenta a yellow:
![](https://i.iinfo.cz/images/410/golang-14-9-prev.png)
Obrázek 9: Postupná změna intenzity jednotlivých složek CMYK. V prvních třech oblastech je hodnota složky K nulová.
Výše uvedený obrázek byl získán tímto demonstračním příkladem:
package main import ( "image" "image/color" "image/png" "os" ) const width = 512 const height = 512 func main() { img := image.NewCMYK(image.Rect(0, 0, width, height)) outfile, err := os.Create("11.png") if err != nil { panic(err) } defer outfile.Close() for x := 0; x < 256; x++ { for y := 0; y < 256; y++ { c := color.CMYK{byte(x), 0, 0, 0} img.SetCMYK(x, y, c) c = color.CMYK{0, byte(x), 0, 0} img.SetCMYK(x+256, y, c) c = color.CMYK{0, 0, byte(x), 0} img.SetCMYK(x, y+256, c) c = color.CMYK{0, 0, 0, byte(x)} img.SetCMYK(x+256, y+256, c) } } png.Encode(outfile, img) }
Hodnotu K však můžeme zvýšit, například na 50%, což odpovídá hodnotě 127:
![](https://i.iinfo.cz/images/410/golang-14-10-prev.png)
Obrázek 10: Barevné přechody v modelu CMYK, černá složka je nastavena na hodnotu 50% (127).
Zdrojový kód tohoto příkladu se změní jen nepatrně:
package main import ( "image" "image/color" "image/png" "os" ) const width = 512 const height = 512 func main() { img := image.NewCMYK(image.Rect(0, 0, width, height)) outfile, err := os.Create("12.png") if err != nil { panic(err) } defer outfile.Close() for x := 0; x < 256; x++ { for y := 0; y < 256; y++ { c := color.CMYK{byte(x), 0, 0, 127} img.SetCMYK(x, y, c) c = color.CMYK{0, byte(x), 0, 127} img.SetCMYK(x+256, y, c) c = color.CMYK{0, 0, byte(x), 127} img.SetCMYK(x, y+256, c) c = color.CMYK{0, byte(y), 0, byte(x)} img.SetCMYK(x+256, y+256, c) } } png.Encode(outfile, img) }
Pokud by se hodnota K nastavila na 100% (255), byl by celý obrázek černý, nezávisle na hodnotě dalších tří barevných složek.
14. Balíček image/draw
Dalším balíčkem, s nímž se dnes alespoň ve stručnosti seznámíme, se jmenuje image/draw. Název tohoto balíčku může být možná poněkud matoucí, protože v něm ve skutečnosti nenajdeme funkce pro kreslení nějakých geometrických tvarů či textů do rastrových obrázků. Balíček image/draw totiž obsahuje implementaci operací prováděných mezi dvěma obrázky – tyto operace se někdy nazývají kompozice, protože se zdrojový (source) obrázek pixel po pixelu aplikuje na obrázek cílový (target).
V tomto balíčku nalezneme především datový typ (strukturu, záznam) Drawer:
type Drawer interface { Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point) }
A potom dvojici až překvapivě univerzálních funkcí implementujících rastrové operace aplikované na dvojici či trojici obrázků.
Rastrová operace mezi zdrojovým a cílovým obrázkem:
func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op)
Rastrová operace mezi zdrojovým a cílovým obrázkem s využitím třetího obrázku ve funkci masky:
func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op)
15. Trik – vyplnění obrázku konstantní barvou s využitím obrazových operací
Funkce Draw a DrawMask nám umožňují provádět množství zajímavých a užitečných triků. Například s nimi dokážeme vyplnit obdélníkovou část obrázku konstantní barvou. Provedeme to tak, že vytvoříme obrázek vyplněný konstantní barvou, který má neomezené rozměry. K vytvoření takto zvláštního obrázku slouží konstruktor NewUniform:
func NewUniform(c color.Color) *Uniform
popř. můžeme přímo inicializovat strukturu Uniform:
image.Uniform{nějaká_barva}
Dále použijeme strukturu image.ZP reprezentující bod ležící na souřadnicích [0, 0]:
image.ZP
a zavoláme funkci Draw, kterou překopírujeme obsah obrázku vyplněného konstantní barvou do obrázku cílového. Přitom se provede ořez do zvoleného obdélníka r:
r := image.Rect(x_from, y_from, x_to, y_to) draw.Draw(img, r, &image.Uniform{nějaká_barva}, image.ZP, draw.Src)
Výsledkem bude, že se do obrázku předaného v posledním parametru draw.Src nakopíruje obdélník s konstantní barvou omezený souřadnicemi [x_from, y_from] a [x_to, y_to].
16. Vykreslení šachovnice
Výše uvedeného triku použijeme pro vykreslení šachovnice:
![](https://i.iinfo.cz/images/410/golang-14-11.png)
Obrázek 11: Šachovnice vykreslená demonstračním příkladem.
Budeme postupovat stejně, jak jsme si to ukázali v předchozí kapitole, pouze využijeme dva obrázky vyplněné konstantní barvou. Každý obrázek použije jednu z barev z této malé palety:
palette := make(map[int]color.RGBA, 2) palette[0] = color.RGBA{150, 205, 50, 255} palette[1] = color.RGBA{0, 100, 0, 255}
Následně ve vnitřní smyčce budeme vykreslovat jednotlivá políčka a nezapomeneme přitom přepínat barvu (přesněji index barvy) mezi nulou a jedničkou:
r := image.Rect(x_from, y_from, x_to, y_to) draw.Draw(img, r, &image.Uniform{palette[index_color]}, image.ZP, draw.Src) index_color = 1 - index_color
Úplný zdrojový kód tohoto příkladu vypadá následovně:
package main import ( "image" "image/color" "image/draw" "image/png" "os" ) const width = 256 const height = 256 func main() { img := image.NewCMYK(image.Rect(0, 0, width, height)) outfile, err := os.Create("17.png") if err != nil { panic(err) } defer outfile.Close() palette := make(map[int]color.RGBA, 2) palette[0] = color.RGBA{150, 205, 50, 255} palette[1] = color.RGBA{0, 100, 0, 255} index_color := 0 board_size := 8 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 } png.Encode(outfile, img) }
17. Vykreslení složitějších tvarů do rastrových obrázků
Další geometrické tvary nejsou balíčkem image/draw podporovány, i když samozřejmě můžeme použít nějaký externí balíček, typicky draw2d. Ovšem (re)implementace některých základních algoritmů pro 2D tvary do jazyka Go může být minimálně poučná.
Pro vykreslení úseček bylo vyvinuto několik algoritmů, ovšem nejrychlejší (a v minulosti taktéž nejpoužívanější) je algoritmus navržený Jackem Eltonem Bresenhamem už v roce 1962. Tento algoritmus pro vykreslení úsečky, který je velmi podrobně popsán na Wikipedii, využívá pouze celočíselné operace a současně jsou v něm eliminovány aritmetické operace pro násobení a dělení, což je i dnes poměrně důležité (zejména na výpočetně slabších 32bitových ARMech). Všechny výpočty se tak zjednoduší na aritmetický posun, sčítání, odčítání a podmíněné skoky. Právě díky těmto vlastnostem se Bresenhamův algoritmus stále v některých aplikacích používá, i když v případě požadavků na co nejvyšší kvalitu vykreslování se někdy přechází na pomalejší algoritmy s antialiasingem.
18. Implementace Bresenhamova algoritmu pro vykreslování úseček
Pokud se při vykreslování úseček spokojíme s menší rychlostí celého programu, je možné Bresenhamův algoritmus implementovat poměrně přímočarým způsobem, což je ostatně patrné z výpisu kódu, který naleznete pod tímto odstavcem. Pomocné lokální proměnné sx a sy slouží pro posuny souřadnice vykreslovaného pixelu, čímž bylo možné eliminovat rozepsání tohoto algoritmu pro všech osm oktantů (díky existenci sx a sy se jakoby pohybujeme pouze v prvním oktantu, i když ve skutečnosti může vykreslování probíhat v jiném směru). Pomocné proměnné dx a dy společně s proměnnou err se používají k určení směru vykreslování. Povšimněte si, že vykreslování vždy začíná v prvním vrcholu [x1, y1] a končí přesně ve druhém vrcholu [x2, y2], nezávisle na vzájemné pozici těchto vrcholů (může se jednat i o jediný bod). Horizontální a vertikální úsečky jsou vykresleny specializovanými (a pravděpodobně i rychlejšími) funkcemi:
package main import ( "image" "image/color" "image/png" "os" ) const width = 256 const height = 256 func DrawHorizontalLine(img *image.RGBA, color color.Color, x1 int, x2 int, y int) { if x1 > x2 { x1, x2 = x2, x1 } for x := x1; x < x2; x++ { img.Set(x, y, color) } } func DrawVerticalLine(img *image.RGBA, color color.Color, x int, y1 int, y2 int) { if y1 > y2 { y1, y2 = y2, y1 } for y := y1; y < y2; y++ { img.Set(x, y, color) } } func Abs(x int) int { if x < 0 { return -x } return x } func Step(v1 int, v2 int) int { if v1 < v2 { return 1 } else { return -1 } } func DrawLine(img *image.RGBA, color color.Color, x1 int, y1 int, x2 int, y2 int) { // specialni pripad - svisla usecka if x1 == x2 { DrawVerticalLine(img, color, x1, y1, y2) return } // specialni pripad - vodorovna usecka if y1 == y2 { DrawHorizontalLine(img, color, x1, x2, y1) return } // takze mame smulu a musime pouzit plnou verzi algoritmu // zrcadleni algoritmu pro dalsi oktanty x := x1 y := y1 // konstanty pouzite pri vykreslovani dx := Abs(x2 - x1) dy := Abs(y2 - y1) sx := Step(x1, x2) sy := Step(y1, y2) // pocatecni hodnota akumulatoru chyby err := dx >> 1 if dx <= dy { err = -dy >> 1 } // vse je pripraveno k vlastnimu vykresleni usecky for { img.Set(x, y, color) // test, zda se jiz doslo k poslednimu bodu if x == x2 && y == y2 { break } e2 := err if e2 > -dx { // prepocet kumulovane chyby err -= dy // posun na predchozi ci dalsi pixel na radku x += sx } if e2 < dy { // prepocet kumulovane chyby err += dx // posun na predchozi ci nasledujici radek y += sy } } } func main() { img := image.NewRGBA(image.Rect(0, 0, width, height)) outfile, err := os.Create("18.png") if err != nil { panic(err) } defer outfile.Close() c := color.RGBA{255, 255, 255, 255} for x := 0; x < width; x++ { for y := 0; y < height; y++ { img.Set(x, y, c) } } c = color.RGBA{0, 0, 255, 255} DrawLine(img, c, 20, 10, 245, 10) c = color.RGBA{255, 0, 0, 255} DrawLine(img, c, 10, 20, 10, 245) c = color.RGBA{0, 255, 0, 255} DrawLine(img, c, 20, 20, width>>1, height>>1) c = color.RGBA{0, 0, 0, 255} for x := 10; x < width; x += 10 { DrawLine(img, c, width-5, x, width-x, height-5) } png.Encode(outfile, img) }
![](https://i.iinfo.cz/images/410/golang-14-12.png)
Obrázek 12: Několik úseček vykreslených funkcí DrawLine.
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:
# | Demonstrační příklad | Popis | Cesta |
---|---|---|---|
1 | 01_empty_image.go | vytvoření rastrového obrázku s výchozími hodnotami pixelů (zcela průhledné černé pixely) | https://github.com/tisnik/go-fedora/blob/master/article14/01_empty_image.go |
2 | 02_empty_image_rectangle.go | alternativní způsob vytvoření obdélníku pro určení rozměrů obrázku | https://github.com/tisnik/go-fedora/blob/master/article14/02_empty_image_rectangle.go |
3 | 03_filled_image_setrgba.go | rastrový obrázek vyplněný konstantní barvou, použití metody SetRGBA | https://github.com/tisnik/go-fedora/blob/master/article14/03_filled_image_setrgba.go |
4 | 04_filled_image_set.go | rastrový obrázek vyplněný konstantní barvou, použití metody Set | https://github.com/tisnik/go-fedora/blob/master/article14/04_filled_image_set.go |
5 | 05_filled_image_alpha.go | obrázek, v němž mají pixely různou průhlednost (nekorektní varianta) | https://github.com/tisnik/go-fedora/blob/master/article14/05_filled_image_alpha.go |
6 | 06_filled_image_alpha_correct.go | korektní varianta předchozího příkladu | https://github.com/tisnik/go-fedora/blob/master/article14/06_filled_image_alpha_correct.go |
7 | 07_filled_image_alpha_correct.go | vylepšená varianta předchozího příkladu | https://github.com/tisnik/go-fedora/blob/master/article14/07_filled_image_alpha_correct.go |
8 | 08_image_internals.go | interní struktura záznamu s informacemi o obrázku | https://github.com/tisnik/go-fedora/blob/master/article14/08_image_internals.go |
9 | 09_raw_pixels.go | přímý přístup k jednotlivým pixelům; první varianta | https://github.com/tisnik/go-fedora/blob/master/article14/09_raw_pixels.go |
10 | 10_raw_pixels2.go | přímý přístup k jednotlivým pixelům; druhá varianta | https://github.com/tisnik/go-fedora/blob/master/article14/10_raw_pixels2.go |
11 | 11_raw_pixels3.go | přímý přístup k jednotlivým pixelům; třetí varianta | https://github.com/tisnik/go-fedora/blob/master/article14/11_raw_pixels3.go |
12 | 12_point_rectangle.go | datové struktury Point a Rectangle | https://github.com/tisnik/go-fedora/blob/master/article14/12_point_rectangle.go |
13 | 13_point_rectangle_operations.go | základní operace s obdélníky a body | https://github.com/tisnik/go-fedora/blob/master/article14/13_point_rectangle_operations.go |
14 | 14_rgba_images.go | základní vlastnosti barvového prostoru RGBA | https://github.com/tisnik/go-fedora/blob/master/article14/14_rgba_images.go |
15 | 15_cmyk_images.go | základní vlastnosti barvového prostoru CMYK | https://github.com/tisnik/go-fedora/blob/master/article14/15_cmyk_images.go |
16 | 16_cmyk_images.go | základní vlastnosti barvového prostoru CMYK | https://github.com/tisnik/go-fedora/blob/master/article14/16_cmyk_images.go |
17 | 17_chessboard.go | využití balíčku draw pro vykreslení šachovnice (rastrové operace) | https://github.com/tisnik/go-fedora/blob/master/article14/17_chessboard.go |
18 | 18_bresenham_algorithm.go | implementace Bresenhamova algoritmu pro vykreslení úsečky | https://github.com/tisnik/go-fedora/blob/master/article14/18_bresenham_algorithm.go |
19 | 19_blending.go | blending v Go (opět rastrové operace) | https://github.com/tisnik/go-fedora/blob/master/article14/19_blending.go |
20. Odkazy na Internetu
- 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