Hlavní navigace

Programovací jazyk Go a grafika: další užitečné funkce poskytované knihovnou GG

Pavel Tišnovský

V šestnácté části seriálu o jazyku Go dokončíme popis možností knihovny GG, která uživatelům nabízí podobné funkce jako Cairo či OpenVG. Budeme se zabývat například vykreslováním cest (path) či textů.

Doba čtení: 38 minut

Sdílet

11. Kubické Bézierovy křivky

12. Vizualizace řídicích bodů i tečných vektorů kubických Bézierových křivek

13. Rotace Bézierových křivek s využitím transformace zadané metodou RotateAbout

14. Převod sekvence snímků na animaci ve formátu GIF

15. Rotace pomocí metod Rotate a RotateAbout

16. Změna měřítka a skládání transformací

17. Práce s fonty, vykreslování písma (textu)

18. Vycentrování textu s využitím metody DrawStringAnchored

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Vymazání kreslicí plochy

Dnes navážeme na předchozí část seriálu o programovacím jazyku Go i o jeho knihovnách, protože si popíšeme další koncepty, na nichž je postavena knihovna GG. Připomeňme si, že tato knihovna slouží pro vykreslování 2D grafiky do rastrových obrázků, které je následně možné uložit na disk, popř. poslat jako výsledek nějakého výpočtu přes zvolený komunikační protokol (představme si například mapový server generující a posílající jednotlivé „dlaždice“, z nichž se výsledná mapa postupně poskládá). Pro ukládání obrázků do souborů se používají formáty PNG a JPEG, ovšem ve čtrnácté kapitole si ukážeme, jakým způsobem je možné provést export sekvence snímků do animovaného formátu GIF.

V dnešním prvním demonstračním příkladu si pouze připomeneme základní koncepty, na nichž je knihovna GG postavena. Při vykreslování se používá takzvaný kontext (context), což je datová struktura obsahující jak rastrový obrázek, tak i parametry vykreslování. Ve výchozím nastavení jsou všechny pixely rastrového obrázku průhledné, ovšem celý obraz můžeme vyplnit jakoukoli jinou barvou tak, že vykreslíme obdélník se shodnými rozměry, jaké má vlastní obrázek, a to s využitím metody nazvané DrawRectangle (to není zcela přesné, protože musíme brát v potaz i případné afinní transformace popsané dále):

func (dc *Context) DrawRectangle(x, y, w, h float64)
Poznámka: naprostá většina metod, které si dnes popíšeme, je vztažena k datové struktuře Context.

V prvním příkladu si povšimněte, že barvové složky definující barvu v prostoru RGB (či RGBA) jsou reprezentovány hodnotami typu float64, které leží v rozsahu <0, 1>:

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.SavePNG("01.png")
}

Po spuštění tohoto příkladu by se měl vytvořit soubor nazvaný „01.png“ s tímto obsahem:

Obrázek 1: Výsledek běhu prvního demonstračního příkladu.

Ve skutečnosti je však možné provést vyplnění celé plochy rastrového obrázku ještě jednodušeji, a to pomocí metody nazvané Clear. Nemusíme tedy explicitně vykreslovat obdélník s rozměry odpovídajícími rozměrům obrázku. Zjednodušený příklad může vypadat následovně:

package main
 
import "github.com/fogleman/gg"
 
func main() {
        const width = 320
        const height = 240
 
        dc := gg.NewContext(width, height)
 
        dc.SetRGB(1.0, 1.0, 1.0)
        dc.Clear()
 
        dc.SavePNG("02.png")
}

Obrázek 2: Výsledek běhu druhého demonstračního příkladu

2. Změna barvy použité při vykreslování v knihovně GG

Prakticky v každé aplikaci, která pro tvorbu 2D grafiky nějakým způsobem využívá knihovnu GG, se setkáme s nutností nastavení barvy vykreslování. Pro tento účel se nejčastěji používá metoda nazvaná SetRGB nebo SetRGBA:

func (dc *Context) SetRGB(r, g, b float64)
 
func (dc *Context) SetRGBA(r, g, b, a float64)

Alternativně je možné použít i další metody, v nichž se barva vykreslování reprezentuje odlišným typem hodnoty. K dispozici je hned několik způsobů:

// využití celočíselných hodnot barvových složek
func (dc *Context) SetRGB255(r, g, b int)
 
// využití celočíselných hodnot barvových složek
func (dc *Context) SetRGBA255(r, g, b, a int)
 
// použita strukura z modulu Draw
func (dc *Context) SetColor(c color.Color)
 
// použita stejná syntaxe, jako v HTML a CSS
func (dc *Context) SetHexColor(x string)

Nastavení barvy je součástí kontextu, tj. barva bude použita pro všechny příkazy Stroke nebo Fill. Následuje příklad použití – vykreslení úseček s rozdílnou intenzitou červené barvy:

for i := 0; i < 256; i += 4 {
        x := float64(i + 32)
        // barvové složky jsou hodnoty v rozsahu 0.0 až 1.0
        r := float64(i) / 256.0
        dc.SetRGB(r, 0.0, 0.0)
        dc.DrawLine(x, 20, x, 75)
        dc.Stroke()
}

Podobně ovšem můžeme použít i metodu SetRGBA a nastavit průhlednost na hodnotu 1,0:

for i := 0; i < 256; i += 4 {
        x := float64(i + 32)
        // barvové složky jsou hodnoty v rozsahu 0.0 až 1.0
        r := float64(i) / 256.0
        dc.SetRGBA(r, 0.0, 0.0, 1.0)
        dc.DrawLine(x, 20, x, 75)
        dc.Stroke()
}

Tato programová smyčka je použita v dnešním třetím demonstračním příkladu, který po svém spuštění vykreslí tři řady úseček, pokaždé s odlišnou barvou. Výsledek by měl vypadat takto:

Obrázek 3: Výsledek běhu dnešního třetího demonstračního příkladu.

Následuje úplný výpis zdrojového kódu třetího příkladu:

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 += 4 {
                x := float64(i + 32)
 
                // barvové složky jsou hodnoty v rozsahu 0.0 až 1.0
                r := float64(i) / 256.0
                dc.SetRGBA(r, 0.0, 0.0, 1.0)
                dc.DrawLine(x, 20, x, 75)
                dc.Stroke()
 
                g := float64(i) / 256.0
                dc.SetRGBA(0.0, g, 0.0, 1.0)
                dc.DrawLine(x, 92, x, height-92)
                dc.Stroke()
 
                b := float64(i) / 256.0
                dc.SetRGBA(0.0, 0.0, b, 1.0)
                dc.DrawLine(x, height-75, x, height-20)
                dc.Stroke()
        }
 
        dc.SavePNG("03.png")
}

3. Modifikace průhlednosti (alfa kanál)

V případě, že se namísto metody SetRGB použije pro nastavení barvy vykreslování metoda nazvaná SetRGBA, je možné specifikovat i průhlednost vykreslovaných pixelů. I hodnota průhlednosti (alfa kanál) většinou leží v rozsahu od 0,0 do 1,0, takže je možné použít například následující úryvek kódu určený pro vykreslení třiceti dvou svislých úseček, každou se stejnou barvou (v tomto případě černou barvou), ale s odlišnou průhledností. Úsečka zcela nalevo je na 100% průhledná, zatímco úsečka napravo je neprůhledná:

for i := 0; i < 256; i += 8 {
        x := float64(i + 32)
        // průhlednost je hodnota v rozsahu 0.0 až 1.0
        alpha := float64(i) / 256.0
        dc.SetRGBA(0.0, 0.0, 0.0, alpha)
 
        dc.DrawLine(x, 20, x, height/2-20)
        dc.Stroke()
}

Ve skutečnosti ovšem bude demonstrační příklad, na němž bude ukázána práce s průhledností, nepatrně složitější, neboť si ukážeme, jaký vliv má hodnota alfa pro černé úsečky kreslené na bílém pozadí i pro bílé úsečky kreslené na pozadí černém. Pozadí má přitom v obou případech (bez ohledu na barvu) nastavenou průhlednost na nulu, takže by měl výsledek vypadat následovně:

Obrázek 4: Výsledek běhu čtvrtého demonstračního příkladu.

Opět si ukažme zdrojový kód čtvrtého demonstračního příkladu:

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/2)
        dc.SetRGB(1.0, 1.0, 1.0)
        dc.Fill()
 
        dc.DrawRectangle(0, height/2, width, height)
        dc.SetRGB(0.0, 0.0, 0.0)
        dc.Fill()
 
        for i := 0; i < 256; i += 8 {
                x := float64(i + 32)
 
                // průhlednost je hodnota v rozsahu 0.0 až 1.0
                alpha := float64(i) / 256.0
                dc.SetRGBA(0.0, 0.0, 0.0, alpha)
 
                dc.DrawLine(x, 20, x, height/2-20)
 
                dc.Stroke()
 
                dc.SetRGBA(1.0, 1.0, 1.0, alpha)
                dc.DrawLine(x, height/2+20, x, height-20)
 
                dc.Stroke()
        }
 
        dc.SavePNG("04.png")
}

Samozřejmě si můžeme vyzkoušet, jak se celá vykreslovaná scéna změní v případě, že i pozadí bude vykresleno poloprůhlednou barvou, konkrétně barvou s alfa nastavenou na 50%:

Obrázek 5: Pozadí je v tomto případě nastaveno na poloprůhlednou barvu.

Obrázek 6: Pro zvýraznění (polo)průhledného pozadí můžeme použít prakticky jakýkoli rastrový grafický editor, který dokáže pracovat s alfa kanálem. Šachovnice v tomto případě není součástí obrázku; jen se tak zvýrazňuje průhledná a poloprůhledná plocha.

Jen pro úplnost si ukažme, jak by mohl vypadat upravený zdrojový kód příkladu. Změní se pouze dvě metody SetRGB za SetRGBA, viz též zvýrazněné části zdrojového kódu:

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/2)
        dc.SetRGBA(1.0, 1.0, 1.0, 0.5)
        dc.Fill()
 
        dc.DrawRectangle(0, height/2, width, height)
        dc.SetRGBA(0.0, 0.0, 0.0, 0.5)
        dc.Fill()
 
        for i := 0; i < 256; i += 8 {
                x := float64(i + 32)
 
                // průhlednost je hodnota v rozsahu 0.0 až 1.0
                alpha := float64(i) / 256.0
                dc.SetRGBA(0.0, 0.0, 0.0, alpha)
 
                dc.DrawLine(x, 20, x, height/2-20)
 
                dc.Stroke()
 
                dc.SetRGBA(1.0, 1.0, 1.0, alpha)
                dc.DrawLine(x, height/2+20, x, height-20)
 
                dc.Stroke()
        }
 
        dc.SavePNG("04B.png")
}

4. Specifikace šířky vykreslovaných obrysů

Při vykreslování obrysů, popř. i jednotlivých úseček (či oblouků) je možné modifikovat i šířku pera použitého při renderingu. Ta se udává – ostatně podobně jako naprostá většina ostatních numerických hodnot v knihovně GG – typem float64, protože je použita subpixelová přesnost a navíc může být šířka modifikována nastavením měřítka odlišného od hodnoty 1,00. Pro nastavení šířky obrysů se nejčastěji používá metoda nazvaná SetLineWidth:

func (dc *Context) SetLineWidth(lineWidth float64)

Použití této metody je přímočaré:

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()
}

Samozřejmě si opět ukážeme, jakým způsobem se tato metoda použije v uceleném příkladu, který po svém spuštění vykreslí následující obrázek:

Obrázek 7: Úsečky vykreslené s různou šířkou obrysů. Povšimněte si, jakým způsobem jsou stopy úseček ukončeny i toho, že u širších úseček stopa končí až za zadaným koncovým bodem (vrcholem).

Úplný zdrojový kód tohoto příkladu naleznete na adrese https://github.com/tisnik/go-fedora/blob/master/article16/05_gg_li­ne_width.go:

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("05.png")
}

5. Styl ukončení křivek i úseček

U otevřených geometrických tvarů, tj. především u cest a lomených čar, je možné specifikovat i způsob jejich zakončení. Uzavřené tvary, tj. kružnice, elipsy, obdélníky či obecné mnohoúhelníky žádný jasně definovaný konec nemají, proto na ně nebude mít nastavení stylu ukončení žádný význam. V knihovně GG, podobně jako v mnoha dalších podobně koncipovaných knihovnách (ostatně i v SVG) je možné specifikovat tři způsoby ukončení stopy otevřených tvarů, které jsou specifikovány datovým typem LineCap odvozeného od základního celočíselného datového typu int:

Hodnota Význam
LineCapRound ukončení obrysu či cesty kruhovým obloukem (obecně dochází k prodloužení tvaru)
LineCapButt ukončení obrysu či cesty kolmým řezem v místě koncových bodů (tvar končí přesně na koncovém bodu/vrcholu)
LineCapSquare ukončení obrysu či cesty kolmým řezem vzdáleným od koncových bodů o 1/2 šířky cesty (dochází tak opět k prodloužení tvaru)

Pro nastavení stylu ukončení je možné použít buď metodu nazvanou jednoduše SetLineCap, které se potřebný styl předá v parametru:

func (dc *Context) SetLineCap(lineCap LineCap)

Alternativně jsou k dispozici i metody, kterým se kromě příjemce už žádný další parametr nepředává (připomeňme si, že příjemce se píše u volání metody před tečku):

func (dc *Context) SetLineCapRound() {
 
func (dc *Context) SetLineCapButt() {
 
func (dc *Context) SetLineCapSquare() {

V dalším demonstračním příkladu je vliv nastaveného stylu ukončení ukázán na úsečkách s rozdílnou šířkou stopy. Povšimněte si vodorovných úseček, které naznačují, na kterém místě leží koncové body (vrcholy) – je patrné, že v některých případech je tvar úsečky skutečně přetažen přes koncový bod:

Obrázek 8: Úsečky proměnné šířky s nakonfigurovaným zakončením.

Samozřejmě si opět ukážeme úplný zdrojový kód demonstračního příkladu:

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.0)
 
        dc.SetLineWidth(10.0)
 
        for i := 0; i < 256; i += 16 {
                x := float64(i + 32)
 
                width := float64(i) / 20
                dc.SetLineWidth(width)
 
                dc.SetLineCapRound()
                dc.DrawLine(x, 20, x, 75)
                dc.Stroke()
 
                dc.SetLineCapButt()
                dc.DrawLine(x, 92, x, height-92)
                dc.Stroke()
 
                dc.SetLineCapSquare()
                dc.DrawLine(x, height-75, x, height-20)
                dc.Stroke()
        }
 
        dc.SetRGBA(1.0, 0.0, 0.0, 1.0)
        dc.SetLineWidth(1.0)
 
        dc.DrawLine(32, 20, width-32, 20)
        dc.Stroke()
 
        dc.DrawLine(32, 75, width-32, 75)
        dc.Stroke()
 
        dc.DrawLine(32, 92, width-32, 92)
        dc.Stroke()
 
        dc.DrawLine(32, height-92, width-32, height-92)
        dc.Stroke()
 
        dc.DrawLine(32, height-75, width-32, height-75)
        dc.Stroke()
 
        dc.DrawLine(32, height-20, width-32, height-20)
        dc.Stroke()
 
        dc.SavePNG("06.png")
}

Kromě stylu ukončení úseček je možné specifikovat i tvar použitý při navazování jednotlivých segmentů cest, a to konkrétně těmito metodami:

func (dc *Context) SetLineJoin(lineJoin LineJoin) {
 
func (dc *Context) SetLineJoinRound() {
 
func (dc *Context) SetLineJoinBevel() {

6. Čárkované a čerchované obrysy

Při kresbě liniových obrazců nebo okrajů plošných obrazců je možné zvolit vzorek (styl) úseček – plná (výchozí nastavení), čárkovaná, čerchovaná, střídavá apod. K nastavení vzorku vykreslení úsečky slouží metoda se jménem SetDash. Parametrem této metody je seznam délek vykreslených segmentů prokládaných délkami segmentů nevykreslených:

func (dc *Context) SetDash(dashes ...float64)

Nastavit je možné i posun (offset) celého vzorku, a to konkrétně metodou SetDashOffset:

func (dc *Context) SetDashOffset(offset float64)

Podívejme se na několik jednoduchých příkladů.

Čárkovaná čára s délkami úseček deset jednotek (při standardním měřítku pixelů):

dc.SetDash(10)
dc.DrawLine(32, 40, 288, 40)
dc.Stroke()

Má stejný význam – délka čárek je stejná jako šířka mezer:

dc.SetDash(10, 10)
dc.DrawLine(32, 60, 288, 60)
dc.Stroke()

Čerchovaná úsečka – čárky mají délku deseti jednotek, mezery pět jednotek a tečka je dlouhá dvě jednotky:

dc.SetDash(10, 5, 2, 5)
dc.DrawLine(32, 220, 288, 220)
dc.Stroke()

Opět si pochopitelně ukážeme příklad, který vykreslí tento obrázek:

Obrázek 9: Úsečky vykreslené s rozdílným stylem (plná, čárkovaná, čerchovaná atd.).

Zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/go-fedora/blob/master/article16/07_gg_set_dash­.go:

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("07.png")
}

7. Základní příkazy určené pro vytvoření cesty

V této kapitole se zmíníme i o důležitém konceptu takzvaných cest (paths), protože z cest jsou složeny prakticky všechny složitěji tvarované dvourozměrné objekty, ať již se jedná o objekty otevřené či naopak o objekty uzavřené. Každá cesta se skládá z prakticky libovolného množství takzvaných segmentů (segments), přičemž je zajímavé, že jednotlivé segmenty na sebe mohou, ale také nemusí navazovat (cesta tedy může v případě potřeby obsahovat i „skoky“). Použití cest v 2D grafice samozřejmě není nic nového; spíš by se dalo říci, že se jedná o dlouhým časem prověřenou technologii použitou například v PostScriptu či ve vektorovém formátu SVG (PostScript lze přitom chápat jako souborový formát, grafický metaformát, programovací jazyk a současně i vykreslovací či možná lépe řečeno renderovací knihovnu).

Datový typ gg.Context programátorům nabízí poměrně velké množství příkazů určených pro definici segmentů cest. Jednotlivé typy segmentů budou podrobněji popsány v navazujících kapitolách, proto si zde pouze uveďme jednotlivé metody a jejich význam:

# Metoda Stručný popis
1 ClosePath uzavření cesty nakreslením úsečky do jejího počátečního vrcholu
2 MoveTo přesun aktivního bodu bez kreslení
3 LineTo lineární segment (úsečka), aktivní bod se přesune na konec úsečky
4 QuadraticTo kvadratická Bézierova křivka (jeden segment), aktivní bod se přesune na konec křivky
5 CubicTo kubická Bézierova křivka (jeden segment), aktivní bod se opět přesune na konec křivky

Ukažme si nyní relativně jednoduchý demonstrační příklad, který po svém spuštění vykreslí domek jedním tahem. Pro tento účel nám poslouží metody nazvané MoveTo (přesun do počátečního bodu kreslení) a LineTo (kreslení úsečkového segmentu):

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.MoveTo(100, 200)
        dc.LineTo(200, 200)
        dc.LineTo(100, 100)
        dc.LineTo(100, 200)
        dc.LineTo(200, 100)
        dc.LineTo(100, 100)
        dc.LineTo(150, 50)
        dc.LineTo(200, 100)
        dc.LineTo(200, 200)
 
        dc.Stroke()
 
        dc.SavePNG("08.png")
}

Výsledek by měl vypadat takto:

Obrázek 10: Domeček vykreslený jedním tahem.

8. Otevřené a uzavřené cesty

Předchozí příklad si nepatrně upravíme takovým způsobem, že vykreslíme pouze obrys domku. Celá cesta reprezentující obrys tedy může být zapsána takto:

dc.MoveTo(100, 200)
dc.LineTo(100, 100)
dc.LineTo(150, 50)
dc.LineTo(200, 100)
dc.LineTo(200, 200)
dc.LineTo(100, 200)

S výsledkem:

Obrázek 11: Obrys domku vykreslený otevřenou cestou (i když je počáteční a koncový bod čistě náhodou shodný).

Celý příklad:

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.MoveTo(100, 200)
        dc.LineTo(100, 100)
        dc.LineTo(150, 50)
        dc.LineTo(200, 100)
        dc.LineTo(200, 200)
        dc.LineTo(100, 200)
 
        dc.Stroke()
 
        dc.SavePNG("09.png")
}

Ve skutečnosti však můžeme cestu uzavřít korektně pomocí metody ClosePath. Samotná deklarace cesty se tedy změní takto:

dc.MoveTo(100, 200)
dc.LineTo(100, 100)
dc.LineTo(150, 50)
dc.LineTo(200, 100)
dc.LineTo(200, 200)
dc.ClosePath()

Celý příklad nyní vypadá takto:

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.MoveTo(100, 200)
        dc.LineTo(100, 100)
        dc.LineTo(150, 50)
        dc.LineTo(200, 100)
        dc.LineTo(200, 200)
        dc.ClosePath()
 
        dc.Stroke()
 
        dc.SavePNG("10.png")
}

