Obsah
2. Změna barvy použité při vykreslování v knihovně GG
3. Modifikace průhlednosti (alfa kanál)
4. Specifikace šířky vykreslovaných obrysů
5. Styl ukončení křivek i úseček
6. Čárkované a čerchované obrysy
7. Základní příkazy určené pro vytvoření cesty
9. Použití kvadratických Bézierových křivek při tvorbě složitějších cest
10. Vizualizace řídicích bodů i tečných vektorů kvadratických Bézierových křivek
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
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)
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_line_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ý).
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:

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.

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

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)
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.
Ú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:
20. Odkazy na Internetu
- Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation