Programovací jazyk Go se používá v mnoha oblastech – cloudové služby a platformy, vývoj back endu, nástroje ovládané z příkazové řádky, nástroje pro CI/CD apod. Go se ovšem z historických důvodů příliš často nepoužíval pro vývoj klasických desktopových aplikací (pouze pro jejich imitaci přes Electron). Poměrně dlouhou dobu totiž nebyly k dispozici vhodné nástroje a knihovny. Tato situace se do značné míry změnila s příchodem (relativně mladého) projektu nazvaného GoGPU, jenž je určen pro tvorbu desktopových aplikací s 2D a popř. i 3D grafikou.
Co se dozvíte v článku
- Knihovna gg – ale která?
- Instalace knihovny GoGPU/gg
- Inicializace grafického kontextu a smazání kreslicí plochy
- Vymazání plochy obrázku se specifikací použité barvy
- Nastavení barvy kreslení bez specifikace průhlednosti i se specifikací průhlednosti
- Barvový prostor RGB a RGBA
- Specifikace šířky vykreslovaných obrysů
- Styl ukončení křivek i úseček, styl vykreslování (čárkované a čerchované stopy)
- Práce s cestami (paths)
- Otevřené a uzavřené cesty
- Složitější cesty založené na kvadratických a kubických Bézierových křivkách
- Vykreslení kvadratických Bézierových křivek
- Vykreslení kubických Bézierových křivek
- Rotace Bézierových křivek s využitím transformace zadané metodou RotateAbout
- Převod sekvence snímků na animaci ve formátu GIF
- Lineární transformace aplikované při vykreslování 2D tvarů
- Práce s fonty, vykreslení textu
- Repositář s demonstračními příklady
- Odkazy na Internetu
Jak je ovšem v ekosystému jazyka Go dobrým zvykem, není GoGPU navržen jako všeobjímající framework, který do značné míry určuje architekturu aplikací. Je tomu přesně naopak, protože GoGPU je „jen“ sadou knihoven, které lze v případě potřeby používat samostatně (což si ostatně ukážeme i dnes):
| Balíček | Stručný popis |
|---|---|
| gogpu | jednoduchý grafický framework, integruje další balíčky (dnes ho nepoužijeme) |
| gg | vykreslování rastrové 2D grafiky jak softwarově, tak pomocí GPU, téma dnešního článku |
| gg-pdf | export vykreslené rastrové 2D grafiky do formátu PDF, rozšiřuje možnosti balíčku gg |
| gg-svg | export vykreslené rastrové 2D grafiky do formátu SVG, rozšiřuje možnosti balíčku gg |
| g3d | vykreslování 3D grafiky jak softwarově, tak pomocí GPU, de facto alternativa ke klasickému OpenGL |
| gputypes | pomocný balíček s typy WebGPU, prozatím ho nepoužijeme |
| ui | tvorba grafického uživatelského rozhraní |
| editor | nový (a nedokončený!) balíček – bude se jednat o komponentu pro tvorbu textových vstupních polí |
| systray | manipulace s obsahem hlavního panelu Windows a některých Linuxových desktopových prostředí |
| audio | práce se zvukem (popíšeme si později) |
Jak již bylo napsáno výše, je možné jednotlivé balíčky používat samostatně. Příkladem je back end služba, která generuje rastrové obrázky popř. SVG kresby. V takových případech si vystačíme s gg nebo gg-svg. Naproti tomu jednoduchá aplikace s grafickým uživatelským rozhraním nebude vyžadovat g3d atd. Taktéž je možné vykreslování provádět jak softwarově, tak i s využitím GPU (výsledky ovšem mohou být nepatrně odlišné).
Knihovna gg – ale která?
Z předchozí kapitoly je zřejmé, že se projekt GoGPU skládá z mnoha balíčků, které za sebou mají různou historii vývoje. Týká se to mj. i balíčku, který se jmenuje gg. Čtenáři s lepší pamětí než autor tohoto článku (tj. všichni čtenáři) si možná vzpomenou, že jsme se s balíčkem gg v seriálu o programovacím jazyce Go již kdysi setkali. Ovšem jednalo se tehdy o balíček fogleman/gg, nikoli o gogpu/gg. Oba balíčky mají podobné, ovšem nikoli totožné API. Dnes se pochopitelně zaměříme na druhý z těchto balíčků, tedy gogpu/gg, který je v některých ohledech odlišný, ovšem navíc nabízí propojení s balíčky go-pdf, go-svg a taktéž ui. V dalším textu tedy budeme pod názvem gg vždy myslet balíček gogpu/gg.
Instalace knihovny GoGPU/gg
V praktické části dnešního článku se – prozatím ovšem pouze v rychlosti a bez dalších podrobností – seznámíme s některými možnostmi, které nám v oblasti 2D grafiky poskytuje knihovna nazvaná GoGPU/gg (a tedy balíček gogpu/gg). Možnosti poskytované touto knihovnou do jisté míry připomínají knihovnu Cairo popř. OpenVG a EGL: 2D scéna je při použití této knihovny vykreslována v immediate (přímém) režimu, přičemž se používá koncept takzvaných cest (path), stylů vykreslování i vyplňování a taktéž lineárních transformací, které je možné v případě potřeby skládat. Navíc tato knihovna podporuje rasterizaci TrueType fontů, takže je možné do obrázků vkládat různě transformovaný text (lineární transformace se pochopitelně vztahují i na text).
Pozor je nutné si dát především na to, že GoGPU vyžaduje relativně novou verzi jazyka Go, konkrétně Go 1.25. Pokud máte nainstalovánu starší verzi Go, je čas na upgrade:
$ sudo dnf upgrade golang Updating and loading repositories: Repositories loaded. Package Arch Version Repository Size Upgrading: golang x86_64 1.25.10-2.fc42 updates 9.6 MiB replacing golang x86_64 1.24.10-1.fc42 updates 8.9 MiB golang-bin x86_64 1.25.10-2.fc42 updates 67.7 MiB replacing golang-bin x86_64 1.24.10-1.fc42 updates 122.1 MiB golang-misc noarch 1.25.10-2.fc42 updates 56.3 KiB replacing golang-misc noarch 1.24.10-1.fc42 updates 60.2 KiB golang-src noarch 1.25.10-2.fc42 updates 81.5 MiB replacing golang-src noarch 1.24.10-1.fc42 updates 79.2 MiB Transaction Summary: Upgrading: 4 packages Replacing: 4 packages Total size of inbound packages is 33 MiB. Need to download 33 MiB. After this operation, 51 MiB will be freed (install 159 MiB, remove 210 MiB). Is this ok [y/N]:
Po upgrade na novější verzi Go se přesvědčíme o tom, že je k dispozici alespoň Go 1.25:
$ go version go version go1.25.10 X:nodwarf5 linux/amd64
Samotná instalace knihovny gogpu/gg je stejná, jako v případě jiných externích balíčků, což konkrétně znamená, že se použije příkaz go get spuštěný v adresáři s projektem:
$ go get github.com/gogpu/gg go: downloading github.com/gogpu/gg v0.50.1 go: downloading github.com/gogpu/gpucontext v0.21.0 go: downloading golang.org/x/image v0.43.0 go: downloading golang.org/x/text v0.38.0 go: downloading github.com/gogpu/gputypes v0.5.1 go: upgraded go 1.24.10 => 1.25.0 go: added github.com/gogpu/gg v0.50.1 go: added github.com/gogpu/gpucontext v0.21.0 go: added github.com/gogpu/gputypes v0.5.1 go: added golang.org/x/image v0.43.0 go: added golang.org/x/text v0.38.0
Projektový soubor by měl nyní mít zhruba následující obsah:
module gg-demos
go 1.25.0
require (
github.com/gogpu/gg v0.50.1 // indirect
github.com/gogpu/gpucontext v0.21.0 // indirect
github.com/gogpu/gputypes v0.5.1 // indirect
golang.org/x/image v0.43.0 // indirect
golang.org/x/text v0.38.0 // indirect
)
Konkrétní hashe použitých balíčků jsou, jak je zvykem, uloženy v souboru go.sum:
github.com/gogpu/gg v0.50.1 h1:6Xb+f68mw6YVOtBu0n7e4INIXE4NUy6+fvNucCMqgH8= github.com/gogpu/gg v0.50.1/go.mod h1:vdfjSLVXNp7Qx9DLH/NRZqvLiQniKhHRFN/JkSF3LJk= github.com/gogpu/gpucontext v0.21.0 h1:O1SnXBiednIyVP4Zx5SASh0svYiG6g/wF1igNZVo1Pc= github.com/gogpu/gpucontext v0.21.0/go.mod h1:6zwdmYXH5GQltoiHbb3WXVS/UJ5bFsCux0mXCVqGlzY= github.com/gogpu/gputypes v0.5.1 h1:X38OPcP6umQqqubzzJYL6Nm1tXHSNQj6TRSAoxdAJmg= github.com/gogpu/gputypes v0.5.1/go.mod h1:cnXrDMwTpWTvJLW1Vreop3PcT6a2YP/i3s91rPaOavw= golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY= golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q= golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
Mimochodem, pokud budete s využitím GoGPU (a balíčku g3d) vytvářet 3D grafiku i s grafickým uživatelským rozhraním, budou závislosti odlišné (ale to již předbíháme):
module gg-demos-3D
go 1.25.10
require (
github.com/go-webgpu/goffi v0.5.6 // indirect
github.com/go-webgpu/webgpu v0.5.2 // indirect
github.com/gogpu/g3d v0.1.1 // indirect
github.com/gogpu/gogpu v0.44.1 // indirect
github.com/gogpu/gpucontext v0.21.0 // indirect
github.com/gogpu/gputypes v0.5.1 // indirect
github.com/gogpu/naga v0.17.15 // indirect
github.com/gogpu/wgpu v0.30.10 // indirect
golang.org/x/sys v0.46.0 // indirect
)
Soubor go.sum bude v tomto případě rozsáhlejší:
github.com/go-webgpu/goffi v0.5.5 h1:xLWMhnJq+GrejlhTC3iVt3q8ozRbmYSq8kzuw6MKlgE= github.com/go-webgpu/goffi v0.5.5/go.mod h1:wfoxNsJkU+5RFbV1kNN1kunhc1lFHuJKK3zpgx08/uM= github.com/go-webgpu/goffi v0.5.6 h1:jAN4ciR63DcpU/qdX4A3wbNvcpZ6AdFK+H7OB/1+9Lc= github.com/go-webgpu/goffi v0.5.6/go.mod h1:wfoxNsJkU+5RFbV1kNN1kunhc1lFHuJKK3zpgx08/uM= github.com/go-webgpu/webgpu v0.5.2 h1:E+yKCbRw/Dgn77pb/CX3kXatwwROGX7/B4ZFbRPaRbQ= github.com/go-webgpu/webgpu v0.5.2/go.mod h1:d2RR3tI2kUtcMfpC+qJt46IRkDslRhftjCfQvnprGkY= github.com/gogpu/g3d v0.1.1 h1:aQHP5zQ0mlfYWSJUf4r+0R1cJ1dfdnOC7YLK3e3hNpU= github.com/gogpu/g3d v0.1.1/go.mod h1:7L7xlaG56S3FzfaMI9s/ww+OXsR+9oHsCwJHnME9TlE= github.com/gogpu/gogpu v0.44.1 h1:oGTO4DpKCLL5huwe6w64CNWfPBZlAueTOd5xx2xfO5c= github.com/gogpu/gogpu v0.44.1/go.mod h1:woyKt98CmzLTCbklx0pPNBH/rSxQVzcH1y+qABuZQtg= github.com/gogpu/gpucontext v0.21.0 h1:O1SnXBiednIyVP4Zx5SASh0svYiG6g/wF1igNZVo1Pc= github.com/gogpu/gpucontext v0.21.0/go.mod h1:6zwdmYXH5GQltoiHbb3WXVS/UJ5bFsCux0mXCVqGlzY= github.com/gogpu/gputypes v0.5.0 h1:i2ED/9w6m6yLxf8XJT69/NIMSNTLO2y5F1LqvugCKIE= github.com/gogpu/gputypes v0.5.0/go.mod h1:cnXrDMwTpWTvJLW1Vreop3PcT6a2YP/i3s91rPaOavw= github.com/gogpu/gputypes v0.5.1 h1:X38OPcP6umQqqubzzJYL6Nm1tXHSNQj6TRSAoxdAJmg= github.com/gogpu/gputypes v0.5.1/go.mod h1:cnXrDMwTpWTvJLW1Vreop3PcT6a2YP/i3s91rPaOavw= github.com/gogpu/naga v0.17.15 h1:uyy4bc8wYlvDaYOpmCBYrJ+d+e7ZqP2BN36csmRVs7o= github.com/gogpu/naga v0.17.15/go.mod h1:15sQaHKkbqXcwTN+hHYGLsA0WBBnkmYzne/eF5p5WEg= github.com/gogpu/wgpu v0.30.2 h1:7csTEfCvQnrQHMxZaUB6ulxgsCmrg2eVJSU4demI1hQ= github.com/gogpu/wgpu v0.30.2/go.mod h1:Ih63YKckUGtNXnXT6LjkHcH6FUfib0TjCwDR5615gPI= github.com/gogpu/wgpu v0.30.10 h1:KWQOo2RR8d+uNFYYD5WGCG0f4AiX8IOHH8kWi2w6Eec= github.com/gogpu/wgpu v0.30.10/go.mod h1:9TDtAhJmIhh1RfxcLySWEtkwfFMVf0Y7X7TLtVqvTBE= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
V adresáři s projektem si můžeme zobrazit nápovědu k právě nainstalovanému balíčku gogpu/gg:
$ go doc gogpu/gg
package gg // import "github.com/gogpu/gg"
Package gg provides a simple 2D graphics library for Go.
# Overview
gg is a Pure Go 2D graphics library inspired by fogleman/gg and designed to
integrate with the GoGPU ecosystem. It provides an immediate-mode drawing API
similar to HTML Canvas, with both software and GPU rendering backends.
# Quick Start
import "github.com/gogpu/gg"
// Create a drawing context (dc = drawing context convention)
dc := gg.NewContext(512, 512)
// Draw shapes
dc.SetRGB(1, 0, 0)
dc.DrawCircle(256, 256, 100)
dc.Fill()
// Save to PNG
dc.SavePNG("output.png")
# API Compatibility
The API is designed to be compatible with fogleman/gg for easy migration.
Most fogleman/gg code should work with minimal changes.
Inicializace grafického kontextu a smazání kreslicí plochy
V knihovně gg se při vykreslování 2D scény pracuje s takzvaným grafickým kontextem, jenž se získá konstruktorem NewContext. Kontext je datová struktura obsahující reference jak na vznikající rastrový obrázek, tak i na parametry vykreslování. Z hlavičky konstruktoru je patrné, že je mu nutné předat alespoň rozměry kreslicí plochy (canvasu), které budou v případě rasterizace výsledků odpovídat rozlišení (v pixelech) :
func NewContext(width, height int, opts ...ContextOption) *Context
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. Vymazání lze (v libovolný okamžik) zajistit metodou nazvanou příznačně Clear:
func (c *Context) Clear()
Clear resets the entire context to transparent (zero alpha). To fill with a
specific background color, use [ClearWithColor].
Výsledný rastrový obrázek po jeho smazání necháme uložit do souboru ve formátu PNG metodou SavePNG:
func (c *Context) SavePNG(path string) error
SavePNG saves the context to a PNG file.
Dnešní první demonstrační příklad po svém spuštění vytvoří soubor 01.png s následujícím (přímo neviditelným) obsahem:
Obrázek 1: Obrázek s průhlednými pixely (výchozí stav).
Následuje výpis úplného zdrojového kódu prvního demonstračního příkladu, který tento obrázek vytvoří:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 1:
// Balíček gogpu/gg.
// Vymazání obsahu rastrového obrázku.
//
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "01.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// smazání obsahu obrázku s využitím kontextu
dc.Clear()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Ve druhém příkladu je realizováno vymazání celého obrázku bílou barvou. Musíme použít tři metody. První z nich „zaregistruje“ vykreslení obdélníka. Sice poněkud předbíháme, ale povšimněte si, že souřadnice i rozměry nejsou celými čísly:
func (c *Context) DrawRectangle(x, y, w, h float64)
DrawRectangle draws a rectangle.
Další metoda SetRGB slouží pro nastavení barvy kreslení či výplně:
func (c *Context) SetRGB(r, g, b float64)
SetRGB sets the current color using RGB values (0-1).
A konečně metoda Fill vyplní uzavřený tvar (což obdélník je) zvolenou barvou:
func (c *Context) Fill() error
Fill fills the current path and clears it. If a GPU accelerator is
registered and supports the path, it is used first. Otherwise, the software
renderer handles the operation. The RasterizerMode set via SetRasterizerMode
controls algorithm selection. Returns an error if the rendering operation
fails.
Výsledkem je bílý obrázek:
Obrázek 2: Obrázek s bílými pixely s explicitně nastavenou průhledností.
Následuje výpis úplného zdrojového kódu druhého demonstračního příkladu:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 2:
// Balíček gogpu/gg.
// Vymazání obrázku vykreslením vyplněného obdélníku.
//
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "02.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// vykreslování s využitím kontextu
dc.DrawRectangle(0, 0, width, height)
dc.SetRGB(1.0, 1.0, 1.0)
dc.Fill()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
Vymazání plochy obrázku se specifikací použité barvy
Pokusme se nyní nastavit barvu vykreslování či výplně a poté zavolat metodu Clear:
// vykreslování s využitím kontextu dc.SetRGB(1.0, 1.0, 1.0) dc.Clear()
Výsledkem bude – možná poněkud překvapivě – obrázek s průhlednými pixely:
Obrázek 3: Obrázek s průhlednými pixely (nejsou ovlivněny nastavenou barvou).
Opět si pochopitelně uvedeme úplný zdrojový kód:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 3:
// Balíček gogpu/gg.
// Vymazání obrazovky (lepší varianta).
//
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "03.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// vykreslování s využitím kontextu
dc.SetRGB(1.0, 1.0, 1.0)
dc.Clear()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Aby byla plocha obrázky vymazána zvolenu barvou, musí se namísto metody Context.Clear zavolat metoda se jménem Context.ClearWithColor:
func (c *Context) ClearWithColor(col RGBA)
ClearWithColor fills the entire context with the specified color. This is
the recommended way to set a background color before drawing.
Korektní postup může využívat barvu reprezentovanou strukturou gg.RGBA:
type RGBA struct {
R, G, B, A float64
}
RGBA represents a color with red, green, blue, and alpha components.
Each component is in the range [0, 1].
To si pochopitelně můžeme velmi snadno otestovat:
// vykreslování s využitím kontextu
color := gg.RGBA{R: 0.5, G: 0.9, B: 0.0, A: 0.5}
dc.ClearWithColor(color)
Výsledek:
Obrázek 4: Explicitně nastavená barva pixelů při smazání plochy obrázku.
Následuje výpis úplného zdrojového kódu příkladu:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 4:
// Balíček gogpu/gg.
// Vymazání obrazovky (varianta se specifikací barvy).
//
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "04.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// vykreslování s využitím kontextu
color := gg.RGBA{R: 0.5, G: 0.9, B: 0.0, A: 0.5}
dc.ClearWithColor(color)
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Nastavení barvy a následné vymazání celé plochy obrázku lze zkrátit na jediný řádek:
// vykreslování s využitím kontextu
dc.ClearWithColor(gg.RGBA{R: 0.5, G: 0.9, B: 0.0, A: 0.5})
S výsledkem:
Obrázek 5: Explicitně nastavená barva pixelů při smazání plochy obrázku.
Zdrojový kód v pořadí již pátého demonstračního příkladu vypadá takto:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 5:
// Balíček gogpu/gg.
// Vymazání obrazovky (varianta se specifikací barvy).
//
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "05.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// vykreslování s využitím kontextu
dc.ClearWithColor(gg.RGBA{R: 0.5, G: 0.9, B: 0.0, A: 0.5})
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Nastavení barvy kreslení bez specifikace průhlednosti i se specifikací průhlednosti
Barva použitá pro vykreslování a/nebo pro výplně je reprezentována v barvovém prostoru RGB, ovšem navíc je k ní přidána i informace o alfa kanálu (tedy průhlednost). Změna aktuálně nastavené barvy se v tom nejjednodušším případě (pokud nepotřebujeme například využít HSL) provádí metodou Context.SetRGB (tu jsme již použili) nebo Context.SetRGBA. Barvové složky i průhlednost jsou reprezentovány hodnotami se znaménkem s rozsahem 0,0 až 1,0:
func (c *Context) SetRGB(r, g, b float64)
SetRGB sets the current color using RGB values (0-1).
a:
func (c *Context) SetRGBA(r, g, b, a float64)
SetRGBA sets the current color using RGBA values (0-1).
Další demonstrační příklad vykreslí vzorník barev, u nichž se mění jejich červená a modrá složka, zatímco složka zelená je nastavená na 50%:
Obrázek 6: Specifikace barvy vykreslování a výplně.
Úplný zdrojový kód tohoto příkladu vypadá následovně:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 6:
// Balíček gogpu/gg.
// Práce s barvami.
//
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 256
const height = 256
// jméno souboru s výsledným obrázkem
const fileName = "06.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
for y := range 16 {
for x := range 16 {
// nastavení barvy kreslení
dc.SetRGB(float64(x)/16.0, 0.5, float64(y)/16.0)
// vykreslení vyplněného obdélníka
dc.DrawRectangle(float64(x*16)+3, float64(y*16)+3, 10, 10)
dc.Fill()
}
}
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Zajímavé bude zjistit, jakým způsobem se vykreslí černé čtverce, u kterých se postupně mění alfa složka od 0,0 do 1,0. Přesně tuto operaci provádíme v pořadí již sedmém demonstračním příkladu:
Obrázek 7: Specifikace průhlednosti barvy vykreslování a výplně.
Zdrojový kód tohoto příkladu má následující tvar:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 7:
// Balíček gogpu/gg.
// Práce s barvami.
//
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 256
const height = 256
// jméno souboru s výsledným obrázkem
const fileName = "07.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
for y := range 16 {
for x := range 16 {
// nastavení barvy kreslení
dc.SetRGBA(0.0, 0.0, 0.0, float64(x)/32.0+float64(y)/32.0)
// vykreslení vyplněného obdélníka
dc.DrawRectangle(float64(x*16)+3, float64(y*16)+3, 10, 10)
dc.Fill()
}
}
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Barvový prostor RGB a RGBA
Nastavení průhlednosti (alfa složky) na hodnotu 1,0 znamená, že barva bude při vykreslování zcela neprůhledná:
Obrázek 8: Specifikace alfa složky barvy při vykreslování.
V dnešním osmém demonstračním příkladu, je použita programová smyčka, která po svém spuštění vykreslí tři řady úseček, pokaždé s odlišnou barvou:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 8:
// Balíček gogpu/gg.
// Barvový prostor RGB a RGBA.
//
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "08.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// pozadí obrázku
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()
}
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
V případě, že se namísto metody SetRGB použije pro nastavení barvy vykreslování metoda nazvaná SetRGBA, je možné, jak již víme, 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 9: Specifikace alfa složky barvy při vykreslování.
Úplný zdrojový kód takto upraveného demonstračního příkladu vypadá následovně:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 9:
// Balíček gogpu/gg.
// Barvový prostor RGB a RGBA.
// Alfa kanál v barvovém prostoru RGBA.
//
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "09.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// pozadí obrázku
dc.DrawRectangle(0, 0, width, height/2)
dc.SetRGB(1.0, 1.0, 1.0)
dc.Fill()
// pozadí obrázku
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()
}
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Specifikace šířky vykreslovaných obrysů
Při vykreslování obrysů popř. i jednotlivých úseček (či oblouků, cest atd.) je možné modifikovat i šířku stopy použité při rasterizaci a renderingu. Ta se udává – ostatně podobně jako naprostá většina ostatních numerických hodnot v knihovně GG – datovým 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 (c *Context) SetLineWidth(width float64)
SetLineWidth sets the line width for stroking.
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 10: Modifikace šířky stopy.
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 9:
// Balíček gogpu/gg.
// Nastavení šířky vykreslovaných úseček a křivek.
//
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "10.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// pozadí obrázku
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()
}
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Styl ukončení křivek i úseček, styl vykreslování (čárkované a čerchované stopy)
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)
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 11: Modifikace stylu zakončení kreslené stopy.
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 11:
// Balíček gogpu/gg.
// Nastavení stylu konců úseček.
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "11.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// pozadí obrázku
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)
// nastavení stylů obou konců úsečky
dc.SetLineCap(gg.LineCapRound)
dc.DrawLine(x, 20, x, 75)
dc.Stroke()
// nastavení stylů obou konců úsečky
dc.SetLineCap(gg.LineCapButt)
dc.DrawLine(x, 92, x, height-92)
dc.Stroke()
// nastavení stylů obou konců úsečky
dc.SetLineCap(gg.LineCapSquare)
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()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
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 12: Změna stylu vykreslování stop.
Zdrojový kód tohoto demonstračního příkladu vypadá takto:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 12:
// Balíček gogpu/gg.
// Nastavení stylu vykreslovaných úseček.
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "12.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// pozadí obrázku
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()
// styl vykreslení úsečky
dc.SetDash(10)
dc.DrawLine(32, 40, 288, 40)
dc.Stroke()
// styl vykreslení úsečky
dc.SetDash(10, 10)
dc.DrawLine(32, 60, 288, 60)
dc.Stroke()
// styl vykreslení úsečky
dc.SetDash(10, 5)
dc.DrawLine(32, 80, 288, 80)
dc.Stroke()
// styl vykreslení úsečky
dc.SetDash(10, 5, 2, 5)
dc.DrawLine(32, 100, 288, 100)
dc.Stroke()
// šířka vykreslení úsečky
dc.SetLineWidth(4.0)
dc.SetLineCap(gg.LineCapButt)
// styl vykreslení úsečky
dc.DrawLine(32, 140, 288, 140)
dc.Stroke()
// styl vykreslení úsečky
dc.SetDash(10)
dc.DrawLine(32, 160, 288, 160)
dc.Stroke()
// styl vykreslení úsečky
dc.SetDash(10, 10)
dc.DrawLine(32, 180, 288, 180)
dc.Stroke()
// styl vykreslení úsečky
dc.SetDash(10, 5)
dc.DrawLine(32, 200, 288, 200)
dc.Stroke()
// styl vykreslení úsečky
dc.SetDash(10, 5, 2, 5)
dc.DrawLine(32, 220, 288, 220)
dc.Stroke()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Práce s cestami (paths)
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 |
Hlavičky těchto metod lze pochopitelně najít v nápovědě:
func (c *Context) ClosePath()
ClosePath closes the current subpath.
func (c *Context) MoveTo(x, y float64)
MoveTo starts a new subpath at the given point.
func (c *Context) LineTo(x, y float64)
LineTo adds a line to the current path.
func (c *Context) QuadraticTo(cx, cy, x, y float64)
QuadraticTo adds a quadratic Bezier curve to the current path.
func (c *Context) CubicTo(c1x, c1y, c2x, c2y, x, y float64)
CubicTo adds a cubic Bezier curve to the current path.
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):
Obrázek 13: Domek vykreslený jedním tahem je složený z liniových segmentů (úseček).
Následuje výpis zdrojového kódu tohoto příkladu:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 13:
// Balíček gogpu/gg.
// Práce s cestou (path).
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "13.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// pozadí obrázku
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)
// začátek vykreslování cesty
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)
// vlastní vykreslení cesty
dc.Stroke()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
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 14: Pět lineárních segmentů (úseček) ve skutečnosti tvoří otevřenou cestu.
Celý příklad:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 14:
// Balíček gogpu/gg.
// Otevřená cesta (open path).
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "14.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// pozadí obrázku
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)
// začátek vykreslování cesty
dc.MoveTo(100, 200)
dc.LineTo(100, 100)
dc.LineTo(150, 50)
dc.LineTo(200, 100)
dc.LineTo(200, 200)
dc.LineTo(100, 200)
// vlastní vykreslení cesty
dc.Stroke()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
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()
Obrázek 15: Uzavřená cesta.
Upravená varianta zdrojového kódu příkladu nyní vypadá takto:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 15:
// Balíček gogpu/gg.
// Uzavřená cesta (closed path).
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "15.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// pozadí obrázku
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)
// začátek vykreslování cesty
dc.MoveTo(100, 200)
dc.LineTo(100, 100)
dc.LineTo(150, 50)
dc.LineTo(200, 100)
dc.LineTo(200, 200)
dc.ClosePath()
// vlastní vykreslení cesty
dc.Stroke()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Složitější cesty založené na kvadratických a kubických Bézierových křivkách
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 vždy 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 16: Bézierovy kvadratické křivky.
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.
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). 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. přesněji řečeno segmenty složené z kubických Bézierových křivek) přidávají do cesty s využitím 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.
Vykreslení kvadratických Bézierových křivek
Další demonstrační příklad, který si dnes ukážeme, vykreslí trojici Bézierových kvadratických křivek:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 16:
// Balíček gogpu/gg.
// Kvadratická Bézierova křivka.
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "16.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// pozadí obrázku
dc.DrawRectangle(0, 0, width, height)
dc.SetRGB(1.0, 1.0, 1.0)
dc.Fill()
// cesta
dc.SetRGBA(1.0, 0.0, 0.0, 1.0)
dc.MoveTo(10, 150)
dc.QuadraticTo(50, 10, 90, 150)
// vlastní vykreslení cesty
dc.Stroke()
// cesta
dc.SetRGBA(0.0, 1.0, 0.0, 1.0)
dc.MoveTo(110, 100)
dc.QuadraticTo(190, 100, 150, 190)
// vlastní vykreslení cesty
dc.Stroke()
// cesta
dc.SetRGBA(0.0, 0.0, 1.0, 1.0)
dc.MoveTo(250, 150)
dc.QuadraticTo(210, 60, 290, 150)
// vlastní vykreslení cesty
dc.Stroke()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
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 17: Bézierovy kvadratické křivky i se svými řídicími body.
Toto vylepšení je implementováno ve funkci drawQuadraticBezier, kterou nalezneme v dalším příkladu:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 17:
// Balíček gogpu/gg.
// Kvadratická Bézierova křivka s řídicími body.
package main
import "github.com/gogpu/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)
// vykreslení
dc.Stroke()
dc.DrawCircle(x1, y1, 3)
// vykreslení
dc.Stroke()
dc.DrawCircle(x2, y2, 3)
// vykreslení
dc.Stroke()
}
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "17.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// pozadí obrázku
dc.DrawRectangle(0, 0, width, height)
dc.SetRGB(1.0, 1.0, 1.0)
dc.Fill()
// vykreslení cest
drawQuadraticBezier(dc, 10, 150, 50, 10, 90, 150)
drawQuadraticBezier(dc, 110, 100, 190, 100, 150, 190)
drawQuadraticBezier(dc, 250, 150, 210, 60, 290, 150)
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Vykreslení kubických Bézierových křivek
Kubickou Bézierovu křivku (pokud nebude navazovat na další segmenty) lze vykreslit snadno. Nejdříve se samostatně zadá první kotvicí bod a následně trojice bodů (jeden kotvicí a dva řídicí):
// cesta dc.SetRGBA(1.0, 0.0, 0.0, 1.0) dc.MoveTo(10, 180) dc.CubicTo(10, 10, 120, 180, 120, 10) // vlastní vykreslení cesty dc.Stroke()
V dalším demonstračním příkladu se vykreslí trojice Bézierových křivek:
Obrázek 18: Bézierovy kubické křivky.
Zdrojový kód takto upraveného příkladu se příliš neliší od příkladů z předchozí kapitoly:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 18:
// Balíček gogpu/gg.
// Kubická Bézierova křivka.
package main
import "github.com/gogpu/gg"
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "18.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// pozadí obrázku
dc.DrawRectangle(0, 0, width, height)
dc.SetRGB(1.0, 1.0, 1.0)
dc.Fill()
// cesta
dc.SetRGBA(1.0, 0.0, 0.0, 1.0)
dc.MoveTo(10, 180)
dc.CubicTo(10, 10, 120, 180, 120, 10)
// vlastní vykreslení cesty
dc.Stroke()
// cesta
dc.SetRGBA(0.0, 1.0, 0.0, 1.0)
dc.MoveTo(110, 180)
dc.CubicTo(190, 100, 80, 100, 160, 180)
// vlastní vykreslení cesty
dc.Stroke()
// cesta
dc.SetRGBA(0.0, 0.0, 1.0, 1.0)
dc.MoveTo(230, 180)
dc.CubicTo(280, 60, 230, 60, 280, 180)
// vlastní vykreslení cesty
dc.Stroke()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Na závěr této části si ukažme, jak se provede úprava příkladu tak, aby se vykreslily řídicí body i tečné vektory kubických Bézierových křivek:
Obrázek 19: Řídicí body a tečné vektory Bézierových kubických křivek.
Obrázek 20: Řídicí body a tečné vektory Bézierovy kubické křivky.
Zdrojový kód demonstračního příkladu se v tomto případě změní následovně:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 19:
// Balíček gogpu/gg.
// Kubická Bézierova křivka s řídicími body.
package main
import "github.com/gogpu/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)
// vykreslení
dc.Stroke()
dc.DrawCircle(x2, y2, 3)
// vykreslení
dc.Stroke()
dc.DrawCircle(x3, y3, 3)
// vykreslení
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() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName1 = "19_1.png"
const fileName2 = "19_2.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
// vymazání obrázku
clearCanvas(dc, width, height)
// vykreslení cest
drawCubicBezier(dc, 10, 180, 10, 10, 120, 180, 120, 10)
drawCubicBezier(dc, 230, 180, 280, 60, 230, 60, 280, 180)
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName1)
// vymazání obrázku
clearCanvas(dc, width, height)
// vykreslení cesty
drawCubicBezier(dc, 130, 180, 210, 100, 100, 100, 180, 180)
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName2)
}
Rotace Bézierových křivek s využitím transformace zadané metodou RotateAbout
Řídicí body Bézierovy křivky (ale i všechny další vykreslované tvary) 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:
func (c *Context) RotateAbout(angle, x, y float64)
RotateAbout rotates around a specific point.
Jednotlivé transformace se skládají (což si ukážeme v dalším textu), takže v případě, že má být realizováno otočení o absolutní úhel, musí se před natočením uložit poslední stav grafického kontextu na zásobník a po provedení transformace a vykreslení opět tento stav obnovit. K těmto operacím se používají metody Push a Pop. Povšimněte si, že se neukládá pouze aktuálně vypočtená transformace, ale i další stav vykreslovacího řetězce:
func (c *Context) Push()
Push saves the current state (transform, paint, clip, and mask).
func (c *Context) Pop()
Pop restores the last saved state.
V příkladu je navíc použita i pomocná funkce pro převod úhlů na radiány:
func Radians(angle float64) float64 {
return float64(angle / 180.0 * math.Pi)
}
Další demonstrační příklad po svém spuštění vytvoří několik snímků s různě natočenou Bézierovou kubikou. Význam metody RotateAbout je vysvětlen v navazujícím textu:
Obrázek 21: Rotace Bézierovy křivky, jejích koncových bodů, obalové křivky atd.
Obrázek 22: Rotace Bézierovy křivky, jejích koncových bodů, obalové křivky atd.
Obrázek 23: Rotace Bézierovy křivky, jejích koncových bodů, obalové křivky atd.
Obrázek 24: Rotace Bézierovy křivky, jejích koncových bodů, obalové křivky atd.
Výše zobrazené Bézierovy křivky byly vykresleny tímto příkladem:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 20:
// Balíček gogpu/gg.
// Rotace vykreslovaných objektů.
package main
import (
"fmt"
"math"
"github.com/gogpu/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 Radians(angle float64) float64 {
return float64(angle / 180.0 * math.Pi)
}
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// vytvoření kontextu
dc := gg.NewContext(width, height)
for alpha := 0; alpha < 360; alpha += 30 {
clearCanvas(dc, width, height)
// transformace a vykreslení
dc.Push()
dc.RotateAbout(Radians(float64(alpha)), width/2, height/2)
drawCubicBezier(dc, 130, 180, 210, 100, 100, 100, 180, 180)
dc.Pop()
filename := fmt.Sprintf("20_%03d.png", alpha)
dc.SavePNG(filename)
}
}
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 25: Rotace Bézierovy křivky.
Využijeme zde kombinaci technik implementovaných ve standardních knihovnách image/draw a image/gif s knihovnou gogpu/gg.
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(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é funkce Draw ze standardní knihovny image/draw) a uložíme ho do pole obrázků:
package draw // import "image/draw"
func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op)
Draw calls DrawMask with a nil mask.
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, a to konkrétně pomocí funkce EncodeAll ze standardní knihovny image/gif:
package gif // import "image/gif"
func EncodeAll(w io.Writer, g *GIF) error
EncodeAll writes the images in g to w in GIF format with the given loop
count and delay between frames.
Realizace je uložení celé animace snadná:
// uložení sekvence obrázků
outfile, err := os.Create(fileName)
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:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 21:
// Balíček gogpu/gg.
// Rasterizace do zvoleného rastrového formátu.
// Výsledkem bude animovaný GIF.
package main
import (
"fmt"
"image"
"image/color/palette"
"image/draw"
"image/gif"
"math"
"os"
"github.com/gogpu/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 Radians(angle float64) float64 {
return float64(angle / 180.0 * math.Pi)
}
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "21.gif"
// vytvoření kontextu
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(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)
fmt.Println(alpha, "of", 360)
}
// uložení sekvence obrázků
outfile, err := os.Create(fileName)
if err != nil {
panic(err)
}
defer outfile.Close()
gif.EncodeAll(outfile, &gif.GIF{
Image: images,
Delay: delays,
})
}
Lineární transformace aplikované při vykreslování 2D tvarů
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 (to je důležité):
func (c *Context) Rotate(angle float64)
Rotate applies a rotation (angle in radians).
Obrázek 26: Rotace okolo počátku souřadné soustavy.
V dalším demonstračním příkladu si povšimněte toho, že pro převod radiánů na stupně můžeme použít funkci Radians:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 22:
// Balíček gogpu/gg.
// Transformace (rotace).
package main
import (
"math"
"github.com/gogpu/gg"
)
func Radians(angle float64) float64 {
return float64(angle / 180.0 * math.Pi)
}
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "22.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
dc.DrawRectangle(0, 0, width, height)
dc.SetRGB(1.0, 1.0, 1.0)
dc.Fill()
dc.Rotate(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()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
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:
func (c *Context) RotateAbout(angle, x, y float64)
RotateAbout rotates around a specific point.
Obrázek 27: Rotace okolo zvoleného bodu.
Příklad, který tuto metodu používá, vypadá následovně:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 23:
// Balíček gogpu/gg.
// Transformace (rotace okolo zvoleného bodu).
package main
import (
"math"
"github.com/gogpu/gg"
)
func Radians(angle float64) float64 {
return float64(angle / 180.0 * math.Pi)
}
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "23.png"
// vytvoření kontextu
dc := gg.NewContext(width, height)
dc.DrawRectangle(0, 0, width, height)
dc.SetRGB(1.0, 1.0, 1.0)
dc.Fill()
dc.RotateAbout(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()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Pro změnu měřítka se používá metoda pojmenovaná Scale (druhá metoda ScaleAbout prozatím chybí). Změnu měřítka většinou potřebujeme vztáhnout k jakémukoli bodu v ploše, nejenom tedy k počátku souřadné soustavy. V takovém případě lze použít skládání transformací – provede se posun do počátku souřadné soustavy, změní se měřítko a následně se provede zpětný posun:
dc.Translate(width/2.0, height/2.0)
dc.Scale(2.0, 2.0)
dc.Translate(-width/2.0, -height/2.0)
Výsledek bude v tomto případě následující:
Obrázek 28: Změna měřítka se projeví i na šířce kreslených stop.
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 24:
// Balíček gogpu/gg.
// Transformace (změna měřítka okolo zvoleného bodu).
package main
import (
"github.com/gogpu/gg"
)
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "24.png"
// vytvoření kontextu
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.Scale(2.0, 2.0)
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()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
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(Radians(15))
dc.Translate(-width/2.0, -height/2.0)
Obrázek 29: Ruční složení několika transformací.
Jen pro doplnění následuje úplný zdrojový kód příkladu:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 25:
// Balíček gogpu/gg.
// Transformace (posun).
package main
import (
"math"
"github.com/gogpu/gg"
)
func Radians(angle float64) float64 {
return float64(angle / 180.0 * math.Pi)
}
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "25.png"
// vytvoření kontextu
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(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()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Práce s fonty, vykreslení textu
Knihovna gogpu/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:
func (c *Context) LoadFontFace(path string, points float64) error
LoadFontFace loads a font from a file and sets it as the current font.
The size is specified in points.
Deprecated: Use text.NewFontSourceFromFile and SetFont instead. This method
is provided for convenience and backward compatibility.
Example (new way):
source, err := text.NewFontSourceFromFile("font.ttf")
if err != nil {
return err
}
face := source.Face(12.0)
ctx.SetFont(face)
func NewFontSourceFromFile(path string, opts ...SourceOption) (*FontSource, error)
NewFontSourceFromFile loads a FontSource from a font file path.
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í:
func (c *Context) DrawString(s string, x, y float64)
DrawString draws text at position (x, y) where y is the baseline. If no font
has been set with SetFont, this function does nothing.
If a GPU accelerator is registered and supports text rendering (implements
GPUTextAccelerator), the text is rendered via the GPU MSDF pipeline. The
CTM (Current Transform Matrix) is passed to the GPU so that Scale, Rotate,
and Skew transforms affect text rendering, not just position. Otherwise,
the CPU text pipeline is used with transform-aware rendering:
- Translation-only: bitmap fast path (zero quality loss)
- Uniform scale ≤256px: bitmap at device size (Strategy A, Skia pattern)
- Everything else: glyph outlines as vector paths (Strategy B, Vello
pattern)
The baseline is the line on which most letters sit. Characters with
descenders (like 'g', 'j', 'p', 'q', 'y') extend below the baseline.
Obrázek 30: Vykreslení textu do počátku souřadné soustavy.
Podívejme se na příklad, který po svém spuštění vytvoří obrázek s textem „Hello, world!“ (font je uložen do repositáře se všemi demonstračními příklady):
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 26:
// Balíček gogpu/gg.
// Vykreslení textu.
package main
import (
"fmt"
"github.com/gogpu/gg"
)
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "26.png"
// jméno souboru s fontem
const fontFileName = "fonts/FreeSans.ttf"
// vytvoření kontextu
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)
// pokus o načtení fontu
if err := dc.LoadFontFace(fontFileName, 36); err != nil {
fmt.Println("Cannot load font")
panic(err)
}
dc.DrawString("Hello, world!", 0, height)
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
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:
func (c *Context) DrawStringAnchored(s string, x, y, ax, ay float64)
DrawStringAnchored draws text with an anchor point. The anchor point is
specified by ax and ay, which are in the range [0, 1].
(0, 0) = top-left
(0.5, 0.5) = center
(1, 1) = bottom-right
The text is positioned so that the anchor point is at (x, y).
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 31: Vykreslení vycentrovaného textu.
Tento obrázek byl vykreslen následujícím příkladem:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 27:
// Balíček gogpu/gg.
// Vykreslení vycentrovaného textu.
package main
import (
"fmt"
"github.com/gogpu/gg"
)
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "27.png"
// jméno souboru s fontem
const fontFileName = "fonts/FreeSans.ttf"
// vytvoření kontextu
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)
// pokus o načtení fontu
if err := dc.LoadFontFace(fontFileName, 36); err != nil {
fmt.Println("Cannot load font")
panic(err)
}
dc.DrawStringAnchored("Hello, world!", width/2, height/2, 0.5, 0.5)
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
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 32: Vliv změny polohy vztažného bodu při vykreslování textu.
Úplný zdrojový kód tohoto příkladu vypadá následovně:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 28:
// Balíček gogpu/gg.
// Vykreslení vycentrovaného textu.
package main
import (
"fmt"
"github.com/gogpu/gg"
)
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "28.png"
// jméno souboru s fontem
const fontFileName = "fonts/FreeSans.ttf"
// vytvoření kontextu
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)
// pokus o načtení fontu
if err := dc.LoadFontFace(fontFileName, 24); err != nil {
fmt.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(fileName)
}
A v dnešním posledním demonstračním příkladu si ukážeme vykreslení textu, který je orotován okolo specifikovaného bodu:
// transformace a vykreslení
dc.Push()
dc.RotateAbout(Radians(0.0), width/2, height/2)
dc.DrawStringAnchored("Hello, world!", width/2, height/2, 0.5, 0.5)
dc.RotateAbout(Radians(120.0), width/2, height/2)
dc.DrawStringAnchored("Hello, world!", width/2, height/2, 0.5, 0.5)
dc.RotateAbout(Radians(120.0), width/2, height/2)
dc.DrawStringAnchored("Hello, world!", width/2, height/2, 0.5, 0.5)
dc.Pop()
Výsledek – trojice textů – bude vypadat následovně:
Obrázek 33: Rotace textu okolo specifikovaného bodu.
Úplný zdrojový kód dnešního posledního demonstračního příkladu vypadá následovně:
// Seriál "Programovací jazyk Go"
// https://www.root.cz/serialy/programovaci-jazyk-go/
//
// Demonstrační příklad číslo 29:
// Balíček gogpu/gg.
// Vykreslení vycentrovaného textu.
package main
import (
"fmt"
"math"
"github.com/gogpu/gg"
)
func Radians(angle float64) float64 {
return float64(angle / 180.0 * math.Pi)
}
func main() {
// rozměry rastrového obrázku
const width = 320
const height = 240
// jméno souboru s výsledným obrázkem
const fileName = "29.png"
// jméno souboru s fontem
const fontFileName = "fonts/FreeSans.ttf"
// vytvoření kontextu
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)
// pokus o načtení fontu
if err := dc.LoadFontFace(fontFileName, 36); err != nil {
fmt.Println("Cannot load font")
panic(err)
}
// transformace a vykreslení
dc.Push()
dc.RotateAbout(Radians(0.0), width/2, height/2)
dc.DrawStringAnchored("Hello, world!", width/2, height/2, 0.5, 0.5)
dc.RotateAbout(Radians(120.0), width/2, height/2)
dc.DrawStringAnchored("Hello, world!", width/2, height/2, 0.5, 0.5)
dc.RotateAbout(Radians(120.0), width/2, height/2)
dc.DrawStringAnchored("Hello, world!", width/2, height/2, 0.5, 0.5)
dc.Pop()
// uložení výsledného rastrového obrázku
dc.SavePNG(fileName)
}
Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – relativně malý, dnes má přibližně šest až sedm megabajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady, které naleznete v následující tabulce:
Odkazy na Internetu
- Projekt GoGPU na GitHubu
https://github.com/orgs/gogpu/repositories - Balíček gogpu/gg
https://github.com/gogpu/gg - Balíček gogpu/gg-svg
https://github.com/gogpu/gg-svg - Balíček gogpu/gg-pdf
https://github.com/gogpu/gg-pdf - package gg (což je jiná varianta 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 - Immediate mode (computer graphics)
https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics) - Retained mode
https://en.wikipedia.org/wiki/Retained_mode - Lecture 11: Linear Interpolation Again – Bézier Curves
http://www.math.kent.edu/~reichel/courses/intr.num.comp.1/fall09/lecture12/bez.pdf - Geometrie/Úvod do křivek
https://cs.wikibooks.org/wiki/Geometrie/%C3%9Avod_do_k%C5%99ivek - Seriál Grafická knihovna OpenGL
https://www.root.cz/serialy/graficka-knihovna-opengl/ - Seriál Tvorba přenositelných grafických aplikací využívajících knihovnu GLUT
https://www.root.cz/serialy/tvorba-prenositelnych-grafickych-aplikaci-vyuzivajicich-knihovnu-glut/ - Seriál OpenGL a nadstavbová knihovna GLU
https://www.root.cz/serialy/opengl-a-nadstavbova-knihovna-glu/ - Seriál OpenGL evaluátory
https://www.root.cz/serialy/opengl-evaluatory/ - Khronos Group
https://www.khronos.org/ - Khronos Group (Wikipedia)
https://en.wikipedia.org/wiki/Khronos_Group - OpenVG (Wikipedia)
https://en.wikipedia.org/wiki/OpenVG - OpenVG Quick Reference Card
https://www.khronos.org/files/openvg-quick-reference-card.pdf - OpenVG on the Raspberry Pi
http://mindchunk.blogspot.cz/2012/09/openvg-on-raspberry-pi.html - ShivaVG: open-source ANSI C OpenVG
http://ivanleben.blogspot.cz/2007/07/shivavg-open-source-ansi-c-openvg.html - Testbed for exploring OpenVG on the Raspberry Pi
https://github.com/ajstarks/openvg - Seriál Grafické karty a grafické akcelerátory
http://www.root.cz/serialy/graficke-karty-a-graficke-akceleratory/ - (Linux) Framebuffer
http://wiki.linuxquestions.org/wiki/Framebuffer - (Linux) Framebuffer HOWTO
http://tldp.org/HOWTO/Framebuffer-HOWTO/ - Linux framebuffer (Wikipedia)
https://en.wikipedia.org/wiki/Linux_framebuffer - Seriál Grafické formáty
http://www.root.cz/serialy/graficke-formaty/ - Seriál Grafický formát GIF
https://www.root.cz/serialy/graficky-format-gif/ - PNG is Not GIF
https://www.root.cz/clanky/png-is-not-gif/