Obrázek 12: Obrys domku vykreslený uzavřenou cestou.

9. Použití kvadratických Bézierových křivek při tvorbě složitějších cest

Při tvorbě cest jsou podporovány jak kvadratické, tak i kubické Bézierovy křivky, což je jen dobře, protože kvadratické křivky jsou často používány například při definici fontů zatímco křivky kubické najdeme například v mnoha vektorových grafických editorech a tím pádem i v souborech exportovaných z těchto nástrojů. Bézierovy kvadratické křivky jsou určeny pouze jedním řídicím bodem a dvojicí bodů kotvicích (koncových). Křivka prochází prvním a třetím bodem (kotvicí body), druhý bod (řídicí) určuje současně oba tečné vektory. Ukázka Bézierovy kvadratické křivky spolu s jejími určujícími body je zobrazena na následujícím obrázku:

404

Obrázek 13: Bézierova kvadratická křivka zadaná dvojicí kotvicích bodů a jedním bodem řídicím.

Následuje příklad cesty vytvořené jedinou Bézierovou kvadratickou křivkou. Povšimněte si nutnosti použít metody MoveTo pro specifikaci počátečního bodu křivky, protože v metodě QuadraticTo se uvádí pouze dvojice bodů:

dc.MoveTo(10, 150)
dc.QuadraticTo(50, 10, 90, 150)
dc.Stroke()

Hladké napojení Bézierových kvadratických křivek lze zajistit zadáním identického koncového bodu první křivky a počátečního bodu křivky druhé. Současně musí být shodné tečné vektory v těchto bodech, tj. prostřední (řídicí) body musí být středově symetrické okolo společného bodu obou křivek.

Další demonstrační příklad vykreslí trojici Bézierových kvadratických křivek:

Obrázek 14: Trojice Bézierových křivek.

Následuje výpis zdrojového kódu tohoto příkladu:

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(1.0, 0.0, 0.0, 1.0)
        dc.MoveTo(10, 150)
        dc.QuadraticTo(50, 10, 90, 150)
        dc.Stroke()
 
        dc.SetRGBA(0.0, 1.0, 0.0, 1.0)
        dc.MoveTo(110, 100)
        dc.QuadraticTo(190, 100, 150, 190)
        dc.Stroke()
 
        dc.SetRGBA(0.0, 0.0, 1.0, 1.0)
        dc.MoveTo(250, 150)
        dc.QuadraticTo(210, 60, 290, 150)
        dc.Stroke()
 
        dc.SavePNG("11.png")
}

10. Vizualizace řídicích bodů i tečných vektorů kvadratických Bézierových křivek

Předchozí příklad je možné snadno upravit takovým způsobem, aby se vykreslila nejenom samotná Bézierova křivka, ale současně se zobrazily i její řídicí body a tečné vektory:

Obrázek 15: Kvadratické Bézierovy křivky zobrazení i se svými řídicími body a tečnými vektory.

Toto vylepšení je implementováno ve funkci drawQuadraticBezier, kterou nalezneme v dnešním dvanáctém demonstračním příkladu:

package main
 
import "github.com/fogleman/gg"
 
func drawQuadraticBezier(dc *gg.Context, x0 float64, y0 float64, x1 float64, y1 float64, x2 float64, y2 float64) {
        dc.SetRGBA(1.0, 0.5, 0.5, 1.0)
        dc.DrawLine(x0, y0, x1, y1)
        dc.Stroke()
        dc.DrawLine(x1, y1, x2, y2)
        dc.Stroke()
 
        dc.SetRGBA(0.0, 0.0, 0.0, 1.0)
        dc.MoveTo(x0, y0)
        dc.QuadraticTo(x1, y1, x2, y2)
        dc.Stroke()
 
        dc.SetRGBA(0.2, 0.2, 1.0, 1.0)
        dc.DrawCircle(x0, y0, 3)
        dc.Stroke()
 
        dc.SetRGBA(0.2, 0.2, 1.0, 1.0)
        dc.DrawCircle(x1, y1, 3)
        dc.Stroke()
 
        dc.SetRGBA(0.2, 0.2, 1.0, 1.0)
        dc.DrawCircle(x2, y2, 3)
        dc.Stroke()
}
 
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()
 
        drawQuadraticBezier(dc, 10, 150, 50, 10, 90, 150)
        drawQuadraticBezier(dc, 110, 100, 190, 100, 150, 190)
        drawQuadraticBezier(dc, 250, 150, 210, 60, 290, 150)
 
        dc.SavePNG("12.png")
}

