Tvorba interaktivních aplikací s GUI s využitím projektu GoGPU

Dnes
Doba čtení: 41 minut

Sdílet

GoGPU
Autor: Andrey Kolkov
Ukážeme si, jakým způsobem lze otevřít okno a přes knihovnu gg provést vykreslení celé 2D scény do plochy tohoto okna. Následně přidáme interaktivní ovládání aplikace pomocí klávesnice i myši.

Na předchozí dvojici článků o knihovně gg [1] [2] dnes navážeme. Ukážeme si totiž, jakým způsobem lze otevřít okno a přes gg provést vykreslení celé 2D scény do plochy tohoto okna. Samotná knihovna gg takové operace sice nepodporuje, ovšem to ve skutečnosti nevadí, protože dokáže spolupracovat s knihovnou gogpu patřící do stejně pojmenovaného projektu.

Co se dozvíte v článku
  1. Události a callback funkce, které na ně reagují
  2. Události Draw a Close
  3. První demonstrační příklad: aplikace s prázdným oknem
  4. Vykreslení pozadí zvolené barvy v ploše okna
  5. Úplný zdrojový kód demonstračního příkladu, který inicializuje pozadí okna
  6. Povolení kontinuálního překreslování okna
  7. Využití canvasu při kreslení do okna: vykreslení obrysu obrazce zvolenou barvou
  8. Vykreslení hvězdy do plochy okna: základ pro navazující příklady
  9. Další základ pro další příklady: vykreslení Bézierových křivek i s jejich řídicími body
  10. Reakce aplikace na stisk klávesy či více kláves
  11. Zjištění stisku kláves použitých pro ovládání aplikace
  12. Demonstrační příklad: rotace hvězdy s využitím kurzorových kláves
  13. Demonstrační příklad: změna barvy hvězdy dalšími klávesami
  14. Přečtení aktuální pozice kurzoru myši popř. detekce stisku tlačítek myši
  15. Demonstrační příklad: posun řídicích bodů Bézierových kubických křivek
  16. Demonstrační příklad: jednoduchý kreslicí program
  17. Vylepšení: kreslení „gumové čáry“ sledující pohyb kurzoru
  18. Repositář s demonstračními příklady
  19. Odkazy na Internetu

To znamená, že ve všech demonstračních příkladech, které si dnes ukážeme, bude importována mj. i tato knihovna:

        "github.com/gogpu/gogpu"

Aplikaci s grafickým uživatelským rozhraním je možné v tom nejjednodušším případě inicializovat konstruktorem NewApp, kterému se předává struktura s konfigurací aplikace:

func NewApp(config Config) *App
    NewApp creates a new application with the given configuration.

Při inicializaci struktury Config můžeme využít konstruktor DefaultConfig vracející, jak název napovídá, výchozí konfiguraci:

func DefaultConfig() Config
    DefaultConfig returns a sensible default configuration. By default,
    uses event-driven rendering (0% CPU when idle). For game loops, use
    WithContinuousRender(true).

V tom nejjednodušším případě tedy může inicializace aplikace vypadat takto:

// inicializace aplikace s GUI
app := gogpu.NewApp(gogpu.DefaultConfig())

Samotné spuštění aplikace (což vede k zobrazení okna atd.) pak vypadá následovně:

// spuštění aplikace
app.Run()

Pro úpravu výchozí konfigurace slouží řada metod objektu Config, které vrací upravenou podobu tohoto objektu. To umožňuje řetězit volání těchto metod, což si ihned ukážeme:

func (c Config) WithAppName(name string) Config
func (c Config) WithBackend(backend types.BackendType) Config
func (c Config) WithContinuousRender(continuous bool) Config
func (c Config) WithFrameless(frameless bool) Config
func (c Config) WithFullscreen() Config
func (c Config) WithGraphicsAPI(api types.GraphicsAPI) Config
func (c Config) WithHeaderAlignment(alignment HeaderAlignment) Config
func (c Config) WithIcon(img image.Image) Config
func (c Config) WithMaxSize(width, height int) Config
func (c Config) WithMinSize(width, height int) Config
func (c Config) WithPowerPreference(pref gputypes.PowerPreference) Config
func (c Config) WithRenderMode(mode RenderMode) Config
func (c Config) WithResizable(resizable bool) Config
func (c Config) WithSize(width, height int) Config
func (c Config) WithTabbingIdentifier(id string) Config
func (c Config) WithTabbingMode(mode TabbingMode) Config
func (c Config) WithTitle(title string) Config
func (c Config) WithVSync(vsync bool) Config

Příkladem může být inicializace aplikace s oknem o rozměrech 400×300 pixelů a s nastaveným titulkem:

// rozměry okna
const width = 400
const height = 300
 
// inicializace aplikace s GUI
app := gogpu.NewApp(gogpu.DefaultConfig().
        WithTitle("GoGPU + gg").
        WithSize(width, height))

Události a callback funkce, které na ně reagují

Vývoj aplikací s interaktivním grafickým uživatelským rozhraním z pochopitelných důvodů vyžaduje použití odlišných technik, než je tomu u vývoje dávkových souborů či jednoduchých skriptů. V mnoha frameworcích určených pro tvorbu grafického uživatelského rozhraní se z tohoto důvodu setkáme s konceptem událostí (event) a reakcí na vznik těchto událostí. V případě knihovny gogpu se na událost (určitého typu) reaguje tak, že se zavolá nějaká callback funkce. Příslušné funkce musí být nejdříve zaregistrovány, a to konkrétně v metodách struktury získané výše zmíněným konstruktorem gogpu.NewApp.

Poznámka: některé frameworky vyžadují, aby programátor události explicitně načítal z fronty událostí (event queue). Tuto operaci za nás provádí přímo knihovna gogpu, pokud neurčíme jinak (pro hry je možná výhodnější pracovat přímo s frontou událostí).

Události Draw a Close

V první sadě demonstračních příkladů si vystačíme se dvěma typy událostí:

  1. Draw – požadavek na překreslení obsahu okna aplikace
  2. Close – požadavek na ukončení aplikace

Na tyto události můžeme (vhodným způsobem) reagovat ve funkcích, které jsou zaregistrovány metodami nazvanými OnDraw a OnClose:

func (a *App) OnDraw(fn func(*Context)) *App
    OnDraw sets the callback for rendering each frame. The Context is only
    valid during the callback. If the primary window has been created (after Run
    starts), its draw callback is also updated to stay in sync.
 
 
 
func (a *App) OnClose(fn func()) *App
    OnClose sets the callback invoked when the application is shutting down,
    before the GPU renderer is destroyed. Use this to release GPU resources
    (e.g., ggcanvas.Canvas) that depend on the renderer being alive.
 
    The callback runs on the render thread.

Jednodušší z těchto metod je OnClose, protože se této metodě předává callback funkce bez parametrů i bez návratové hodnoty. V tom nejjednodušším případě příslušná callback funkce pouze zavolá metodu CloseAccelerator (což je asi v tomto případě výhodnější, než se spoléhat na defer). Registrace příslušné callback funkce může vypadat takto:

// callback metoda zavolaná ve chvíli, kdy se okno uzavírá
app.OnClose(func() {
        gg.CloseAccelerator()
})
Poznámka: připomeňme si, že jsme ve výše uvedeném fragmentu zdrojového kódu vytvořili anonymní funkci, která je ovšem současně i uzávěrem (closure). To je důležité a užitečné, neboť takový uzávěr může přistupovat k proměnným definovaným na stejné či vyšší úrovni, než samotný uzávěr – v našem případě tedy může přistupovat k lokálním proměnným funkce main. Stav aplikace tedy můžeme modifikovat přes tyto proměnné, což popravdě není příliš čisté, ovšem výsledné kódy budou poměrně krátké a snadno pochopitelné.

Callback funkci volané v případě, že je nutné překreslit okno, se předává parametr typu gogpu.Context, který – jak dále uvidíme – můžeme využít podobně jako grafický kontext v knihovně gg:

type Context struct {
        // Has unexported fields.
}
    Context provides drawing operations for a single frame. It is only valid
    during the OnDraw callback and should not be stored.
 
    In multi-window mode, each Context targets a specific window surface.
    The surface field points to the target RenderTarget; if nil, the renderer's
    primary surface is used (single-window backward compat).

I když to není zcela korektní, je možné ponechat tělo této callback funkce prázdné popř. do něj pouze přidat výpis nějaké zprávy na standardní výstup:

// callback metoda zavolaná ve chvíli, kdy se má překreslit okno
app.OnDraw(func(dc *gogpu.Context) {
        fmt.Println("Redraw....")
})
Poznámka: aplikace se ovšem nemusí v takovém případě chovat zcela podle očekávání (někteří správci oken ji mohou odmítnout zavřít). Toto chování opravíme v dnešním druhém demonstračním příkladu

První demonstrační příklad: aplikace s prázdným oknem

Konečně se dostáváme ke zdrojovému kódu dnešního prvního demonstračního příkladu. Po jeho překladu a spuštění by se mělo vykreslit okno, jehož plocha může mít v podstatě libovolný obsah (není totiž překreslena). Typicky je možné v ploše okna vidět původní obsah obrazovky:

Okno, jehož plocha se nepřekresluje a proto vidíme původní obsah desktopu.

Obrázek 1: Plocha okna se nepřekresluje a proto vidíme původní obsah desktopu pod oknem.

Autor: tisnik, podle licence: Rights Managed

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

package main
 
import (
        "fmt"
 
        "github.com/gogpu/gg"
        _ "github.com/gogpu/gg/gpu"
        "github.com/gogpu/gogpu"
)
 