11. Kubické Bézierovy křivky

Kubické Bézierovy křivky většina uživatelů používajících vektorové grafické editory již pravděpodobně velmi dobře zná (ale nalezneme je i v rastrových grafických editorech, například v GIMPu). Připomeňme si tedy, že tyto křivky jsou definovány počátečním bodem, koncovým bodem a dvojicí řídicích bodů (těmi křivka sice obecně neprochází, tyto body však ovlivňují její tvar). Větší množství řídicích bodů dává uživatelům i větší možnosti tvarování křivky, protože je možné vytvořit i esíčko, smyčku atd. Ve knihovně GG se tyto křivky (resp. segmenty složené z kubických Bézierových křivek) přidávají do cesty pomocí metody CubicTo, přičemž tato metoda očekává tři body (přesněji šestici souřadnic), protože počáteční bod již známe – je jím dočasný poslední bod aktuálně vytvářené cesty.

403

Obrázek 16: Bézierova kubická křivka zadaná dvojicí kotvicích (koncových) bodů a dvojicí bodů řídicích.

Následuje příklad cesty s jedinou Bézierovou kubickou křivkou. Opět si povšimněte nutnosti použít metody MoveTo pro specifikaci počátečního bodu křivky:

dc.MoveTo(10, 180)
dc.CubicTo(10, 10, 120, 180, 120, 10)
dc.Stroke()
Poznámka: Bézierovy kubické křivky jsou v počítačové grafice velmi rozšířeny. Mezi jejich hlavní přednosti patří intuitivní zadávání a snadné hladké navazování křivek na sebe. Také výpočet bodů, které leží na křivce, je velmi jednoduchý a rychlý. Pomocí Bézierových kubických křivek však nelze přesně modelovat kuželosečky, zejména kruh a elipsu, což omezuje použití těchto křivek v CAD systémech. Také nelze k obecné Bézierově kubice vytvořit offsetovou křivku, tj. křivku, která se od zadané křivky nachází v určité vzdálenosti. Toto omezení se teoreticky může projevit i v komerční grafice (doména programů typu „Illustrator“), ale je nutno říci, že chyba vzniklá použitím Bézierových křivek bývá velmi malá, mnohdy pod rozlišovací schopností lidského oka (opět však platí, že pro přesné CAD a CAM je nutné přijít s přesnějším řešením). Většinou postačí (automatické) rozdělení Bézierovy křivky, která má tvořit offsetovou cestu, na více částí, čímž se získá i větší množství řídicích bodů, se kterými je možné manipulovat. Tuto funkci však přímo v knihovně GG (alespoň prozatím) nenajdeme.

Obrázek 17: Výsledek činnosti demonstračního příkladu, jehož zdrojový kód je umístěn pod tento obrázek.

Trojici kubických Bézierových křivek vykreslíme například takto:

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(1.0, 0.0, 0.0, 1.0)
        dc.MoveTo(10, 180)
        dc.CubicTo(10, 10, 120, 180, 120, 10)
        dc.Stroke()
 
        dc.SetRGBA(0.0, 1.0, 0.0, 1.0)
        dc.MoveTo(110, 180)
        dc.CubicTo(190, 100, 80, 100, 160, 180)
        dc.Stroke()
 
        dc.SetRGBA(0.0, 0.0, 1.0, 1.0)
        dc.MoveTo(230, 180)
        dc.CubicTo(280, 60, 230, 60, 280, 180)
        dc.Stroke()
 
        dc.SavePNG("13.png")
}

12. Vizualizace řídicích bodů i tečných vektorů kubických Bézierových křivek

Opět si ukažme, jak se po úpravě příkladu tak, aby se vykreslily řídicí body i tečné vektory kubických Bézierových křivek změní výsledek:

Obrázek 18: Dvojice Bézierových křivek s tečnými vektory i řídicími body.

Obrázek 19: Smyčka vytvořená Bézierovou křivkou.

Následuje výpis zdrojového kódu příkladu, který po svém spuštění vytvoří dvojici výše zobrazených obrázků:

package main
 
import "github.com/fogleman/gg"
 
func drawCubicBezier(dc *gg.Context,
        x0 float64, y0 float64, x1 float64, y1 float64,
        x2 float64, y2 float64, x3 float64, y3 float64) {
 
        dc.SetRGBA(1.0, 0.5, 0.5, 1.0)
        dc.DrawLine(x0, y0, x1, y1)
        dc.Stroke()
        dc.DrawLine(x1, y1, x2, y2)
        dc.Stroke()
        dc.DrawLine(x2, y2, x3, y3)
        dc.Stroke()
 
        dc.SetRGBA(0.0, 0.0, 0.0, 1.0)
        dc.MoveTo(x0, y0)
        dc.CubicTo(x1, y1, x2, y2, x3, y3)
        dc.Stroke()
 
        dc.SetRGBA(0.2, 0.2, 1.0, 1.0)
        dc.DrawCircle(x0, y0, 3)
        dc.Stroke()
 
        dc.DrawCircle(x1, y1, 3)
        dc.Stroke()
 
        dc.DrawCircle(x2, y2, 3)
        dc.Stroke()
 
        dc.DrawCircle(x2, y2, 3)
        dc.Stroke()
 
        dc.DrawCircle(x3, y3, 3)
        dc.Stroke()
}
 
func clearCanvas(dc *gg.Context, width float64, height float64) {
        dc.DrawRectangle(0, 0, width, height)
        dc.SetRGB(1.0, 1.0, 1.0)
        dc.Fill()
}
 
func main() {
        const width = 320
        const height = 240
 
        dc := gg.NewContext(width, height)
        clearCanvas(dc, width, height)
 
        drawCubicBezier(dc, 10, 180, 10, 10, 120, 180, 120, 10)
        drawCubicBezier(dc, 230, 180, 280, 60, 230, 60, 280, 180)
 
        dc.SavePNG("14A.png")
 
        clearCanvas(dc, width, height)
 
        drawCubicBezier(dc, 130, 180, 210, 100, 100, 100, 180, 180)
 
        dc.SavePNG("14B.png")
}

13. Rotace Bézierových křivek s využitím transformace zadané metodou RotateAbout