func main() {
        // rozměry okna
        const width = 400
        const height = 300
 
        // inicializace aplikace s GUI
        app := gogpu.NewApp(gogpu.DefaultConfig().
                WithTitle("GoGPU + gg").
                WithSize(width, height))
 
        // callback metoda zavolaná ve chvíli, kdy se má překreslit okno
        app.OnDraw(func(dc *gogpu.Context) {
                fmt.Println("Redraw....")
        })
 
        // callback metoda zavolaná ve chvíli, kdy se okno uzavírá
        app.OnClose(func() {
                gg.CloseAccelerator()
        })
 
        // spuštění aplikace
        app.Run()
}
Poznámka: povšimněte si, že jsme importovali balíček github.com/gogpu/gg/gpu, který ovšem není ve zdrojovém kódu explicitně využit (a proto je nutné ho importovat s využitím zástupného symbolu _). Je tomu tak z toho důvodu, že musíme do výsledného binárního souboru nějakým důvodem „nacpat“ ovladače použité pro vykreslování. Prozatím si vystačíme se SW renderingem, ovšem později si ukážeme, že lze využít i vykreslování akcelerované přes GPU.

Vykreslení pozadí zvolené barvy v ploše okna

Aby se aplikace chovala zcela korektně, musí při požadavku na překreslení okna skutečně toto překreslení provést. K tomuto účelu si připravíme pomocnou funkci nazvanou renderFrame (protože vlastně vždy vykreslíme jeden snímek okna). Této funkci se bude předávat grafický kontext a taktéž šířka a výška plochy, která se má překreslit:

func renderFrame(dc *gg.Context, width, height int) {
     ...
     ...
     ...
}

Grafický kontext již dobře známe z popisu knihovny gg. Můžeme tedy v kontextu nastavit barvu vykreslování (RGB=000) a vykreslit vyplněný obdélník o stejných rozměrech, jaké má plocha okna:

func renderFrame(dc *gg.Context, width, height int) {
        // barva pozadí
        dc.SetRGBA(0.0, 0.0, 0.0, 1)
 
        // obdélník s plochou odpovídající oknu
        dc.DrawRectangle(0, 0, float64(width), float64(height))
        dc.Fill()
}
Poznámka: rozměry okna jsou předávány formou celých čísel, kdežto při vykreslování do grafického kontextu jsou souřadnice zadávány hodnotami s plovoucí řádovou čárkou. Z tohoto důvodu se v dnešních demonstračních příkladech často setkáme s nutností explicitního přetypování.

Zbývá nám funkci renderFrame zavolat ve chvíli, kdy vznikne událost typu Draw. Teoreticky může celý kód vypadat následovně – získáme rozměry kreslicího plátna, zavoláme metodu Draw, které se předá reference na naši funkci renderFrame a nakonec se zavolá druhá metoda Render, která vykreslení dokončí:

// callback metoda zavolaná ve chvíli, kdy se má překreslit okno
app.OnDraw(func(dc *gogpu.Context) {
        cw, ch := canvas.Size()
 
        canvas.Draw(func(cc *gg.Context) {
                renderFrame(cc, cw, ch)
        })
 
        canvas.Render(dc.RenderTarget())
})

Ovšem ve skutečnosti to zase tak jednoduché není, protože musíme nějakým způsobem získat objekt typu ggcanvas.Canvas. Teoreticky je možné neustále, tedy při každém překreslení okna, canvas získávat tímto kódem:

provider := app.GPUContextProvider()
w, h := dc.Width(), dc.Height()
canvas, _ = ggcanvas.New(provider, w, h)

To ovšem znamená neustálé inicializace objektu, zatížení GC atd. Proto je výhodnější mít ve funkci main připravenou proměnnou typu ggcanvas.Canvas, která se naplní před prvním překreslením okna a poté již není nutné opakovat její inicializaci:

var canvas *ggcanvas.Canvas

Callback funkce zaregistrovaná přes app.OnDraw je tedy v praxi poněkud komplikovaná (ovšem již nebude nutné ji později měnit):

// callback metoda zavolaná ve chvíli, kdy se má překreslit okno
app.OnDraw(func(dc *gogpu.Context) {
        if canvas == nil {
                provider := app.GPUContextProvider()
                w, h := dc.Width(), dc.Height()
                canvas, _ = ggcanvas.New(provider, w, h)
        }
 
        cw, ch := canvas.Size()
 
        canvas.Draw(func(cc *gg.Context) {
                renderFrame(cc, cw, ch)
        })
 
        canvas.Render(dc.RenderTarget())
})
Okno, jehož plocha je vymazána černou barvou.

Obrázek 2: Plocha okna se korektně překresluje černou barvou.

Autor: tisnik, podle licence: Rights Managed

Úplný zdrojový kód demonstračního příkladu, který inicializuje pozadí okna

Úplný zdrojový kód dnešního druhého demonstračního příkladu, který korektně inicializuje pozadí okna (a samozřejmě provede i jeho překreslení, pokud je to nutné), vypadá následovně:

package main
 
import (
        "fmt"
 
        "github.com/gogpu/gg"
        _ "github.com/gogpu/gg/gpu"
        "github.com/gogpu/gg/integration/ggcanvas"
        "github.com/gogpu/gogpu"
)
 
func main() {
        // rozměry okna
        const width = 400
        const height = 300
 
        // inicializace aplikace s GUI
        app := gogpu.NewApp(gogpu.DefaultConfig().
                WithTitle("GoGPU + gg").
                WithSize(width, height))
 
        var canvas *ggcanvas.Canvas
 
        // callback metoda zavolaná ve chvíli, kdy se má překreslit okno
        app.OnDraw(func(dc *gogpu.Context) {
                fmt.Println("Redraw....")
                if canvas == nil {
                        provider := app.GPUContextProvider()
                        w, h := dc.Width(), dc.Height()
                        canvas, _ = ggcanvas.New(provider, w, h)
                }
 
                cw, ch := canvas.Size()
 
                canvas.Draw(func(cc *gg.Context) {
                        renderFrame(cc, cw, ch)
                })
 
                canvas.Render(dc.RenderTarget())
        })
 
        // callback metoda zavolaná ve chvíli, kdy se okno uzavírá
        app.OnClose(func() {
                fmt.Println("Closing")
                gg.CloseAccelerator()
        })
 
        // spuštění aplikace
        app.Run()
}
 
func renderFrame(dc *gg.Context, width, height int) {
        // barva pozadí
        dc.SetRGBA(0.0, 0.0, 0.0, 1)
 
        // obdélník s plochou odpovídající oknu
        dc.DrawRectangle(0, 0, float64(width), float64(height))
        dc.Fill()
}

Povolení kontinuálního překreslování okna

Při inicializaci aplikace s grafickým uživatelským rozhraním jsme prozatím postupovali následovně:

app := gogpu.NewApp(gogpu.DefaultConfig().
        WithTitle("GoGPU + gg").
        WithSize(width, height))

Takto inicializovaná aplikace vyvolávala událost typu Draw jen tehdy, pokud bylo skutečně nutné provést překreslení okna (nebo jeho části). Ovšem existují situace, ve kterých je například vyžadováno zobrazení animace, obnovení obsahu nějakého dashboardu atd. V takových případech může být výhodnější vynutit si kontinuální překreslování okna. To je zajištěno zavoláním metody WithContinuousRender:

// inicializace aplikace s GUI
app := gogpu.NewApp(gogpu.DefaultConfig().
        WithTitle("GoGPU + gg").
        WithSize(width, height).
        WithContinuousRender(true))
Okno, jehož plocha je periodicky mazána.

Obrázek 3: Plocha okna se periodicky překresluje černou barvou.

Autor: tisnik, podle licence: Rights Managed

Pokud si přeložíte a spustíte následující demonstrační příklad s výše uvedenou úpravou, bude sice obsah okna zdánlivě stále stejný, ale při pohledu na terminál lze snadno zjistit, že se neustále vypisuje zpráva „Redraw…“ a tudíž se neustále volá obslužná callback funkce pro událost typu Draw:

package main
 
import (
        "fmt"
 
        "github.com/gogpu/gg"
        _ "github.com/gogpu/gg/gpu"
        "github.com/gogpu/gg/integration/ggcanvas"
        "github.com/gogpu/gogpu"
)
 
func main() {
        // rozměry okna
        const width = 400
        const height = 300
 
        // inicializace aplikace s GUI
        app := gogpu.NewApp(gogpu.DefaultConfig().
                WithTitle("GoGPU + gg").
                WithSize(width, height).
                WithContinuousRender(true))
 
        var canvas *ggcanvas.Canvas
 
        // callback metoda zavolaná ve chvíli, kdy se má překreslit okno
        app.OnDraw(func(dc *gogpu.Context) {
                fmt.Println("Redraw....")
                if canvas == nil {
                        provider := app.GPUContextProvider()
                        w, h := dc.Width(), dc.Height()
                        canvas, _ = ggcanvas.New(provider, w, h)
                }
 
                cw, ch := canvas.Size()
 
                canvas.Draw(func(cc *gg.Context) {
                        renderFrame(cc, cw, ch)
                })
 
                canvas.Render(dc.RenderTarget())
        })
 
        // callback metoda zavolaná ve chvíli, kdy se okno uzavírá
        app.OnClose(func() {
                fmt.Println("Closing")
                gg.CloseAccelerator()
        })
 
        // spuštění aplikace
        app.Run()
}
 
func renderFrame(dc *gg.Context, width, height int) {
        // barva pozadí
        dc.SetRGBA(0.0, 0.0, 0.0, 1)
 
        // obdélník s plochou odpovídající oknu
        dc.DrawRectangle(0, 0, float64(width), float64(height))
        dc.Fill()
}

Využití canvasu při kreslení do okna: vykreslení obrysu obrazce zvolenou barvou