Řídicí body Bézierovy křivky je možné podrobit afinní transformaci (posun, rotace, změna měřítka). Výsledkem aplikace transformace pouze na řídicí body bude po vykreslení zcela korektně transformovaná Bézierova křivka, což je samozřejmě výhodné, protože je rychlejší transformovat tři nebo čtyři body a nikoli všechny body, z nichž se skládají segmenty křivky:

Obrázek 20: Rotace Bézierovy křivky.

Další demonstrační příklad po svém spuštění vytvoří osm snímků s různě natočenou Bézierovou kubikou. Význam metody RotateAbout je vysvětlen v navazujícím textu:

package main
 
import (
        "fmt"
        "github.com/fogleman/gg"
)
 
func drawCubicBezier(dc *gg.Context,
        x0 float64, y0 float64, x1 float64, y1 float64,
        x2 float64, y2 float64, x3 float64, y3 float64) {
 
        dc.SetRGBA(1.0, 0.5, 0.5, 1.0)
        dc.DrawLine(x0, y0, x1, y1)
        dc.Stroke()
        dc.DrawLine(x1, y1, x2, y2)
        dc.Stroke()
        dc.DrawLine(x2, y2, x3, y3)
        dc.Stroke()
 
        dc.SetRGBA(0.0, 0.0, 0.0, 1.0)
        dc.MoveTo(x0, y0)
        dc.CubicTo(x1, y1, x2, y2, x3, y3)
        dc.Stroke()
 
        dc.SetRGBA(0.2, 0.2, 1.0, 1.0)
        dc.DrawCircle(x0, y0, 3)
        dc.Stroke()
 
        dc.DrawCircle(x1, y1, 3)
        dc.Stroke()
 
        dc.DrawCircle(x2, y2, 3)
        dc.Stroke()
 
        dc.DrawCircle(x2, y2, 3)
        dc.Stroke()
 
        dc.DrawCircle(x3, y3, 3)
        dc.Stroke()
}
 
func clearCanvas(dc *gg.Context, width float64, height float64) {
        dc.DrawRectangle(0, 0, width, height)
        dc.SetRGB(1.0, 1.0, 1.0)
        dc.Fill()
}
 
func main() {
        const width = 320
        const height = 240
 
        dc := gg.NewContext(width, height)
 
        for alpha := 0; alpha < 360; alpha += 30 {
                clearCanvas(dc, width, height)
                dc.Push()
                dc.RotateAbout(gg.Radians(float64(alpha)), width/2, height/2)
                drawCubicBezier(dc, 130, 180, 210, 100, 100, 100, 180, 180)
                dc.Pop()
                filename := fmt.Sprintf("15_%03d.png", alpha)
                dc.SavePNG(filename)
        }
 
}

14. Převod sekvence snímků na animaci ve formátu GIF

V této kapitole si vysvětlíme, jakým způsobem vznikla následující jednoduchá animace:

Obrázek 21: Jednoduchá animace vytvořená demonstračním příkladem.

V animaci se používá rotace okolo zadaného bodu. Používáme zde metody Push a Pop pro uložení a pro následné obnovení tvaru transformační matice:

for alpha := 0; alpha < 360; alpha += 5 {
        clearCanvas(dc, width, height)
        dc.Push()
        dc.RotateAbout(gg.Radians(float64(alpha)), width/2, height/2)
        ...
        vykreslení
        ...
        dc.Pop()
}

Po vykreslení získáme z kontextu knihovny GG vlastní rastrový obrázek, převedeme ho na obrázek s barvovou paletou (pomocí nám již známé metody Draw) a uložíme ho do pole obrázků. Současně inicializujeme i pole s časy přechodu mezi jednotlivými snímky:

sourceImage := dc.Image()
palettedImage := image.NewPaletted(image.Rect(0, 0, width, height), palette.Plan9)
draw.Draw(palettedImage, palettedImage.Rect, sourceImage, image.ZP, draw.Over)
images = append(images, palettedImage)
delays = append(delays, 10)
Poznámka: v příkladu se ve skutečnosti nepracuje přímo s poli, ale s řezy (slice), které ovšem interně pole používají.

A nakonec je nutné všechny snímky vyexportovat do formátu GIF:

outfile, err := os.Create("16.gif")
if err != nil {
        panic(err)
}
defer outfile.Close()
 
gif.EncodeAll(outfile, &gif.GIF{
        Image: images,
        Delay: delays,
})

Následuje výpis celého zdrojového kódu tohoto demonstračního příkladu:

package main
 
import (
        "github.com/fogleman/gg"
        "image"
        "image/color/palette"
        "image/draw"
        "image/gif"
        "os"
)
 
func drawCubicBezier(dc *gg.Context,
        x0 float64, y0 float64, x1 float64, y1 float64,
        x2 float64, y2 float64, x3 float64, y3 float64) {
 
        dc.SetRGBA(1.0, 0.5, 0.5, 1.0)
        dc.DrawLine(x0, y0, x1, y1)
        dc.Stroke()
        dc.DrawLine(x1, y1, x2, y2)
        dc.Stroke()
        dc.DrawLine(x2, y2, x3, y3)
        dc.Stroke()
 
        dc.SetRGBA(0.0, 0.0, 0.0, 1.0)
        dc.MoveTo(x0, y0)
        dc.CubicTo(x1, y1, x2, y2, x3, y3)
        dc.Stroke()
 
        dc.SetRGBA(0.2, 0.2, 1.0, 1.0)
        dc.DrawCircle(x0, y0, 3)
        dc.Stroke()
 
        dc.DrawCircle(x1, y1, 3)
        dc.Stroke()
 
        dc.DrawCircle(x2, y2, 3)
        dc.Stroke()
 
        dc.DrawCircle(x2, y2, 3)
        dc.Stroke()
 
        dc.DrawCircle(x3, y3, 3)
        dc.Stroke()
}
 
func clearCanvas(dc *gg.Context, width float64, height float64) {
        dc.DrawRectangle(0, 0, width, height)
        dc.SetRGB(1.0, 1.0, 1.0)
        dc.Fill()
}
 
func main() {
        const width = 320
        const height = 240
 
        dc := gg.NewContext(width, height)
 
        var images []*image.Paletted
        var delays []int
 
        for alpha := 0; alpha < 360; alpha += 5 {
                clearCanvas(dc, width, height)
                dc.Push()
                dc.RotateAbout(gg.Radians(float64(alpha)), width/2, height/2)
                drawCubicBezier(dc, 130, 180, 210, 100, 100, 100, 180, 180)
                dc.Pop()
                sourceImage := dc.Image()
                palettedImage := image.NewPaletted(image.Rect(0, 0, width, height), palette.Plan9)
                draw.Draw(palettedImage, palettedImage.Rect, sourceImage, image.ZP, draw.Over)
                images = append(images, palettedImage)
                delays = append(delays, 10)
                println(alpha, "of", 360)
        }
 
        outfile, err := os.Create("16.gif")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        gif.EncodeAll(outfile, &gif.GIF{
                Image: images,
                Delay: delays,
        })
}