Vzhledem k tomu, že je kreslení do plochy okna prováděno přes canvas, můžeme upravit všechny demonstrační příklady z úvodního článku o knihovně gg do podoby, ve které se obrázek namísto uložení na disk vykreslí do okna. Pochopitelně zde všechny tyto příklady nebudou přepsány, ale ukážeme si alespoň tři z nich (protože na ně navážeme v dalším textu).

Nejdříve je (v naprosté většině případů) nutné překreslit celou plochu okna konstantní barvou. Posléze do plochy okna vykreslíme obrys obdélníku, jehož hrany budou od okrajů okna vzdáleny o čtyřicet pixelů:

func renderFrame(dc *gg.Context, width, height int) {
        // barva pozadí
        dc.SetRGB(0.9, 0.9, 0.7)
 
        // obdélník s plochou odpovídající oknu
        dc.DrawRectangle(0, 0, float64(width), float64(height))
        dc.Fill()
 
        const border = 40
        dc.DrawRectangle(border, border, float64(width)-border*2, float64(height)-border*2)
        dc.SetRGB(0.0, 0.0, 0.0)
 
        // vykreslení obrysu
        dc.Stroke()
}
Vykreslení tvaru s využitím grafického kontextu.

Obrázek 4: Vykreslení grafického obrazce do plochy okna.

Autor: tisnik, podle licence: Rights Managed

Úplný zdrojový kód takto upraveného demonstračního příkladu vypadá následovně:

package main
 
import (
        "fmt"
 
        "github.com/gogpu/gg"
        _ "github.com/gogpu/gg/gpu"
        "github.com/gogpu/gg/integration/ggcanvas"
        "github.com/gogpu/gogpu"
)
 
func main() {
        // rozměry okna
        const width = 400
        const height = 300
 
        // inicializace aplikace s GUI
        app := gogpu.NewApp(gogpu.DefaultConfig().
                WithTitle("GoGPU + gg").
                WithSize(width, height))
 
        var canvas *ggcanvas.Canvas
 
        // callback metoda zavolaná ve chvíli, kdy se má překreslit okno
        app.OnDraw(func(dc *gogpu.Context) {
                fmt.Println("Redraw....")
                if canvas == nil {
                        provider := app.GPUContextProvider()
                        w, h := dc.Width(), dc.Height()
                        canvas, _ = ggcanvas.New(provider, w, h)
                }
 
                cw, ch := canvas.Size()
 
                canvas.Draw(func(cc *gg.Context) {
                        renderFrame(cc, cw, ch)
                })
 
                canvas.Render(dc.RenderTarget())
        })
 
        // callback metoda zavolaná ve chvíli, kdy se okno uzavírá
        app.OnClose(func() {
                fmt.Println("Closing")
                gg.CloseAccelerator()
        })
 
        // spuštění aplikace
        app.Run()
}
 
func renderFrame(dc *gg.Context, width, height int) {
        // barva pozadí
        dc.SetRGB(0.9, 0.9, 0.7)
 
        // obdélník s plochou odpovídající oknu
        dc.DrawRectangle(0, 0, float64(width), float64(height))
        dc.Fill()
 
        const border = 40
        dc.DrawRectangle(border, border, float64(width)-border*2, float64(height)-border*2)
        dc.SetRGB(0.0, 0.0, 0.0)
 
        // vykreslení obrysu
        dc.Stroke()
}

Vykreslení hvězdy do plochy okna: základ pro navazující příklady

V dalším příkladu, který bude později rozšířen, se ve funkci renderFrame nejprve vyplní plocha okna a následně se do středu vykreslí pěticípá hvězda. Prakticky stejný příklad byl ukázán již v předchozím článku, takže si jen v krátkosti ukažme, jak je vykreslení hvězdy realizováno:

customColor := gg.Hex("44aadd")
dc.SetFillBrush(gg.Solid(customColor))
 
dc.SetFillRule(gg.FillRuleEvenOdd)
 
radius := float64(height) / 3
 
w := float64(width)
h := float64(height)
 
// začátek vykreslování cesty
dc.MoveTo(w/2, h/2-radius)
for i := range 4 {
        angle := float64(i+1) * 3.0 * 360 / 5.0
        x := radius * math.Sin(angle*math.Pi/180.0)
        y := radius * math.Cos(angle*math.Pi/180.0)
        dc.LineTo(w/2+x, h/2-y)
}
dc.ClosePath()
 
// vyplnění obrysu
dc.Fill()
Vykreslení a vyplnění složitějšího tvaru s využitím grafického kontextu.

Obrázek 5: Vykreslení grafického obrazce (s výplní) do plochy okna.

Autor: tisnik, podle licence: Rights Managed

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

package main
 
import (
        "fmt"
        "math"
 
        "github.com/gogpu/gg"
        _ "github.com/gogpu/gg/gpu"
        "github.com/gogpu/gg/integration/ggcanvas"
        "github.com/gogpu/gogpu"
)
 
func main() {
        // rozměry okna
        const width = 400
        const height = 300
 
        // inicializace aplikace s GUI
        app := gogpu.NewApp(gogpu.DefaultConfig().
                WithTitle("GoGPU + gg").
                WithSize(width, height))
 
        var canvas *ggcanvas.Canvas
 
        // callback metoda zavolaná ve chvíli, kdy se má překreslit okno
        app.OnDraw(func(dc *gogpu.Context) {
                fmt.Println("Redraw....")
                if canvas == nil {
                        provider := app.GPUContextProvider()
                        w, h := dc.Width(), dc.Height()
                        canvas, _ = ggcanvas.New(provider, w, h)
                }
 
                cw, ch := canvas.Size()
 
                canvas.Draw(func(cc *gg.Context) {
                        renderFrame(cc, cw, ch)
                })
 
                canvas.Render(dc.RenderTarget())
        })
 
        // callback metoda zavolaná ve chvíli, kdy se okno uzavírá
        app.OnClose(func() {
                fmt.Println("Closing")
                gg.CloseAccelerator()
        })
 
        // spuštění aplikace
        app.Run()
}
 
func renderFrame(dc *gg.Context, width, height int) {
        // barva pozadí
        dc.SetRGB(0.9, 0.9, 0.7)
 
        // obdélník s plochou odpovídající oknu
        dc.DrawRectangle(0, 0, float64(width), float64(height))
        dc.Fill()
 
        customColor := gg.Hex("44aadd")
        dc.SetFillBrush(gg.Solid(customColor))
 
        dc.SetFillRule(gg.FillRuleEvenOdd)
 
        radius := float64(height) / 3
 
        w := float64(width)
        h := float64(height)
 
        // začátek vykreslování cesty
        dc.MoveTo(w/2, h/2-radius)
        for i := range 4 {
                angle := float64(i+1) * 3.0 * 360 / 5.0
                x := radius * math.Sin(angle*math.Pi/180.0)
                y := radius * math.Cos(angle*math.Pi/180.0)
                dc.LineTo(w/2+x, h/2-y)
        }
        dc.ClosePath()
 
        // vyplnění obrysu
        dc.Fill()

}

Další základ pro další příklady: vykreslení Bézierových křivek i s jejich řídicími body

Posledním „zahřívacím“ příkladem, který v rámci dalších kapitol rozšíříme, je příklad, který do plochy okna vykreslí dvojici Bézierových kubických křivek i s jejich řídicími body. Připomeňme si, že Bézierovy kubické křivky se do cesty (path) vloží metodou CubicTo. První bod křivky je již součástí cesty a tudíž se nezapisuje:

dc.CubicTo(x1, y1, x2, y2, x3, y3)
Složitější scéna, která se skládá z dvojice Bézierových kubických křivek se zvýrazněním jejich řídicích bodů.

Obrázek 6: Vykreslení složitějšího grafického obrazce do plochy okna.   

Autor: tisnik, podle licence: Rights Managed

Strukturu celého příkladu již známe z úvodní dvojice článků, takže si jen v krátkosti uvedeme celý jeho zdrojový kód:

package main
 
import (
        "fmt"
 
        "github.com/gogpu/gg"
        _ "github.com/gogpu/gg/gpu"
        "github.com/gogpu/gg/integration/ggcanvas"
        "github.com/gogpu/gogpu"
)
 
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 main() {
        // rozměry okna
        const width = 400
        const height = 300
 
        // inicializace aplikace s GUI
        app := gogpu.NewApp(gogpu.DefaultConfig().
                WithTitle("GoGPU + gg").
                WithSize(width, height))
 
        var canvas *ggcanvas.Canvas
 
        // callback metoda zavolaná ve chvíli, kdy se má překreslit okno
        app.OnDraw(func(dc *gogpu.Context) {
                fmt.Println("Redraw....")
                if canvas == nil {
                        provider := app.GPUContextProvider()
                        w, h := dc.Width(), dc.Height()
                        canvas, _ = ggcanvas.New(provider, w, h)
                }
 
                cw, ch := canvas.Size()
 
                canvas.Draw(func(cc *gg.Context) {
                        renderFrame(cc, cw, ch)
                })
 
                canvas.Render(dc.RenderTarget())
        })
 
        // callback metoda zavolaná ve chvíli, kdy se okno uzavírá
        app.OnClose(func() {
                fmt.Println("Closing")
                gg.CloseAccelerator()
        })
 
        // spuštění aplikace
        app.Run()
}
 
func renderFrame(dc *gg.Context, width, height int) {
        // barva pozadí
        dc.SetRGB(0.9, 0.9, 0.7)
 
        // obdélník s plochou odpovídající oknu
        dc.DrawRectangle(0, 0, float64(width), float64(height))
        dc.Fill()
 
        // vykreslení cest
        drawCubicBezier(dc, 50, 220, 50, 50, 160, 220, 160, 50)
        drawCubicBezier(dc, 270, 220, 320, 100, 270, 100, 320, 220)
}

Reakce aplikace na stisk klávesy či více kláves

Aplikace s grafickým uživatelským rozhraním jsou většinou interaktivní, tj. musí nějakým způsobem reagovat na stisk kláves, posun myši, stisk tlačítka myši apod. Nejprve se zaměříme na detekci stisku kláves. Při stisku klávesy (kupodivu) nevznikne událost, protože se předpokládá, že čtení stavu klávesnice bude probíhat v rámci zpracování události Update, tedy v této funkci:

app.OnUpdate(func(dt float64) {
    ...
    ...
    ...
}

Objekt (struktura) typu App poskytuje metodu Input vracející ukazatel na strukturu State, z níž lze vyčíst informaci a stisknutých klávesách (ale i o dalších typech uživatelského vstupu):

func (a *App) Input() *input.State
    Input returns the input state for Ebiten-style polling. This enables
    game-loop style input handling with KeyPressed, KeyJustPressed, etc.

Funkce a metody struktury Input:

package input // import "github.com/gogpu/gogpu/input"
 
type State struct {
        // Has unexported fields.
}
    State holds the current input state. All methods are thread-safe.
 
func New() *State
func (s *State) Keyboard() *KeyboardState
func (s *State) Mouse() *MouseState
func (s *State) Update()

Nás bude v této chvíli nejvíce zajímat metoda Keyboard vracející další strukturu KeyboardState:

package input // import "github.com/gogpu/gogpu/input"
 
type KeyboardState struct {
        // Has unexported fields.
}
    KeyboardState holds keyboard input state. All methods are thread-safe.
 
func (k *KeyboardState) AnyPressed() bool
func (k *KeyboardState) JustPressed(key Key) bool
func (k *KeyboardState) JustReleased(key Key) bool
func (k *KeyboardState) Modifier(mod Modifier) bool
func (k *KeyboardState) Pressed(key Key) bool
func (k *KeyboardState) SetKey(key Key, pressed bool)
func (k *KeyboardState) UpdateFrame()

Z názvů poskytovaných metod je zřejmé, že stisk nějaké klávesy lze zjistit přes metodu Pressed popř. JustPressed. Záleží na tom, zda potřebujeme reagovat jen na událost vznikající při stisku klávesy (tedy jedenkrát), nebo zda již stisknutá klávesa povede k opakování nějaké operace.

Zjištění stisku kláves použitých pro ovládání aplikace

Poměrně jednoduchým postupem lze zajistit, aby aplikace reagovala na stisk kurzorových kláves šipka doleva a šipka doprava. Pokud bude jedna z těchto kláves stlačena, bude se hvězda otáčet příslušným směrem. Povšimněte si, že v tomto případě musíme reagovat nikoli přímo na stisk klávesy (směrem dolů), ale na její stav – pokud totiž kurzorovou šipku podržíme, měla by se hvězda neustále otáčet. Z tohoto důvodu bude stav kurzorových šipek zjišťován metodami Pressed a nikoli JustPressed:

// callback metoda volaná v případě, že se má scéna překreslit
app.OnUpdate(func(dt float64) {
        inp := app.Input()
        const rotationDelta = 1.5
 
        // reakce na stisk šipky doleva
        if inp.Keyboard().Pressed(input.KeyLeft) {
                rotation -= rotationDelta
                // vynucení překreslení okna
                app.RequestRedraw()
        }
 
        // reakce na stisk šipky doprava
        if inp.Keyboard().Pressed(input.KeyRight) {
                rotation += rotationDelta
                // vynucení překreslení okna
                app.RequestRedraw()
        }
})

Samotná programová realizace otáčení může být postavena na manipulaci s transformační maticí (RotateAbout) nebo nepatrnou úpravou výpočtů pro zjištění souřadnic vrcholů hvězdy. Pro zajímavost použijeme druhou možnost:

// začátek vykreslování cesty
dc.MoveTo(
        w/2+radius*math.Sin(rad(rotation)),
        h/2-radius*math.Cos(rad(rotation)))
 
// další segmenty cesty
for i := range 4 {
        angle := float64(i+1)*3.0*360/5.0 + rotation
        x := radius * math.Sin(rad(angle))
        y := radius * math.Cos(rad(angle))
        dc.LineTo(w/2+x, h/2-y)
}
// uzavření cesty
dc.ClosePath()
Interaktivní otáčení hvězdou pomocí kurzorových šipek.

Obrázek 7: Rotace hvězdy stiskem kurzorových kláves.

Autor: tisnik, podle licence: Rights Managed

Interaktivní otáčení hvězdou pomocí kurzorových šipek.

Obrázek 8: Rotace hvězdy stiskem kurzorových kláves.

Autor: tisnik, podle licence: Rights Managed

Demonstrační příklad: rotace hvězdy s využitím kurzorových kláves

Úplný zdrojový kód demonstračního příkladu, který umožní rotaci hvězdy umístěné do středu okna s využitím kurzorových kláves, vypadá následovně. Příklad vlastně ani není příliš dlouhý – má jen cca 115 řádků i s komentáři:

package main
 
import (
        "fmt"
        "math"
 
        "github.com/gogpu/gg"
        _ "github.com/gogpu/gg/gpu"
        "github.com/gogpu/gg/integration/ggcanvas"
        "github.com/gogpu/gogpu"
        "github.com/gogpu/gogpu/input"
)
 
func main() {
        // rozměry okna
        const width = 400
        const height = 300
 
        rotation := 0.0
 
        // inicializace aplikace s GUI
        app := gogpu.NewApp(gogpu.DefaultConfig().
                WithTitle("GoGPU + gg").
                WithSize(width, height))
 
        var canvas *ggcanvas.Canvas
 
        // callback metoda zavolaná ve chvíli, kdy se má překreslit okno
        app.OnDraw(func(dc *gogpu.Context) {
                fmt.Println("Redraw....")
                if canvas == nil {
                        provider := app.GPUContextProvider()
                        w, h := dc.Width(), dc.Height()
                        canvas, _ = ggcanvas.New(provider, w, h)
                }
 
                cw, ch := canvas.Size()
 
                canvas.Draw(func(cc *gg.Context) {
                        renderFrame(cc, cw, ch, rotation)
                })
 
                canvas.Render(dc.RenderTarget())
        })
 
        // callback metoda volaná v případě, že se má scéna překreslit
        app.OnUpdate(func(dt float64) {
                inp := app.Input()
                const rotationDelta = 1.5
 
                // reakce na stisk šipky doleva
                if inp.Keyboard().Pressed(input.KeyLeft) {
                        rotation -= rotationDelta
                        // vynucení překreslení okna
                        app.RequestRedraw()
                }
 
                // reakce na stisk šipky doprava
                if inp.Keyboard().Pressed(input.KeyRight) {
                        rotation += rotationDelta
                        // vynucení překreslení okna
                        app.RequestRedraw()
                }
        })
 
        // callback metoda zavolaná ve chvíli, kdy se okno uzavírá
        app.OnClose(func() {
                fmt.Println("Closing")
                gg.CloseAccelerator()
        })
 
        // spuštění aplikace
        app.Run()
}
 
func rad(angle float64) float64 {
        return angle * math.Pi / 180.0
}
 
func renderFrame(dc *gg.Context, width, height int, rotation float64) {
        // barva pozadí
        dc.SetRGB(0.9, 0.9, 0.7)
 
        // obdélník s plochou odpovídající oknu
        dc.DrawRectangle(0, 0, float64(width), float64(height))
        dc.Fill()
 
        customColor := gg.Hex("44aadd")
        dc.SetFillBrush(gg.Solid(customColor))
 
        dc.SetFillRule(gg.FillRuleEvenOdd)
 
        radius := float64(height) / 3
 
        w := float64(width)
        h := float64(height)
 
        // začátek vykreslování cesty
        dc.MoveTo(
                w/2+radius*math.Sin(rad(rotation)),
                h/2-radius*math.Cos(rad(rotation)))
 
        // další segmenty cesty
        for i := range 4 {
                angle := float64(i+1)*3.0*360/5.0 + rotation
                x := radius * math.Sin(rad(angle))
                y := radius * math.Cos(rad(angle))
                dc.LineTo(w/2+x, h/2-y)
        }
        // uzavření cesty
        dc.ClosePath()
 
        // vyplnění obrysu
        dc.Fill()
}

Demonstrační příklad: změna barvy hvězdy dalšími klávesami

Příklad lze podobným postupem libovolně rozšiřovat. Například je možné přidat změnu barvy hvězdy s využitím kláves Q, W, E a A, S, D – pro každou barvovou složku se používá dvojice pod sebou umístěných kláves:

Interaktivní změna barvy výplně.

Obrázek 9: Interaktivní modifikace barvy hvězdy.

Autor: tisnik, podle licence: Rights Managed

Tento příklad uvádím z toho důvodu, aby bylo zřejmé, jak objem zdrojového kódu narůstá (například v porovnání s odlišným přístupem použitým v knihovně SDL). V Go (a GoGPU) musíme na stisk každé klávesy reagovat ve zvláštním bloku:

// reakce na stisk klávesy Q
if keyboard.Pressed(input.KeyQ) {
        // změna červené barvové složky
        red = math.Min(1.0, red+colorDelta)
        // vynucení překreslení okna
        app.RequestRedraw()
}
 
// reakce na stisk klávesy W
if keyboard.Pressed(input.KeyW) {
        // změna zelené barvové složky
        green = math.Min(1.0, green+colorDelta)
        // vynucení překreslení okna
        app.RequestRedraw()
}
 
// reakce na stisk klávesy E
if keyboard.Pressed(input.KeyE) {
        // změna modré barvové složky
        blue = math.Min(1.0, blue+colorDelta)
        // vynucení překreslení okna
        app.RequestRedraw()
}
 
// reakce na stisk klávesy A
if keyboard.Pressed(input.KeyA) {
        // změna červené barvové složky
        red = math.Max(0.0, red-colorDelta)
        // vynucení překreslení okna
        app.RequestRedraw()
}
 
// reakce na stisk klávesy S
if keyboard.Pressed(input.KeyS) {
        // změna zelené barvové složky
        green = math.Max(0.0, green-colorDelta)
        // vynucení překreslení okna
        app.RequestRedraw()
}
 
// reakce na stisk klávesy D
if keyboard.Pressed(input.KeyD) {
        // změna modré barvové složky
        blue = math.Max(0.0, blue-colorDelta)
        // vynucení překreslení okna
        app.RequestRedraw()
}

Přepis na konstrukci switch-case je kratší jen o několik řádků (ale pravděpodobně čitelnější):

switch {
// reakce na stisk šipky doleva
case keyboard.Pressed(input.KeyLeft):
        rotation -= rotationDelta
        // vynucení překreslení okna
        app.RequestRedraw()
 
// reakce na stisk šipky doprava
case keyboard.Pressed(input.KeyRight):
        rotation += rotationDelta
        // vynucení překreslení okna
        app.RequestRedraw()
 
// reakce na stisk klávesy Q
case keyboard.Pressed(input.KeyQ):
        // změna červené barvové složky
        red = math.Min(1.0, red+colorDelta)
        // vynucení překreslení okna
        app.RequestRedraw()
 
// reakce na stisk klávesy W
case keyboard.Pressed(input.KeyW):
        // změna zelené barvové složky
        green = math.Min(1.0, green+colorDelta)
        // vynucení překreslení okna
        app.RequestRedraw()
 
// reakce na stisk klávesy E
case keyboard.Pressed(input.KeyE):
        // změna modré barvové složky
        blue = math.Min(1.0, blue+colorDelta)
        // vynucení překreslení okna
        app.RequestRedraw()
 
// reakce na stisk klávesy A
case keyboard.Pressed(input.KeyA):
        // změna červené barvové složky
        red = math.Max(0.0, red-colorDelta)
        // vynucení překreslení okna
        app.RequestRedraw()
 
// reakce na stisk klávesy S
case keyboard.Pressed(input.KeyS):
        // změna zelené barvové složky
        green = math.Max(0.0, green-colorDelta)
        // vynucení překreslení okna
        app.RequestRedraw()
 
// reakce na stisk klávesy D
case keyboard.Pressed(input.KeyD):
        // změna modré barvové složky
        blue = math.Max(0.0, blue-colorDelta)
        // vynucení překreslení okna
        app.RequestRedraw()
 
// reakce na stisk klávesy Escape
case inp.Keyboard().Pressed(input.KeyEscape):
        app.Quit()

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

package main
 
import (
        "fmt"
        "math"
 
        "github.com/gogpu/gg"
        _ "github.com/gogpu/gg/gpu"
        "github.com/gogpu/gg/integration/ggcanvas"
        "github.com/gogpu/gogpu"
        "github.com/gogpu/gogpu/input"
)
 
func main() {
        // rozměry okna
        const width = 400
        const height = 300
 
        rotation := 0.0
 
        // inicializace aplikace s GUI
        app := gogpu.NewApp(gogpu.DefaultConfig().
                WithTitle("GoGPU + gg").
                WithSize(width, height))
 
        var canvas *ggcanvas.Canvas
        var red = 0.3
        var green = 0.7
        var blue = 0.9
 
        // callback metoda zavolaná ve chvíli, kdy se má překreslit okno
        app.OnDraw(func(dc *gogpu.Context) {
                if canvas == nil {
                        provider := app.GPUContextProvider()
                        w, h := dc.Width(), dc.Height()
                        canvas, _ = ggcanvas.New(provider, w, h)
                }
 
                cw, ch := canvas.Size()
 
                canvas.Draw(func(cc *gg.Context) {
                        renderFrame(cc, cw, ch, rotation, red, green, blue)
                })
 
                canvas.Render(dc.RenderTarget())
        })
 
        // callback metoda volaná v případě, že se má scéna překreslit
        app.OnUpdate(func(dt float64) {
                inp := app.Input()
                const rotationDelta = 1.5
                const colorDelta = 0.02
 
                keyboard := inp.Keyboard()
 
                // reakce na stisk šipky doleva
                if keyboard.Pressed(input.KeyLeft) {
                        rotation -= rotationDelta
                        // vynucení překreslení okna
                        app.RequestRedraw()
                }
 
                // reakce na stisk šipky doprava
                if keyboard.Pressed(input.KeyRight) {
                        rotation += rotationDelta
                        // vynucení překreslení okna
                        app.RequestRedraw()
                }
 
                // reakce na stisk klávesy Q
                if keyboard.Pressed(input.KeyQ) {
                        // změna červené barvové složky
                        red = math.Min(1.0, red+colorDelta)
                        // vynucení překreslení okna
                        app.RequestRedraw()
                }
 
                // reakce na stisk klávesy W
                if keyboard.Pressed(input.KeyW) {
                        // změna zelené barvové složky
                        green = math.Min(1.0, green+colorDelta)
                        // vynucení překreslení okna
                        app.RequestRedraw()
                }
 
                // reakce na stisk klávesy E
                if keyboard.Pressed(input.KeyE) {
                        // změna modré barvové složky
                        blue = math.Min(1.0, blue+colorDelta)
                        // vynucení překreslení okna
                        app.RequestRedraw()
                }
 
                // reakce na stisk klávesy A
                if keyboard.Pressed(input.KeyA) {
                        // změna červené barvové složky
                        red = math.Max(0.0, red-colorDelta)
                        // vynucení překreslení okna
                        app.RequestRedraw()
                }
 
                // reakce na stisk klávesy S
                if keyboard.Pressed(input.KeyS) {
                        // změna zelené barvové složky
                        green = math.Max(0.0, green-colorDelta)
                        // vynucení překreslení okna
                        app.RequestRedraw()
                }
 
                // reakce na stisk klávesy D
                if keyboard.Pressed(input.KeyD) {
                        // změna modré barvové složky
                        blue = math.Max(0.0, blue-colorDelta)
                        // vynucení překreslení okna
                        app.RequestRedraw()
                }
 
                // reakce na stisk klávesy Escape
                if inp.Keyboard().Pressed(input.KeyEscape) {
                        app.Quit()
                }
        })
 
        // callback metoda zavolaná ve chvíli, kdy se okno uzavírá
        app.OnClose(func() {
                fmt.Println("Closing")
                gg.CloseAccelerator()
        })
 
        // spuštění aplikace
        app.Run()
}
 
func rad(angle float64) float64 {
        return angle * math.Pi / 180.0
}
 
func renderFrame(dc *gg.Context, width, height int, rotation, red, green, blue float64) {
        // barva pozadí
        dc.SetRGB(0.9, 0.9, 0.7)
 
        // obdélník s plochou odpovídající oknu
        dc.DrawRectangle(0, 0, float64(width), float64(height))
        dc.Fill()
 
        // výpočet barvy hvězdy
        customColor := gg.RGB(red, green, blue)
        dc.SetFillBrush(gg.Solid(customColor))
 
        dc.SetFillRule(gg.FillRuleEvenOdd)
 
        radius := float64(height) / 3
 
        w := float64(width)
        h := float64(height)
 
        // začátek vykreslování cesty
        dc.MoveTo(
                w/2+radius*math.Sin(rad(rotation)),
                h/2-radius*math.Cos(rad(rotation)))
 
        // další segmenty cesty
        for i := range 4 {
                angle := float64(i+1)*3.0*360/5.0 + rotation
                x := radius * math.Sin(rad(angle))
                y := radius * math.Cos(rad(angle))
                dc.LineTo(w/2+x, h/2-y)
        }
        // uzavření cesty
        dc.ClosePath()
 
        // vyplnění obrysu
        dc.Fill()
}

Přečtení aktuální pozice kurzoru myši popř. detekce stisku tlačítek myši

Poměrně jednoduchá a přímočará je práce s myší. Při obsluze události Update získáme objekt (strukturu) typu MouseState následujícím způsobem:

inp := app.Input()
 
mouseState := inp.Mouse()

Tato struktura nabízí sadu metod, které dokážou zjistit pozici kurzoru myši, vypočítat relativní pohyb myši, zjistit stav tlačítek myši atd. Popis struktury MouseState lze pochopitelně získat v nápovědě:

type MouseState struct {
        // Has unexported fields.
}
    MouseState holds mouse input state. All methods are thread-safe.
 
func (m *MouseState) Delta() (dx, dy float32)
func (m *MouseState) JustPressed(button MouseButton) bool
func (m *MouseState) JustReleased(button MouseButton) bool
func (m *MouseState) Position() (x, y float32)
func (m *MouseState) Pressed(button MouseButton) bool
func (m *MouseState) Scroll() (x, y float32)
func (m *MouseState) SetButton(button MouseButton, pressed bool)
func (m *MouseState) SetPosition(x, y float32)
func (m *MouseState) SetScroll(x, y float32)
func (m *MouseState) UpdateFrame()
func (m *MouseState) X() float32
func (m *MouseState) Y() float32

V navazujících příkladech nám budou postačovat metody Pressed, X a Y.

Demonstrační příklad: posun řídicích bodů Bézierových kubických křivek

V předchozích kapitolách byl ukázán příklad, který vykreslil dvojici Bézierových kubických křivek. Relativně snadno lze do tohoto kódu doimplementovat editaci těchto křivek, která se provádí posunem řídicích bodů. Po stisku levého tlačítka myši se získají souřadnice kurzoru myši a následně se nalezne nejbližší řídicí bod. Tento bod je posunut na pozici kurzoru (to je sice značně primitivní způsob, ovšem je ho možné realizovat poměrně přímočarým způsobem:

func findNearestControlPoint(controlPoints []Point, x, y float64) int {
        nearestIndex := 0
        nearestDistance := math.MaxFloat64
        for i, point := range controlPoints {
                distance := math.Hypot(point.X-x, point.Y-y)
                if distance < nearestDistance {
                        nearestDistance = distance
                        nearestIndex = i
                }
        }
        return nearestIndex
}
Interaktivní posun řídicích bodů Bézierových křivek.

Obrázek 10: Interaktivní změna tvaru Bézierových křivek.

Autor: tisnik, podle licence: Rights Managed

Interaktivní posun řídicích bodů Bézierových křivek.

Obrázek 11: Interaktivní změna tvaru Bézierových křivek.

Autor: tisnik, podle licence: Rights Managed

Opět následuje úplný výpis zdrojového kódu tohoto demonstračního příkladu:

package main
 
import (
        "fmt"
        "math"
 
        "github.com/gogpu/gg"
        _ "github.com/gogpu/gg/gpu"
        "github.com/gogpu/gg/integration/ggcanvas"
        "github.com/gogpu/gogpu"
        "github.com/gogpu/gogpu/input"
)
 
type Point struct {
        X float64
        Y float64
}
 
func drawCubicBezier(dc *gg.Context,
        p0, p1, p2, p3 Point) {
 
        dc.SetRGBA(1.0, 0.5, 0.5, 1.0)
        dc.DrawLine(p0.X, p0.Y, p1.X, p1.Y)
        dc.Stroke()
        dc.DrawLine(p1.X, p1.Y, p2.X, p2.Y)
        dc.Stroke()
        dc.DrawLine(p2.X, p2.Y, p3.X, p3.Y)
        dc.Stroke()
 
        dc.SetRGBA(0.0, 0.0, 0.0, 1.0)
        dc.MoveTo(p0.X, p0.Y)
        dc.CubicTo(p1.X, p1.Y, p2.X, p2.Y, p3.X, p3.Y)
        dc.Stroke()
 
        dc.SetRGBA(0.2, 0.2, 1.0, 1.0)
 
        dc.DrawCircle(p0.X, p0.Y, 3)
        dc.Stroke()
 
        dc.DrawCircle(p1.X, p1.Y, 3)
        dc.Stroke()
 
        dc.DrawCircle(p2.X, p2.Y, 3)
        // vykreslení
        dc.Stroke()
 
        dc.DrawCircle(p2.X, p2.Y, 3)
        // vykreslení
        dc.Stroke()
 
        dc.DrawCircle(p3.X, p3.Y, 3)
        // vykreslení
        dc.Stroke()
}
 
func main() {
        // rozměry okna
        const width = 400
        const height = 300
 
        // inicializace aplikace s GUI
        app := gogpu.NewApp(gogpu.DefaultConfig().
                WithTitle("GoGPU + gg").
                WithSize(width, height))
 
        var canvas *ggcanvas.Canvas
 
        // všechny řídicí body obou Bézierových kubik
        controlPoints := []Point{
                Point{50, 220},
                Point{50, 50},
                Point{160, 220},
                Point{160, 50},
                Point{270, 220},
                Point{320, 100},
                Point{270, 100},
                Point{320, 220}}
 
        // callback metoda zavolaná ve chvíli, kdy se má překreslit okno
        app.OnDraw(func(dc *gogpu.Context) {
                if canvas == nil {
                        provider := app.GPUContextProvider()
                        w, h := dc.Width(), dc.Height()
                        canvas, _ = ggcanvas.New(provider, w, h)
                }
 
                cw, ch := canvas.Size()
 
                canvas.Draw(func(cc *gg.Context) {
                        renderFrame(cc, cw, ch, controlPoints)
                })
 
                canvas.Render(dc.RenderTarget())
        })
 
        // callback metoda volaná v případě, že se má scéna překreslit
        app.OnUpdate(func(dt float64) {
                inp := app.Input()
 
                // reakce na stisk levého tlačítka myši
                if inp.Mouse().Pressed(input.MouseButtonLeft) {
                        x, y := inp.Mouse().Position()
                        i := findNearestControlPoint(controlPoints, float64(x), float64(y))
                        // nastavení nových souřadnic
                        controlPoints[i].X = float64(x)
                        controlPoints[i].Y = float64(y)
                        // vynucení překreslení okna
                        app.RequestRedraw()
                }
        })
 
        // callback metoda zavolaná ve chvíli, kdy se okno uzavírá
        app.OnClose(func() {
                fmt.Println("Closing")
                gg.CloseAccelerator()
        })
 
        // spuštění aplikace
        app.Run()
}
 
func renderFrame(dc *gg.Context, width, height int, cp []Point) {
        // barva pozadí
        dc.SetRGB(0.9, 0.9, 0.7)
 
        // obdélník s plochou odpovídající oknu
        dc.DrawRectangle(0, 0, float64(width), float64(height))
        dc.Fill()
 
        // vykreslení cest
        drawCubicBezier(dc, cp[0], cp[1], cp[2], cp[3])
        drawCubicBezier(dc, cp[4], cp[5], cp[6], cp[7])
}
 
func findNearestControlPoint(controlPoints []Point, x, y float64) int {
        nearestIndex := 0
        nearestDistance := math.MaxFloat64
        for i, point := range controlPoints {
                distance := math.Hypot(point.X-x, point.Y-y)
                if distance < nearestDistance {
                        nearestDistance = distance
                        nearestIndex = i
                }
        }
        return nearestIndex
}

Demonstrační příklad: jednoduchý kreslicí program

Snad ještě jednodušší je implementace jednoduchého kreslicího programu. Levým tlačítkem myši se postupně vytváří kresba složená z na sebe navazujících úseček. Koncové body úseček jsou ukládány do řezu:

// body spojené úsečkami
points := []Point{}

Stiskem levého tlačítka myši se do řezu vloží další bod a provede se překreslení celé scény:

// reakce na stisk levého tlačítka myši
if inp.Mouse().Pressed(input.MouseButtonLeft) {
        x, y := inp.Mouse().Position()
        point := Point{X: float64(x), Y: float64(y)}
        points = append(points, point)
        // vynucení překreslení okna
        app.RequestRedraw()
}
Kreslení domečku v grafickém editoru.

Obrázek 12: Postupné kreslení domečku v grafickém editoru.

Autor: tisnik, podle licence: Rights Managed

Kreslení domečku v grafickém editoru.

Obrázek 13: Postupné kreslení domečku v grafickém editoru.

Autor: tisnik, podle licence: Rights Managed

Úplný zdrojový kód kreslicího programu vypadá takto:

package main
 
import (
        "fmt"
 
        "github.com/gogpu/gg"
        _ "github.com/gogpu/gg/gpu"
        "github.com/gogpu/gg/integration/ggcanvas"
        "github.com/gogpu/gogpu"
        "github.com/gogpu/gogpu/input"
)
 
type Point struct {
        X float64
        Y float64
}
 
func drawPath(dc *gg.Context,
        points []Point) {
 
        dc.SetRGBA(0.0, 0.0, 0.0, 1.0)
 
        for i := 0; i < len(points)-1; i++ {
                dc.DrawLine(
                        points[i].X, points[i].Y,
                        points[i+1].X, points[i+1].Y,
                )
        }
        dc.Stroke()
}
 
func main() {
        // rozměry okna
        const width = 400
        const height = 300
 
        // inicializace aplikace s GUI
        app := gogpu.NewApp(gogpu.DefaultConfig().
                WithTitle("GoGPU + gg").
                WithSize(width, height))
 
        var canvas *ggcanvas.Canvas
 
        // body spojené úsečkami
        points := []Point{}
 
        // callback metoda zavolaná ve chvíli, kdy se má překreslit okno
        app.OnDraw(func(dc *gogpu.Context) {
                if canvas == nil {
                        provider := app.GPUContextProvider()
                        w, h := dc.Width(), dc.Height()
                        canvas, _ = ggcanvas.New(provider, w, h)
                }
 
                cw, ch := canvas.Size()
 
                canvas.Draw(func(cc *gg.Context) {
                        renderFrame(cc, cw, ch, points)
                })
 
                canvas.Render(dc.RenderTarget())
        })
 
        // callback metoda volaná v případě, že se má scéna překreslit
        app.OnUpdate(func(dt float64) {
                inp := app.Input()
 
                // reakce na stisk levého tlačítka myši
                if inp.Mouse().Pressed(input.MouseButtonLeft) {
                        x, y := inp.Mouse().Position()
                        point := Point{X: float64(x), Y: float64(y)}
                        points = append(points, point)
                        // vynucení překreslení okna
                        app.RequestRedraw()
                }
        })
 
        // callback metoda zavolaná ve chvíli, kdy se okno uzavírá
        app.OnClose(func() {
                fmt.Println("Closing")
                gg.CloseAccelerator()
        })
 
        // spuštění aplikace
        app.Run()
}
 
func renderFrame(dc *gg.Context, width, height int, points []Point) {
        // barva pozadí
        dc.SetRGB(0.9, 0.9, 0.7)
 
        // obdélník s plochou odpovídající oknu
        dc.DrawRectangle(0, 0, float64(width), float64(height))
        dc.Fill()
 
        // vykreslení cest
        drawPath(dc, points)
}

Vylepšení: kreslení „gumové čáry“ sledující pohyb kurzoru

Většina grafických editorů při kreslení úseček zobrazuje tzv. gumovou čáru mezi naposledy nakresleným segmentem a pozicí kurzoru myši. Čára pochopitelně sleduje pohyb myši. Tuto funkcionalitu je velmi snadné přidat i do našeho editoru. Aktuální pozici kurzoru myši si zapamatujeme ve struktuře currentPoint:

// vybrané koncové body
points := []Point{}
 
// aktuální bod
currentPoint := Point{X: 0, Y: 0}

Následně se ve funkci provádějící vykreslování scény tato „gumová čára“ bude neustále překreslovat (odlišnou barvou), ovšem pouze za předpokladu, že již existuje alespoň jeden nakreslený segment. Navíc ještě na pozici kurzoru myši vykreslíme malé kolečko:

// barva vykreslení
dc.SetRGBA(0.8, 0.0, 0.0, 1.0)
dc.DrawCircle(currentPoint.X, currentPoint.Y, 4)
 
// kreslení gumové čáry
if len(points) > 0 {
        lastPointIndex := len(points) - 1
        dc.DrawLine(
                points[lastPointIndex].X, points[lastPointIndex].Y,
                currentPoint.X, currentPoint.Y)
        dc.Stroke()
}

Výsledek vlastně ani není špatný – na program, který má jen 120 řádků včetně komentářů:

Školení Zabbix

Kreslení domečku v grafickém editoru.

Obrázek 14: Postupné kreslení domečku v grafickém editoru. Červeně je zobrazena úsečka ovládaná myší.

Autor: tisnik, podle licence: Rights Managed

Kreslení domečku v grafickém editoru.

Obrázek 15: Postupné kreslení domečku v grafickém editoru. Červeně je zobrazena úsečka ovládaná myší.

Autor: tisnik, podle licence: Rights Managed

Zdrojový kód dnešního posledního demonstračního příkladu vypadá následovně:

package main
 
import (
        "fmt"
 
        "github.com/gogpu/gg"
        _ "github.com/gogpu/gg/gpu"
        "github.com/gogpu/gg/integration/ggcanvas"
        "github.com/gogpu/gogpu"
        "github.com/gogpu/gogpu/input"
)
 
type Point struct {
        X float64
        Y float64
}
 
func drawPath(dc *gg.Context,
        points []Point, currentPoint Point) {
 
        dc.SetRGBA(0.0, 0.0, 0.0, 1.0)
 
        for i := 0; i < len(points)-1; i++ {
                dc.DrawLine(
                        points[i].X, points[i].Y,
                        points[i+1].X, points[i+1].Y,
                )
        }
        dc.Stroke()
 
        // barva vykreslení
        dc.SetRGBA(0.8, 0.0, 0.0, 1.0)
        dc.DrawCircle(currentPoint.X, currentPoint.Y, 4)
 
        // kreslení gumové čáry
        if len(points) > 0 {
                lastPointIndex := len(points) - 1
                dc.DrawLine(
                        points[lastPointIndex].X, points[lastPointIndex].Y,
                        currentPoint.X, currentPoint.Y)
                dc.Stroke()
        }
}
 
func main() {
        // rozměry okna
        const width = 400
        const height = 300
 
        // inicializace aplikace s GUI
        app := gogpu.NewApp(gogpu.DefaultConfig().
                WithTitle("GoGPU + gg").
                WithSize(width, height))
 
        var canvas *ggcanvas.Canvas
 
        // vybrané koncové body
        points := []Point{}
 
        // aktuální bod
        currentPoint := Point{X: 0, Y: 0}
 
        // callback metoda zavolaná ve chvíli, kdy se má překreslit okno
        app.OnDraw(func(dc *gogpu.Context) {
                if canvas == nil {
                        provider := app.GPUContextProvider()
                        w, h := dc.Width(), dc.Height()
                        canvas, _ = ggcanvas.New(provider, w, h)
                }
 
                cw, ch := canvas.Size()
 
                canvas.Draw(func(cc *gg.Context) {
                        renderFrame(cc, cw, ch, points, currentPoint)
                })
 
                canvas.Render(dc.RenderTarget())
        })
 
        // callback metoda volaná v případě, že se má scéna překreslit
        app.OnUpdate(func(dt float64) {
                inp := app.Input()
 
                // reakce na stisk levého tlačítka myši
                if inp.Mouse().Pressed(input.MouseButtonLeft) {
                        x, y := inp.Mouse().Position()
                        point := Point{X: float64(x), Y: float64(y)}
                        points = append(points, point)
                        // vynucení překreslení okna
                        app.RequestRedraw()
                } else {
                        x, y := inp.Mouse().Position()
                        currentPoint.X = float64(x)
                        currentPoint.Y = float64(y)
                        // vynucení překreslení okna
                        app.RequestRedraw()
                }
        })
 
        // callback metoda zavolaná ve chvíli, kdy se okno uzavírá
        app.OnClose(func() {
                fmt.Println("Closing")
                gg.CloseAccelerator()
        })
 
        // spuštění aplikace
        app.Run()
}
 
func renderFrame(dc *gg.Context, width, height int, points []Point, currentPoint Point) {
        // barva pozadí
        dc.SetRGB(0.9, 0.9, 0.7)
 
        // obdélník s plochou odpovídající oknu
        dc.DrawRectangle(0, 0, float64(width), float64(height))
        dc.Fill()
 
        // vykreslení cest
        drawPath(dc, points, currentPoint)
}

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

Zdrojové kódy všech minule i 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:

# Příklad Stručný popis Cesta
1 01_clear_screen.go vymazání obsahu rastrového obrázku https://github.com/tisnik/go-root/blob/master/article_AF/01_cle­ar_screen.go
2 02_fill_rectangle.go vymazání obrázku vykreslením vyplněného obdélníku https://github.com/tisnik/go-root/blob/master/article_AF/02_fi­ll_rectangle.go
3 03_clear_screen_color.go vymazání obrazovky (lepší varianta) https://github.com/tisnik/go-root/blob/master/article_AF/03_cle­ar_screen_color.go
4 04_clear_screen_color.go vymazání obrazovky (varianta se specifikací barvy) https://github.com/tisnik/go-root/blob/master/article_AF/04_cle­ar_screen_color.go
5 05_clear_screen_color.go vymazání obrazovky (varianta se specifikací barvy) https://github.com/tisnik/go-root/blob/master/article_AF/05_cle­ar_screen_color.go
6 06_colors.go práce s barvami https://github.com/tisnik/go-root/blob/master/article_AF/06_co­lors.go
7 07_color_alpha.go práce s barvami, alfa složka https://github.com/tisnik/go-root/blob/master/article_AF/07_co­lor_alpha.go
8 08_rgba_color.go barvový prostor RGB a RGBA https://github.com/tisnik/go-root/blob/master/article_AF/08_rgba_co­lor.go
9 09_alpha_component.go barvový prostor RGB a RGBA. Alfa kanál v barvovém prostoru RGBA https://github.com/tisnik/go-root/blob/master/article_AF/09_al­pha_component.go
10 10_line_width.go nastavení šířky vykreslovaných úseček a křivek https://github.com/tisnik/go-root/blob/master/article_AF/10_li­ne_width.go
11 11_line_caps.go nastavení stylu konců úseček https://github.com/tisnik/go-root/blob/master/article_AF/11_li­ne_caps.go
12 12_dash.go nastavení stylu vykreslovaných úseček https://github.com/tisnik/go-root/blob/master/article_AF/12_dash.go
13 13_simple_path.go práce s cestou (path) https://github.com/tisnik/go-root/blob/master/article_AF/13_sim­ple_path.go
14 14_open_path.go otevřená cesta (open path) https://github.com/tisnik/go-root/blob/master/article_AF/14_o­pen_path.go
15 15_closed_path.go uzavřená cesta (closed path) https://github.com/tisnik/go-root/blob/master/article_AF/15_clo­sed_path.go
16 16_quadratic_bezier.go kvadratická Bézierova křivka https://github.com/tisnik/go-root/blob/master/article_AF/16_qu­adratic_bezier.go
17 17_quadratic_bezier_control_points.go kvadratická Bézierova křivka s řídicími body https://github.com/tisnik/go-root/blob/master/article_AF/17_qu­adratic_bezier_control_po­ints.go
18 18_cubic_bezier.go kubická Bézierova křivka https://github.com/tisnik/go-root/blob/master/article_AF/18_cu­bic_bezier.go
19 19_cubic_bezier_control_points.go kubická Bézierova křivka s řídicími body https://github.com/tisnik/go-root/blob/master/article_AF/19_cu­bic_bezier_control_points­.go
20 20_rotation.go rotace vykreslovaných objektů https://github.com/tisnik/go-root/blob/master/article_AF/20_ro­tation.go
21 21_to_image.go rasterizace do zvoleného rastrového formátu. Výsledkem bude animovaný GIF https://github.com/tisnik/go-root/blob/master/article_AF/21_to_i­mage.go
22 22_transformation_rotate.go transformace (rotace) https://github.com/tisnik/go-root/blob/master/article_AF/22_tran­sformation_rotate.go
23 23_transformation_rotate_about.go transformace (rotace okolo zvoleného bodu) https://github.com/tisnik/go-root/blob/master/article_AF/23_tran­sformation_rotate_about.go
24 24_scale_about.go transformace (změna měřítka okolo zvoleného bodu) https://github.com/tisnik/go-root/blob/master/article_AF/24_sca­le_about.go
25 25_translate_rotate.go transformace (posun) https://github.com/tisnik/go-root/blob/master/article_AF/25_tran­slate_rotate.go
26 26_text.go vykreslení textu https://github.com/tisnik/go-root/blob/master/article_AF/26_text.go
27 27_text_centered.go vykreslení vycentrovaného textu https://github.com/tisnik/go-root/blob/master/article_AF/27_tex­t_centered.go
28 28_text_centered.go vykreslení vycentrovaného textu https://github.com/tisnik/go-root/blob/master/article_AF/28_tex­t_centered.go
29 29_text_rotated.go vykreslení vycentrovaného textu, který je rotován https://github.com/tisnik/go-root/blob/master/article_AF/29_tex­t_rotated.go
30 go.mod   https://github.com/tisnik/go-root/blob/master/article_AF/go.mod
       
31 01_stroke_rectangle.go vykreslení obrysu obdélníku https://github.com/tisnik/go-root/blob/master/article_B0/01_stro­ke_rectangle.go
32 02_fill_rectangle.go vykreslení vyplněného obdélníku https://github.com/tisnik/go-root/blob/master/article_B0/02_fi­ll_rectangle.go
33 03_hex_color.go specifikace barvy trojicí hex složek https://github.com/tisnik/go-root/blob/master/article_B0/03_hex_co­lor.go
34 04_stroke_brush.go konfigurace štětce pro vykreslování obrysů https://github.com/tisnik/go-root/blob/master/article_B0/04_stro­ke_brush.go
35 05_fill_brush.go konfigurace štětce pro výplň uzavřených obrazců https://github.com/tisnik/go-root/blob/master/article_B0/05_fi­ll_brush.go
36 06_stroke_and_fill.go pokus o kombinaci štěte pro obrysy a výplně https://github.com/tisnik/go-root/blob/master/article_B0/06_stro­ke_and_fill.go
37 07_blend_mode_normal.go vykreslení překrývajících se obrazců https://github.com/tisnik/go-root/blob/master/article_B0/07_blen­d_mode_normal.go
38 08_blend_mode_alpha.go překrývající se obrazce a alfa složka barvy https://github.com/tisnik/go-root/blob/master/article_B0/08_blen­d_mode_alpha.go
39 09_blend_mode_multiply.go režimy prolínaní https://github.com/tisnik/go-root/blob/master/article_B0/09_blen­d_mode_multiply.go
40 10_fill_rule_default.go výchozí pravidlo pro vyplňování obrazců, jejichž hrany se překrývají https://github.com/tisnik/go-root/blob/master/article_B0/10_fi­ll_rule_default.go
41 11_fill_rule_odd.go pravidla „lichý počet přetnutí paprsku“ pro vyplňování obrazců, jejichž hrany se překrývají https://github.com/tisnik/go-root/blob/master/article_B0/11_fi­ll_rule_odd.go
42 12_linear_gradient.go lineární gradient, přechod dvou barev https://github.com/tisnik/go-root/blob/master/article_B0/12_li­near_gradient.go
43 13_linear_gradient.go lineární gradient, přechod dvou barev, odlišný úhel https://github.com/tisnik/go-root/blob/master/article_B0/13_li­near_gradient.go
44 14_linear_gradient.go lineární gradient, přechod tří barev https://github.com/tisnik/go-root/blob/master/article_B0/14_li­near_gradient.go
45 15_linear_gradient.go lineární gradient, přechod tří barev, https://github.com/tisnik/go-root/blob/master/article_B0/15_li­near_gradient.go
46 16_radial_gradient.go radiální gradient, přechod dvou barev https://github.com/tisnik/go-root/blob/master/article_B0/16_ra­dial_gradient.go
47 17_radial_gradient.go radiální gradient, přechod tří barev https://github.com/tisnik/go-root/blob/master/article_B0/17_ra­dial_gradient.go
48 18_sweep_gradient.go gradient závislý na úhlu, přechod dvou barev https://github.com/tisnik/go-root/blob/master/article_B0/18_swe­ep_gradient.go
49 19_sweep_gradient.go gradient závislý na úhlu, přechod tří barev https://github.com/tisnik/go-root/blob/master/article_B0/19_swe­ep_gradient.go
50 20_svg_draw.go provedení exportu vektorové grafiky do formátu SVG https://github.com/tisnik/go-root/blob/master/article_B0/20_svg_draw­.go
51 21_quadratic_bezier_control_points.go export do SVG, výsledek vykreslení Bézierových křivek https://github.com/tisnik/go-root/blob/master/article_B0/21_qu­adratic_bezier_control_po­ints.go
52 22_linear_gradient_svg.go export do SVG, výsledek vykreslení gradientního lineárního přechodu https://github.com/tisnik/go-root/blob/master/article_B0/22_li­near_gradient_svg.go
53 23_radial_gradient_svg.go export do SVG, výsledek vykreslení gradientního radiálního přechodu https://github.com/tisnik/go-root/blob/master/article_B0/23_ra­dial_gradient_svg.go
54 24_pdf_draw.go provedení exportu vektorové grafiky do formátu PDF https://github.com/tisnik/go-root/blob/master/article_B0/24_pdf_draw­.go
       
55 01_window.go aplikace s prázdným oknem https://github.com/tisnik/go-root/blob/master/article_B1/01_win­dow.go
56 02_init_background.go vykreslení pozadí zvolenou barvou v ploše okna https://github.com/tisnik/go-root/blob/master/article_B1/02_i­nit_background.go
57 03_continuous_redraw.go povolení kontinuálního překreslování okna https://github.com/tisnik/go-root/blob/master/article_B1/03_con­tinuous_redraw.go
58 04_draw_rectangle.go využití canvasu při kreslení do okna https://github.com/tisnik/go-root/blob/master/article_B1/04_draw_rec­tangle.go
59 05_fill_rule.go vykreslení hvězdy do plochy okna https://github.com/tisnik/go-root/blob/master/article_B1/05_fi­ll_rule.go
60 06_cubic_bezier_control_points.go vykreslení Bézierových křivek i s jejich řídicími body https://github.com/tisnik/go-root/blob/master/article_B1/06_cu­bic_bezier_control_points­.go
61 07_keyboard_input.go zjištění stisku kláves použitých pro ovládání aplikace https://github.com/tisnik/go-root/blob/master/article_B1/07_ke­yboard_input.go
62 08_keyboard_input.go modifikace barvy vykreslovaného obrazce klávesnicí https://github.com/tisnik/go-root/blob/master/article_B1/08_ke­yboard_input.go
63 09_keyboard_input.go modifikace barvy vykreslovaného obrazce klávesnicí, vylepšená varianta https://github.com/tisnik/go-root/blob/master/article_B1/09_ke­yboard_input.go
64 10_mouse_input.go reakce na stisk tlačítka myši, přečtení souřadnic kurzoru myši https://github.com/tisnik/go-root/blob/master/article_B1/10_mou­se_input.go
65 11_mouse_input.go jednoduchý kreslicí program https://github.com/tisnik/go-root/blob/master/article_B1/11_mou­se_input.go
66 12_mouse_input.go vylepšený jednoduchý kreslicí program: kreslení „gumové čáry“ https://github.com/tisnik/go-root/blob/master/article_B1/12_mou­se_input.go

Odkazy na Internetu

  1. Projekt GoGPU na GitHubu
    https://github.com/orgs/gog­pu/repositories
  2. Balíček gogpu/gg
    https://github.com/gogpu/gg
  3. Balíček gogpu/gg-svg
    https://github.com/gogpu/gg-svg
  4. Balíček gogpu/gg-pdf
    https://github.com/gogpu/gg-pdf
  5. package gg (což je jiná varianta gg)
    https://godoc.org/github.com/fo­gleman/gg
  6. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  7. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  8. The Go image package
    https://blog.golang.org/go-image-package
  9. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  10. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  11. Immediate mode (computer graphics)
    https://en.wikipedia.org/wi­ki/Immediate_mode_(computer_grap­hics)
  12. Retained mode
    https://en.wikipedia.org/wi­ki/Retained_mode
  13. Lecture 11: Linear Interpolation Again – Bézier Curves
    http://www.math.kent.edu/~re­ichel/courses/intr.num.com­p.1/fall09/lecture12/bez.pdf
  14. Geometrie/Úvod do křivek
    https://cs.wikibooks.org/wi­ki/Geometrie/%C3%9Avod_do_k%C5%99i­vek
  15. Seriál Grafická knihovna OpenGL
    https://www.root.cz/serialy/graficka-knihovna-opengl/
  16. 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/
  17. Seriál OpenGL a nadstavbová knihovna GLU
    https://www.root.cz/serialy/opengl-a-nadstavbova-knihovna-glu/
  18. Seriál OpenGL evaluátory
    https://www.root.cz/serialy/opengl-evaluatory/
  19. Khronos Group
    https://www.khronos.org/
  20. Khronos Group (Wikipedia)
    https://en.wikipedia.org/wi­ki/Khronos_Group
  21. OpenVG (Wikipedia)
    https://en.wikipedia.org/wiki/OpenVG
  22. OpenVG Quick Reference Card
    https://www.khronos.org/files/openvg-quick-reference-card.pdf
  23. OpenVG on the Raspberry Pi
    http://mindchunk.blogspot­.cz/2012/09/openvg-on-raspberry-pi.html
  24. ShivaVG: open-source ANSI C OpenVG
    http://ivanleben.blogspot­.cz/2007/07/shivavg-open-source-ansi-c-openvg.html
  25. Testbed for exploring OpenVG on the Raspberry Pi
    https://github.com/ajstarks/openvg
  26. Seriál Grafické karty a grafické akcelerátory
    http://www.root.cz/serialy/graficke-karty-a-graficke-akceleratory/
  27. (Linux) Framebuffer
    http://wiki.linuxquestion­s.org/wiki/Framebuffer
  28. (Linux) Framebuffer HOWTO
    http://tldp.org/HOWTO/Framebuffer-HOWTO/
  29. Linux framebuffer (Wikipedia)
    https://en.wikipedia.org/wi­ki/Linux_framebuffer
  30. Seriál Grafické formáty
    http://www.root.cz/serialy/graficke-formaty/
  31. Seriál Grafický formát GIF
    https://www.root.cz/serialy/graficky-format-gif/
  32. PNG is Not GIF
    https://www.root.cz/clanky/png-is-not-gif/

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.