15. Rotace pomocí metod Rotate a RotateAbout

Všechny vykreslované úsečky a cesty jsou před vlastním renderingem podrobeny afinní transformaci: rotaci, změně měřítka, posunu, popř. libovolné kombinaci transformací (zkosení atd.). První metodou pro zadání transformace je metoda nazvaná Rotate, která provádí rotaci okolo počátku souřadné soustavy:

Obrázek 22: Rotace o 15° okolo počátku souřadné soustavy s využitím metody Rotate.

V dalším příkladu si povšimněte toho, že pro převod radiánů na stupně můžeme použít funkci Radians:

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.Rotate(gg.Radians(15.0))
 
        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("17.png")
}

Mnohem častější bývá požadavek na rotaci okolo zvoleného bodu, nejenom okolo počátku souřadné soustavy. Zde využijeme metodu pojmenovanou příhodně RotateAbout:

Obrázek 23: Rotace o 15° okolo bodu [width/2, height/2] s využitím metody RotateAbout.

Příklad, který tuto metodu používá, vypadá následovně:

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.RotateAbout(gg.Radians(15.0), width/2.0, height/2.0)
 
        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("18.png")
}

16. Změna měřítka a skládání transformací

Pro změnu měřítka se používají dvě metody pojmenované Scale a ScaleAbout. Většinou se používá druhá uvedená metoda, a to z toho důvodu, že se změna měřítka může vztáhnout k jakémukoli bodu v ploše, nejenom tedy k počátku souřadné soustavy:

dc.ScaleAbout(2.0, 2.0, width/2.0, height/2.0)

Výsledek může vypadat například takto:

Obrázek 24: Změna měřítka před vykreslením úseček s různými typy čáry.

Úplný zdrojový kód příkladu:

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.ScaleAbout(2.0, 2.0, width/2.0, height/2.0)
 
        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("19.png")
}

Nic nám samozřejmě nebrání v tom provést složení transformací ručně. Rotace o 15° okolo zadaného bodu [width/2, height/2] se může naprogramovat i tímto způsobem:

dc.Translate(width/2.0, height/2.0)
dc.Rotate(gg.Radians(15))
dc.Translate(-width/2.0, -height/2.0)

Obrázek 25: Výsledek ručně složené transformace.

Jen pro doplnění následuje úplný zdrojový kód příkladu:

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.Translate(width/2.0, height/2.0)
        dc.Rotate(gg.Radians(15))
        dc.Translate(-width/2.0, -height/2.0)
 
        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")
}

17. Práce s fonty, vykreslování písma (textu)

Knihovna GG podporuje i vykreslování textu. Používají se přitom fonty typu TrueType, které je nejdříve nutné načíst metodou LoadFontFace, jíž se předá jak jméno souboru s fontem, tak i požadovaná velikost písma. Pro vykreslení lze použít metodu DrawString, které se předává souřadnice levého dolního rohu pomyslného obdélníku, do něhož se text vykreslí:

Obrázek 26: Text vykreslený knihovnou GG.

Podívejme se na příklad, který po svém spuštění vytvoří obrázek číslo 25:

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.SetRGB(0.0, 0.0, 0.0)
        if err := dc.LoadFontFace("luxisr.ttf", 36); err != nil {
                println("Cannot load font")
                panic(err)
        }
        dc.DrawString("Hello, world!", 0, height)
 
        dc.SavePNG("21.png")
}

18. Vycentrování textu s využitím metody DrawStringAnchored

V případě, že je nutné text vycentrovat, můžeme namísto metody DrawString použít metodu DrawStringAnchored, které se kromě vlastního textu předají i souřadnice vztažného bodu (anchor) a taktéž dvojice vah udávajících relativní pozici textu k tomuto bodu. Pro vycentrování použijte hodnoty 0,5:

dc.DrawStringAnchored("Hello, world!", width/2, height/2, 0.5, 0.5)

S výsledkem:

Obrázek 27: Vycentrovaný text.

Opět si samozřejmě ukažme, jak vypadá celý příklad:

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.SetRGB(0.0, 0.0, 0.0)
        if err := dc.LoadFontFace("luxisr.ttf", 36); err != nil {
                println("Cannot load font")
                panic(err)
        }
        dc.DrawStringAnchored("Hello, world!", width/2, height/2, 0.5, 0.5)
 
        dc.SavePNG("22.png")
}

Můžeme si samozřejmě vyzkoušet, jak se projeví různé hodnoty předané do předposledního parametru metody DrawStringAnchored. Tento parametr určuje, kterou část textu má vztažný bod určovat. Parametry budeme měnit od hodnoty –1,0 do 1,0:

var h float64 = 20
weight := -1.0
 
for i := 0; i < 11; i++ {
        dc.DrawStringAnchored("Hello, world!", width/2, h, weight, 0.5)
        h += 20
        weight += 0.2
}

S výsledkem:

Obrázek 28: Různé hodnoty váhy udávající relativní vzdálenost textu ke vztažnému bodu.

cif-tip-digitalizaceCR

Úplný zdrojový kód tohoto příkladu vypadá následovně:

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.SetRGB(0.0, 0.0, 0.0)
        if err := dc.LoadFontFace("luxisr.ttf", 24); err != nil {
                println("Cannot load font")
                panic(err)
        }
 
        var h float64 = 20
        weight := -1.0
 
        for i := 0; i < 11; i++ {
                dc.DrawStringAnchored("Hello, world!", width/2, h, weight, 0.5)
                h += 20
                weight += 0.2
        }
 
        dc.SavePNG("23.png")
}

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_gg_clear_screen.go vymazání kreslicí plochy pomocí metody fill https://github.com/tisnik/go-fedora/blob/master/article16/01_gg_cle­ar_screen.go
2 02_gg_clear_screen.go vymazání kreslicí plochy pomocí metody clear https://github.com/tisnik/go-fedora/blob/master/article16/02_gg_cle­ar_screen.go
3 03_gg_rgba_color.go změna barvy vykreslování obrysů (zde jednotlivých úseček) https://github.com/tisnik/go-fedora/blob/master/article16/03_gg_rgba_co­lor.go
4 04_gg_alpha_component.go změna průhlednosti při vykreslování obrysů (zde opět jednotlivých úseček) https://github.com/tisnik/go-fedora/blob/master/article16/04_gg_al­pha_component.go
5 05_gg_line_width.go modifikace šířky kreslených obrysů https://github.com/tisnik/go-fedora/blob/master/article16/05_gg_li­ne_width.go
6 06_gg_line_caps.go modifikace typů ukončení obrysů (úseček atd.) https://github.com/tisnik/go-fedora/blob/master/article16/06_gg_li­ne_caps.go
7 07_gg_set_dash.go nastavení čárkované, čerchované atd. úsečky https://github.com/tisnik/go-fedora/blob/master/article16/07_gg_set_dash­.go
8 08_gg_simple_path.go jednoduchá cesta (path) https://github.com/tisnik/go-fedora/blob/master/article16/08_gg_sim­ple_path.go
9 09_gg_open_path.go otevřená cesta (path) https://github.com/tisnik/go-fedora/blob/master/article16/09_gg_o­pen_path.go
10 10_gg_closed_path.go explicitně uzavřená cesta https://github.com/tisnik/go-fedora/blob/master/article16/10_gg_clo­sed_path.go
11 11_gg_quadratic_bezier.go kvadratická Bézierova křivka https://github.com/tisnik/go-fedora/blob/master/article16/11_gg_qu­adratic_bezier.go
12 12_gg_quadratic_bezier_con­trol_points.go kvadratická Bézierova křivka s vyznačením řídicích bodů https://github.com/tisnik/go-fedora/blob/master/article16/12_gg_qu­adratic_bezier_control_po­ints.go
13 13_gg_cubic_bezier.go kubická Bézierova křivka https://github.com/tisnik/go-fedora/blob/master/article16/13_gg_cu­bic_bezier.go
14 14_gg_cubic_bezier_control_points.go kubická Bézierova křivka s vyznačením řídicích bodů https://github.com/tisnik/go-fedora/blob/master/article16/14_gg_cu­bic_bezier_control_points­.go
15 15_gg_rotation.go rotace celé Bézierovy křivky https://github.com/tisnik/go-fedora/blob/master/article16/15_gg_ro­tation.go
16 16_gg_to_image.go animace, export všech snímků do formátu GIF https://github.com/tisnik/go-fedora/blob/master/article16/16_gg_to_i­mage.go
17 17_gg_transformation_rotate.go afinní transformace: rotace https://github.com/tisnik/go-fedora/blob/master/article16/17_gg_tran­sformation_rotate.go
18 18_gg_transformation_rotate_about.go afinní transformace: rotace okolo zadaného bodu https://github.com/tisnik/go-fedora/blob/master/article16/18_gg_tran­sformation_rotate_about.go
19 19_gg_scale_about.go afinní transformace: změna měřítka okolo zadaného bodu https://github.com/tisnik/go-fedora/blob/master/article16/19_gg_sca­le_about.go
20 20_gg_translate.go skládání několika transformací https://github.com/tisnik/go-fedora/blob/master/article16/20_gg_tran­slate.go
21 21_gg_text.go vykreslení textu s využitím TTF fontů https://github.com/tisnik/go-fedora/blob/master/article16/21_gg_tex­t.go
22 22_gg_text_centered.go vykreslení vycentrovaného textu https://github.com/tisnik/go-fedora/blob/master/article16/22_gg_tex­t_centered.go
23 23_gg_text_centered.go vykreslení textu, který je horizontálně posunut o různou hodnotu https://github.com/tisnik/go-fedora/blob/master/article16/23_gg_tex­t_centered.go

20. Odkazy na Internetu

  1. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  2. package gg
    https://godoc.org/github.com/fo­gleman/gg
  3. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  4. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  5. The Go image package
    https://blog.golang.org/go-image-package
  6. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  7. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  8. YAML
    https://yaml.org/
  9. edn
    https://github.com/edn-format/edn
  10. Smile
    https://github.com/FasterXML/smile-format-specification
  11. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  12. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  13. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  14. Introducing JSON
    http://json.org/
  15. Package json
    https://golang.org/pkg/encoding/json/
  16. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  17. Go by Example: JSON
    https://gobyexample.com/json
  18. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  19. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  20. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  21. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  22. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  23. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  24. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  25. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  26. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  27. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  28. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  29. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  30. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  31. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  32. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  33. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  34. Algorithms to Go
    https://yourbasic.org/
  35. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  36. 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/
  37. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  38. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  39. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  40. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  41. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  42. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  43. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  44. The Go Programming Language (home page)
    https://golang.org/
  45. GoDoc
    https://godoc.org/
  46. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  47. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  48. The Go Programming Language Specification
    https://golang.org/ref/spec
  49. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  50. Package builtin
    https://golang.org/pkg/builtin/
  51. Package fmt
    https://golang.org/pkg/fmt/
  52. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  53. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  54. Learning Go
    https://www.miek.nl/go/
  55. Go Bootcamp
    http://www.golangbootcamp.com/
  56. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  57. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  58. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  59. The Go Blog
    https://blog.golang.org/
  60. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  61. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  62. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  63. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  64. 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
  65. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  66. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  67. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  68. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  69. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  70. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  71. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  72. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  73. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  74. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  75. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  76. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  77. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  78. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  79. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  80. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  81. 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/
  82. 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
  83. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  84. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  85. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  86. Go vs. Python
    https://www.peterbe.com/plog/govspy
  87. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  88. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  89. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  90. Go by Example: Slices
    https://gobyexample.com/slices
  91. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  92. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  93. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  94. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  95. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  96. nils In Go
    https://go101.org/article/nil.html
  97. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  98. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  99. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  100. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  101. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  102. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  103. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  104. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  105. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  106. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  107. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  108. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  109. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  110. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  111. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  112. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  113. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  114. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  115. 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
  116. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  117. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  118. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  119. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  120. Selectors
    https://golang.org/ref/spec#Selectors
  121. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  122. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  123. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  124. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  125. Part 21: Goroutines
    https://golangbot.com/goroutines/
  126. Part 22: Channels
    https://golangbot.com/channels/
  127. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  128. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  129. 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/
  130. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  131. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  132. Control Structures
    https://www.golang-book.com/books/intro/5
  133. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  134. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  135. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  136. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  137. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  138. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  139. Effective Go
    https://golang.org/doc/ef­fective_go.html
  140. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  141. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation