Hlavní navigace

Programovací jazyk Go a grafika: tvorba animovaných GIFů, grafická knihovna GG

12. 3. 2019
Doba čtení: 40 minut

Sdílet

 Autor: Go Lang
Budeme se zabývat dvěma důležitými tématy 2D grafiky. Nejdříve si ukážeme tvorbu animací s využitím klasických animovaných GIFů a pak se zmíníme o užitečné knihovně GG (Go Graphics), která nabízí podobné funkce jako Cairo či OpenVG.

Obsah

1. Základní vlastnosti grafického formátu GIF

2. Logická obrazovka, rámce a barvová paleta

3. Export rastrových obrázků do formátu GIF

4. Rozdíl při exportu obrázků s barvovým prostorem RGBA a obrázků s barvovou paletou

5. Nastavení vlastností výsledného GIFu

6. Omezení počtu barev ve výsledném GIFu

7. Animované GIFy

8. Vytvoření jednoduchého animovaného GIFu – blikající čtvereček

9. Složitější animace – pohybující se šachovnice

10. Volba metody přechodu mezi jednotlivými snímky animace

11. Použití nastaveného pozadí GIFu

12. Přechod na další snímek bez smazání snímku předchozího

13. Vytvoření GIFu s animovaným progress barem

14. Tvorba 2D grafiky s využitím knihovny GG

15. Parametry kreslení zapamatované v kontextu

16. Kreslení otevřených tvarů (úseček) pomocí příkazu Stroke

17. Vyplnění uzavřené křivky příkazem Fill

18. Nastavení základních parametrů cest: průhlednost, šířka tahu a styl vykreslení

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

20. Odkazy na Internetu

1. Základní vlastnosti grafického formátu GIF

„This format has the unique quality that, like the AK47, in a lot of circumstances, it just works, unlike the more complex alternatives.“

V úvodní části dnešního článku o programovacím jazyku Go si ukážeme práci s rastrovým grafickým formátem GIF. První varianta grafického formátu GIF vznikla již v roce 1987 (ostatně proto se taky tato verze nazývá GIF87a), takže se jedná o souborový formát, který se v oboru IT udržel již neskutečných 32 let. Dnes se ovšem budeme zabývat převážně novější verzí GIFu pocházející z roku 1989. Tato varianta se jmenuje GIF89a a jedná se o formát, jenž je význačný především tím, že podporuje animace a v neposlední řadě i tím, že GIF je prakticky univerzálně akceptovaný napříč většinou počítačových architektur a platforem, a to i těch (pre)historických. Pokud je tedy zapotřebí vložit na webovou stránku nějakou jednoduchou animaci (ideálně v případě, že se jedná o animaci s objekty ostrých tvarů a s malým počtem barev), je u GIFu prakticky zaručeno, že příjemce bude schopen si animaci prohlédnout, a to bez nutnosti instalace kodeků či nějakých pluginů do prohlížečů.

V dalších odstavcích se zmíníme o některých omezeních GIFu. Jedno zásadní omezení spočívající v patentovaném algoritmu už sice není relevantní, ale technická omezení způsobila, že vzniklo několik alternativ, které měly GIF v oblasti jednoduchých animací nahradit. V první řadě se jednalo o formát MNG (Multiple-image Network Graphics), jenž je založený na populárním PNG. Na rozdíl od PNG (pouze statické obrázky) se však MNG příliš nerozšířil a nakonec byla jeho podpora odstraněna i z webových prohlížečů, s tím, že by měl být MNG nahrazen jiným formátem nazvaným APNG neboli Animated Portable Network Graphics. Tento formát je podporován ve většině významných prohlížečů, viz též Can I use: Animated PNG (APNG), ovšem s tím varováním, že podpora chybí pro IE a Edge: Edge platform status. I přes relativně dobrou podporu jak na straně prohlížečů, tak i na straně software pro výrobu animací, však není APNG zdaleka tak rozšířen, jako „starý dobrý“ GIF a proto je znalost práce s GIFem stále relevantní.

Poznámka: dnes nás pochopitelně bude zajímat především programová tvorba animovaných GIFů, protože jazyk Go se bude v tomto případě používat na straně serveru vytvářejícího a nabízejícího (dynamický) obsah.

Obrázek 1: Takřka ideální kandidát na použití animovaného GIFu – výsledek jednoduché simulace.

2. Logická obrazovka, rámce a barvová paleta

Grafický formát GIF primárně slouží pro záznam a přenos grafických informací uložených ve formě bitmapy, tj. obecně vícebarevného rastrového obrázku. Většina z dnes používaných grafických formátů, například JPEG, PNG, TGA či BMP obsahuje popis rastrového obrázku jako celku, přičemž se (buď přímo nebo v komprimované podobě) ukládají barvové hodnoty všech pixelů, ze kterých se rastrový obrázek skládá.

V grafickém formátu GIF je však použit odlišný přístup. Celý obrázek, který je zde nazývaný logická obrazovka (logical screen), není v souboru uložen jako jeden celek, ale skládá se z několika takzvaných rámců (frame), což jsou obdélníkové oblasti umístěné uvnitř logické obrazovky. Minimálně musí být vždy přítomen jeden rámec, jejich maximální množství však není omezeno. Každý rámec je možné chápat jako rastrový obrázek, který je celou svou plochou umístěn v logické obrazovce – podle specifikace nesmí žádný pixel z rámce padnout mimo logickou obrazovku. Pozice rámce v logické obrazovce je určena souřadnicí jeho horního levého rohu a velikostí (šířkou, výškou) zadanou v pixelech. Dále může být k rámci přiřazena lokální barvová paleta, pokud však není přítomna, použije se globální barvová paleta (viz další text).

Poznámka: právě nutnost specifikace velikosti a umístění rámce vedla k tomu, že ve standardní knihovně draw popsané minule se při vytváření rastrových obrázků používá datová struktura Rectangle, i když by v jiném případě postačovalo specifikovat šířku a výšku obrázků v pixelech.

Obrázek 2: Vztah mezi logickou obrazovkou a rámci.

Na předchozím obrázku je naznačené, jakým způsobem může být logická obrazovka popsána pomocí rámců. Vidíme, že není nutné, aby rámce pokryly celou logickou obrazovku, rámce se dokonce mohou v případě potřeby navzájem překrývat. Jak uvidíme dále, je při překryvu rámců možné použít transparentní pixely, takže překryv může být ve skutečnosti mnohem složitější, než pouhé přepsání barev pixelů v zadané obdélníkové oblasti.

Rámce nachází v grafickém formátu GIF mnoho uplatnění. Na dalších dvou obrázcích je naznačeno, jakým způsobem je možné použít rámce pro redukci celkové velikosti obrázku. Prázdné bílé plochy není nutné explicitně ukládat, může se zde pouze vykreslit barva pozadí. Algoritmus LZW je, podobně jako některé další algoritmy použité pro komprimaci obrázků (kromě primitivního RLE), poměrně hloupý a zbytečně by si sekvencemi stejných bytů zaplnil hešovací tabulku a tím by byl nucen ji buď rozšířit, nebo uvést do původní podoby (bez zapamatovaných sekvencí delších než jeden pixel). Obojí však vede ke zvětšení výsledného souboru. Mimochodem, na obrázcích 3 a 4 je ukázána úvodní stránka z první kapitoly TEXbooku.

Obrázek 3: Obrázek uložený v jednom rámci.

Obrázek 4: Obrázek rozdělený na tři rámce s využitím barvy pozadí (ta je pro ilustraci zobrazena šedě).

Vzhledem k tomu, že každý pixel umístěný v rámci může být popsán maximálně osmi bity, znamená to, že jeho barvu je možné vybrat z barvové palety obsahující nejvýše 256 barev. Každá barva je v barvové paletě popsána trojicí hodnot – barvových složek označených písmeny R (red), G (green) a B (blue), přičemž každá barvová složka je uložena v jednom bytu. Minimální velikost barvové palety (se dvěma barvami) je proto rovna šesti bytům (2×3), maximální velikost barvové palety s 256 barvami pak 768 bytům (256×3). U mnoha obrázků se proto může stát, že barvová paleta tvoří nezanedbatelnou část jejich celkové velikosti.

Poznámka: to znamená, že knihovna draw musí obrázky převádět do barvového prostoru omezeného paletou s maximálně 256 barvami (i když je teoreticky možné s využitím rámců dosáhnout efektu většího množství barev).

V grafickém formátu GIF rozlišujeme dvě barvové palety: globální a lokální. Globální barvová paleta (global color table) může v souboru typu GIF existovat maximálně jednou a její velikost (tj. počet barev) je zadána v hlavičce popisující logickou obrazovku. V některých případech nemusí být globální barvová paleta přítomna vůbec. Lokální barvová paleta (local color table) může být přiřazena ke každému rámci, opět se však nejedná o povinnou součást rámce. V případě, že není přítomna ani globální ani lokální barvová paleta, měl by prohlížecí program použít systémovou paletu, v případě webových prohlížečů takzvanou web-safe paletu složenou z 216 barev rovnoměrně umístěných v jednotkové RGB krychli; zbylé barvy tvoří odstíny šedi. Absence obou barvových palet sice může zmenšit celkovou velikost souboru, způsob zobrazení se však může v různých prohlížečích lišit, proto se většinou (99% případů) alespoň jedna barvová paleta používá.

Důležitou součástí popisu logické obrazovky je index jedné barvy v globální barvové paletě, která bude použita pro vykreslení pozadí. Dodnes sice většina programů při ukládání GIFů každý obrázek uloží do jednoho velikého rámce, ale při optimalizaci na velikost je možné (a některé sofistikovanější programy to provádí), aby některé pixely logické obrazovky nebyly pokryty žádným z rámců. Nepokryté pixely by poté měly být vybarveny právě barvou pozadí. Tímto způsobem, jak si ostatně ukážeme v další části, je možné vytvořit optimalizované obrázky, či dokonce obrázky o prakticky jakékoli velikosti, které však stále budou zabírat malé místo na disku (díky tomu, že je uložen například pouze malý rámec uprostřed obrovské volné plochy).

Poznámka: tuto vlastnost GIFu použijeme v některých demonstračních příkladech.

Verze GIF89a přinesla oproti původní verzi GIF87a jednu podstatnou novinku – pomocí rozšiřujícího řídicího bloku (graphics control extension) je možné v každém rámci specifikovat jeden index do barvové palety, který představuje průhledné pixely. Díky tomu je možné, aby optický tvar rámce nebyl pouze obdélníkový, ale v podstatě libovolný. Vzhledem k tomu, že je možné „průhlednými“ pixely překreslit i původně neprůhlednou barvu pozadí, může mít i obrázek jako celek neobdélníkový tvar, což je velmi často využíváno zejména při tvorbě různých log na webových stránkách a v neposlední řadě i v řadě animací. Na třetím obrázku je ukázka GIFu s průhledností.

Obrázek 5: GIF s průhlednými pixely.

3. Export rastrových obrázků do formátu GIF

Nejprve si ukážeme ten nejjednodušší způsob exportu rastrových obrázků do formátu GIF. Obrázek je samozřejmě nutné nejdříve vytvořit, například funkcí CreateChessboard, kterou jsme si ukázali minule:

img := CreateChessboard(256, 256, BoardSize)

Dále vytvoříme nový soubor pro uložení obrázku:

outfile, err := os.Create("02.gif")
if err != nil {
        panic(err)
}
defer outfile.Close()

A následně obrázek nechápe zakódovat pomocí funkce Encode, podobně jako jsme to udělali v případě formátu PNG:

err = gif.Encode(outfile, img, nil)
if err != nil {
        panic(err)
}

Výsledek:

Obrázek 6: Výsledek činnosti prvního demonstračního příkladu.

Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/go-fedora/blob/master/article15/02_gif_ex­port_rgba.go:

package main
 
import (
        "image"
        "image/color"
        "image/draw"
        "image/gif"
        "os"
)
 
const BoardSize = 8
 
func CreateChessboard(width int, height int, board_size int) *image.RGBA {
        var palette = []color.Color{
                color.RGBA{150, 205, 50, 255},
                color.RGBA{0, 100, 0, 255},
        }
 
        img := image.NewRGBA(image.Rect(0, 0, width, height))
 
        index_color := 0
        hor_block := int(width / board_size)
        ver_block := int(height / board_size)
 
        x_from := 0
        x_to := hor_block
        for x := 0; x < board_size; x++ {
                y_from := 0
                y_to := ver_block
                for y := 0; y < board_size; y++ {
                        r := image.Rect(x_from, y_from, x_to, y_to)
                        draw.Draw(img, r, &image.Uniform{palette[index_color]}, image.ZP, draw.Src)
                        y_from = y_to
                        y_to += ver_block
                        index_color = 1 - index_color
                }
                x_from = x_to
                x_to += hor_block
                index_color = 1 - index_color
        }
        return img
}
 
func main() {
        img := CreateChessboard(256, 256, BoardSize)
 
        outfile, err := os.Create("02.gif")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        err = gif.Encode(outfile, img, nil)
        if err != nil {
                panic(err)
        }
}

4. Rozdíl při exportu obrázků s barvovým prostorem RGBA a obrázků s barvovou paletou

Pokusme se nyní vytvořit jednopixelový obrázek (o rozměrech 1×1 pixel) v barvovém prostoru RGBA nebo NRGBA:

c := color.RGBA{255, 0, 0, 255}
img := image.NewRGBA(image.Rect(0, 0, 1, 1))
img.Set(0, 0, c)

Po exportu do GIFu vznikne tento soubor:

Obrázek 7: Jednopixelový obrázek, který však obsahuje paletu s 256 barvami.

Pokud takový obrázek otevřeme v nějakém grafickém editoru, můžeme se přesvědčit o tom, že obsahuje úplnou barvovou paletu s 256 barvami:

Obrázek 8: Zobrazení barvové palety v grafickém editoru.

Celková velikost souboru s obrázkem je taktéž až enormně velká v poměru k obrazovým informacím, které obsahuje: celých 798 bajtů!

Úplný výpis zdrojového kódu tohoto demonstračního příkladu je umístěn na adrese https://github.com/tisnik/go-fedora/blob/master/article15/03_gif_1×1_rgba­.go:

package main
 
import (
        "image"
        "image/color"
        "image/gif"
        "os"
)
 
func CreateImage(width int, height int) *image.RGBA {
        c := color.RGBA{255, 0, 0, 255}
        img := image.NewRGBA(image.Rect(0, 0, width, height))
        img.Set(0, 0, c)
 
        return img
}
 
func main() {
        img := CreateImage(1, 1)
 
        outfile, err := os.Create("03.gif")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        err = gif.Encode(outfile, img, nil)
        if err != nil {
                panic(err)
        }
}

Tento příklad ovšem můžeme modifikovat, a to konkrétně takovým způsobem, že se namísto obrázku s barvovým prostorem RGBA (16 milionů barvových odstínů) použije obrázek s paletou. Minimální velikost palety je ve formátu GIF rovna dvěma položkám, takže si takovou paletu vytvoříme:

var palette = []color.Color{
        color.RGBA{255, 128, 128, 255},
        color.RGBA{255, 255, 255, 255},
}

Následně je možné vytvořit rastrový obrázek používající tuto paletu a nastavit jeho (jediný) pixel na zvolenou barvu vybranou z palety:

c := color.RGBA{255, 128, 128, 255}
img := image.NewPaletted(image.Rect(0, 0, 1, 1), palette)
img.Set(0, 0, c)
Poznámka: povšimněte si, že druhým parametrem konstruktoru NewPaletted je právě barvová palety, tj. pole s prvky typu Color.

Po uložení získáme tento soubor:

Obrázek 9: Jednopixelový obrázek, který obsahuje paletu se dvěma barvami.

Jeho velikost je nyní pouhých 34 bajtů, což je současně nejmenší možná velikost zcela validního obrázku uloženého ve formátu GIF.

Po otevření vytvořeného obrázku v grafickém editoru je patrné, že paleta se rapidně zmenšila na dvě položky:

Obrázek 10: Zobrazení barvové palety v grafickém editoru.

Upravený demonstrační příklad, který vytvoří obrázek s naprosto stejnou obrazovou informací jako obrázek předchozí, může vypadat následovně:

package main
 
import (
        "image"
        "image/color"
        "image/gif"
        "os"
)
 
func CreateImage(width int, height int) *image.Paletted {
        var palette = []color.Color{
                color.RGBA{255, 128, 128, 255},
                color.RGBA{255, 255, 255, 255},
        }
 
        c := color.RGBA{255, 128, 128, 255}
        img := image.NewPaletted(image.Rect(0, 0, width, height), palette)
        img.Set(0, 0, c)
 
        return img
}
 
func main() {
        img := CreateImage(1, 1)
 
        outfile, err := os.Create("04.gif")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        err = gif.Encode(outfile, img, nil)
        if err != nil {
                panic(err)
        }
}

5. Nastavení vlastností výsledného GIFu

Při exportu rastrových obrázků do formátu GIF je možné přes poslední parametr Encode specifikovat ukazatel na strukturu typu Options, která obsahuje doplňující informace o vlastnostech výsledného GIFu. Můžeme například explicitně nastavit počet barev:

options := gif.Options{NumColors: 255, Quantizer: nil, Drawer: nil}

Ukazatel na nově vytvořenou proměnnou se předá do funkce Encode:

err = gif.Encode(outfile, img, &options)
if err != nil {
        panic(err)
}

Příklad použití:

package main
 
import (
        "image"
        "image/color"
        "image/draw"
        "image/gif"
        "os"
)
 
const BoardSize = 8
 
func CreateChessboard(width int, height int, board_size int) *image.RGBA {
        var palette = []color.Color{
                color.RGBA{150, 205, 50, 255},
                color.RGBA{0, 100, 0, 255},
        }
 
        img := image.NewRGBA(image.Rect(0, 0, width, height))
 
        index_color := 0
        hor_block := int(width / board_size)
        ver_block := int(height / board_size)
 
        x_from := 0
        x_to := hor_block
        for x := 0; x < board_size; x++ {
                y_from := 0
                y_to := ver_block
                for y := 0; y < board_size; y++ {
                        r := image.Rect(x_from, y_from, x_to, y_to)
                        draw.Draw(img, r, &image.Uniform{palette[index_color]}, image.ZP, draw.Src)
                        y_from = y_to
                        y_to += ver_block
                        index_color = 1 - index_color
                }
                x_from = x_to
                x_to += hor_block
                index_color = 1 - index_color
        }
        return img
}
 
func main() {
        img := CreateChessboard(256, 256, BoardSize)
 
        outfile, err := os.Create("05.gif")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        options := gif.Options{NumColors: 255, Quantizer: nil, Drawer: nil}
 
        err = gif.Encode(outfile, img, &options)
        if err != nil {
                panic(err)
        }
}

Export šachovnice do obrázku typu GIF s dvoubarevnou barvovou paletou:

Obrázek 11: Šachovnice, barvová paleta je snížena na dvě barvy, celková velikost obrázku je nyní pouze 2667 bajtů.

Předchozí obrázek byl vytvořen takto upraveným příkladem:

package main
 
import (
        "image"
        "image/color"
        "image/draw"
        "image/gif"
        "os"
)
 
const BoardSize = 8
 
func CreateChessboard(width int, height int, board_size int) *image.Paletted {
        var palette = []color.Color{
                color.RGBA{150, 205, 50, 255},
                color.RGBA{0, 100, 0, 255},
        }
 
        img := image.NewPaletted(image.Rect(0, 0, width, height), palette)
 
        index_color := 0
        hor_block := int(width / board_size)
        ver_block := int(height / board_size)
 
        x_from := 0
        x_to := hor_block
        for x := 0; x < board_size; x++ {
                y_from := 0
                y_to := ver_block
                for y := 0; y < board_size; y++ {
                        r := image.Rect(x_from, y_from, x_to, y_to)
                        draw.Draw(img, r, &image.Uniform{palette[index_color]}, image.ZP, draw.Src)
                        y_from = y_to
                        y_to += ver_block
                        index_color = 1 - index_color
                }
                x_from = x_to
                x_to += hor_block
                index_color = 1 - index_color
        }
        return img
}
 
func main() {
        img := CreateChessboard(256, 256, BoardSize)
 
        outfile, err := os.Create("06.gif")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        err = gif.Encode(outfile, img, nil)
        if err != nil {
                panic(err)
        }
}

6. Omezení počtu barev ve výsledném GIFu

S barvovými prostory RGBA a NRGBA jsme se již setkali v předchozím článku. V obou případech se barva pixelu zadává čtyřmi složkami – červenou, zelenou, modrou a průhledností. Každá z těchto složek je uložena v bajtu (jedná se tedy o hodnotu v rozsahu 0 až 255). Celkový počet barev ve spektru dosahuje hodnoty 2563 = 16777216 barev (více než šestnáct milionů). Pokud by tento rozsah nedostačoval, například při provádění některých editačních operací (vícenásobná filtrace atd.), lze použít prostory RGBA64 a NRGBA64. V případě prostoru NRGBA se při ukládání barev neprovádí žádné další operace, u RGBA se však nejprve barvové složky přednásobí průhledností. Ukažme si, jak se v tomto modelu vytvoří gradientní přechody:

img := image.NewNRGBA(image.Rect(0, 0, width, height))
 
for x := 0; x < 256; x++ {
        for y := 0; y < 256; y++ {
                c := color.NRGBA{0, byte(x), byte(y), 255}
                img.SetNRGBA(x, y, c)
 
                c = color.NRGBA{85, byte(x), byte(y), 255}
                img.SetNRGBA(x+256, y, c)
 
                c = color.NRGBA{170, byte(x), byte(y), 255}
                img.SetNRGBA(x, y+256, c)
 
                c = color.NRGBA{255, byte(x), byte(y), 255}
                img.SetNRGBA(x+256, y+256, c)
        }
}

Takto vytvořený obrázek uložíme do formátu GIF, ovšem postupně budeme měnit počet barev v paletě od 1 do 256 (bude se jednat o mocniny dvou, což vyplývá z omezení samotného GIFu):

for colors := 1; colors <= 256; colors <<= 1 {
        options := gif.Options{NumColors: colors, Quantizer: nil, Drawer: nil}
 
        gif.Encode(outfile, img, &options)
}

Výsledky ukazují, jak se s omezením počtu barev dobře či špatně vypořádal interní algoritmus využívající dithering:

Obrázek 12: Použití ditheringu při cílovém počtu 256 barev.

Obrázek 13: Použití ditheringu při cílovém počtu 128 barev.

Obrázek 14: Použití ditheringu při cílovém počtu 64 barev.

Obrázek 15: Použití ditheringu při cílovém počtu 32 barev.

Obrázek 16: Použití ditheringu při cílovém počtu 16 barev.

Obrázek 17: Použití ditheringu při cílovém počtu 8 barev.

Obrázek 18: Použití ditheringu při cílovém počtu 4 barev.

Obrázek 19: Použití ditheringu při cílovém počtu 2 barev.

Pochopitelně si opět ukážeme úplné znění tohoto demonstračního příkladu:

package main
 
import (
        "fmt"
        "image"
        "image/color"
        "image/gif"
        "os"
)
 
const width = 512
const height = 512
 
func main() {
        img := image.NewNRGBA(image.Rect(0, 0, width, height))
 
        for x := 0; x < 256; x++ {
                for y := 0; y < 256; y++ {
                        c := color.NRGBA{0, byte(x), byte(y), 255}
                        img.SetNRGBA(x, y, c)
 
                        c = color.NRGBA{85, byte(x), byte(y), 255}
                        img.SetNRGBA(x+256, y, c)
 
                        c = color.NRGBA{170, byte(x), byte(y), 255}
                        img.SetNRGBA(x, y+256, c)
 
                        c = color.NRGBA{255, byte(x), byte(y), 255}
                        img.SetNRGBA(x+256, y+256, c)
                }
        }
 
        for colors := 1; colors <= 256; colors <<= 1 {
                filename := fmt.Sprintf("07_%03d.gif", colors)
 
                outfile, err := os.Create(filename)
                if err != nil {
                        panic(err)
                }
                defer outfile.Close()
 
                options := gif.Options{NumColors: colors, Quantizer: nil, Drawer: nil}
 
                gif.Encode(outfile, img, &options)
        }
}

7. Animované GIFy

Grafický formát GIF je na Internetu stále oblíbený zejména z toho důvodu, že podporuje animace – a to bez nutnosti instalace pluginů na straně prohlížeče nebo použití speciálních technik (JavaScript, streamované video apod.). Animace byly na Internetu zavedeny s příchodem Netscape 2 a posléze Internet Exploreru 3. Ve skutečnosti nejsou animace nic jiného, než sled po sobě jdoucích rámců (které mohou ležet na sobě), mezi jejichž zobrazením je vložena krátká či delší časová prodleva. Pokud jsou rámce překreslovány rychlostí cca 10 snímků za sekundu, vzniká dojem více či méně plynulé animace.

V některých případech, například při požadavcích na prezentaci krátkého videa na webových stránkách, musí tvůrce stránek porovnat možnosti grafického formátu GIF se souborovými či streamovými formáty určenými pro ukládání videa a zjistit, který formát je pro jeho aplikaci výhodnější. GIF není v žádném případě vhodný pro ukládání ani přehrávání větších animací, protože osmibitová paleta je mnohdy nedostačující a mezisnímková komprimace je obecně nedostatečná, zejména při práci s „reálným“, tj. typicky zašuměným videem. GIF také nepodporuje ukládání zvukových stop ani přesné časování snímků (o tom jsme se mohli předminule přesvědčit při práci s „true color“ GIFy).

Pro filmy je použita větší snímková frekvence, typicky 24 či 25 snímků za sekundu, musíme si však uvědomit, že filmové políčko má většinou mnohem větší rozlišení, než animovaný GIF, takže případné „trhání“ není u GIFu tak patrné. Animace byly zavedeny až ve verzi GIF89a.

Pro export/vytvoření animovaných GIFů slouží funkce nazvaná EncodeAll, kterou pochopitelně nalezneme v balíčku image/gif. Příklady použití této užitečné funkce naleznete v navazujících kapitolách:

func EncodeAll(w io.Writer, g *GIF) error

8. Vytvoření jednoduchého animovaného GIFu – blikající čtvereček

V dalším příkladu je ukázáno, jak snadno je možné s použitím základních balíčků programovacího jazyka Go vytvořit jednoduchý animovaný GIF. Celá animace bude obsahovat pouhé dva snímky, každý o rozměrech 32×32 pixelů, které budou vyplněny konstantní barvou. Snímky se budou opakovat ve smyčce (což je výchozí nastavení řízené jedním bitem hlavičky GIFu) a bude mezi nimi prodleva přibližně jedné sekundy.

Obrázky nejdříve musíme uložit do pole ukazatelů na strukturu Paletted (rastrový obrázek s barvovou paletou):

images := []*image.Paletted{
        CreateImage(32, 32, 0),
        CreateImage(32, 32, 1),
}

Dále je nutné vytvořit pole obsahující prodlevy mezi jednotlivými snímky. Prodlevy jsou reprezentovány celými čísly, přičemž základní jednotkou je 10ms (setina sekundy):

delays := []int{100, 100}

Pokud počet obrázků odpovídá počtu prodlev, je vytvoření výsledného animovaného GIFu již snadné:

gif.EncodeAll(outfile, &gif.GIF{
        Image: images,
        Delay: delays,
})

Velikost výsledného animovaného GIFu s dvojicí snímků je rovna pouhým 147 bajtům, což je prakticky stejná velikost, jakou zabírá tato věta:

Obrázek 20: Animovaný GIF se dvěma snímky.

Opět si pochopitelně ukážeme úplný zdrojový kód tohoto jednoduchého demonstračního příkladu:

package main
 
import (
        "image"
        "image/color"
        "image/draw"
        "image/gif"
        "os"
)
 
const BoardSize = 8
 
func CreateImage(width int, height int, paletteIndex int) *image.Paletted {
        var palette []color.Color
 
        if paletteIndex == 0 {
                palette = []color.Color{
                        color.RGBA{150, 150, 150, 255},
                }
        } else {
                palette = []color.Color{
                        color.RGBA{250, 0, 0, 255},
                }
        }
 
        img := image.NewPaletted(image.Rect(0, 0, width, height), palette)
 
        r := image.Rect(0, 0, width, height)
        draw.Draw(img, r, &image.Uniform{palette[0]}, image.ZP, draw.Src)
 
        return img
}
 
func main() {
        images := []*image.Paletted{
                CreateImage(32, 32, 0),
                CreateImage(32, 32, 1),
        }
 
        delays := []int{100, 100}
 
        outfile, err := os.Create("08.gif")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        gif.EncodeAll(outfile, &gif.GIF{
                Image: images,
                Delay: delays,
        })
}

9. Složitější animace – pohybující se šachovnice

Nyní nám již nic nebrání si vyzkoušet vytvořit složitější animace. Můžeme například začít se šachovnicí, kterou jsme si ukázali minule. Funkci pro vykreslení šachovnice do obrázku je samozřejmě možné modifikovat tak, aby se celá šachnovnice pohybovala (scrollovala) v horizontálním i vertikálním směru. Postačuje přidat parametry xoffset, yoffset a step_size pro specifikaci maximálního posunu. Posuny musí být součástí smyček pro vykreslení jednotlivých políček šachovnice:

x_from := 0
x_to := hor_block
for x := 0; x < board_size+step_size; x++ {
        y_from := 0
        y_to := ver_block
        for y := 0; y < board_size+step_size; y++ {
                r := image.Rect(x_from+xoffset, y_from+yoffset, x_to+xoffset, y_to+yoffset)
                draw.Draw(img, r, &image.Uniform{palette[index_color]}, image.ZP, draw.Src)
                y_from = y_to
                y_to += ver_block
                index_color = 1 - index_color
        }
        x_from = x_to
        x_to += hor_block
        index_color = 1 - index_color
}
Poznámka: některé obdélníky (políčka) budou kresleny mimo vlastní obrázek, to však nemusí vadit (snad jen z hlediska rychlosti vykreslování).

Samotnou animaci můžeme vytvořit například takto:

steps := 2 * 256 / BoardSize
for step := 0; step < steps; step++ {
        img := CreateChessboard(256, 256, BoardSize, step*2-steps*2, step-steps, steps*2)
        images = append(images, img)
        delays = append(delays, 10)
}
Poznámka: povšimněte si, že využíváme faktu, že se vzorek se šachovnicí opakuje po 32 pixelech, takže výsledná animace má jen velmi malý počet snímků a tudíž i relativně malou velikost (méně než 170 kB).

Obrázek 21: Pohybující se šachovnice uložená do formátu GIF.

Celý demonstrační příklad nyní vypadá takto:

package main
 
import (
        "image"
        "image/color"
        "image/draw"
        "image/gif"
        "os"
)
 
const BoardSize = 8
 
func CreateChessboard(width int, height int, board_size int, xoffset int, yoffset int, step_size int) *image.Paletted {
        var palette = []color.Color{
                color.RGBA{150, 205, 50, 255},
                color.RGBA{0, 100, 0, 255},
        }
 
        img := image.NewPaletted(image.Rect(0, 0, width, height), palette)
 
        index_color := 0
        hor_block := int(width / board_size)
        ver_block := int(height / board_size)
 
        x_from := 0
        x_to := hor_block
        for x := 0; x < board_size+step_size; x++ {
                y_from := 0
                y_to := ver_block
                for y := 0; y < board_size+step_size; y++ {
                        r := image.Rect(x_from+xoffset, y_from+yoffset, x_to+xoffset, y_to+yoffset)
                        draw.Draw(img, r, &image.Uniform{palette[index_color]}, image.ZP, draw.Src)
                        y_from = y_to
                        y_to += ver_block
                        index_color = 1 - index_color
                }
                x_from = x_to
                x_to += hor_block
                index_color = 1 - index_color
        }
        return img
}
 
func main() {
        var images []*image.Paletted
        var delays []int
 
        steps := 2 * 256 / BoardSize
        for step := 0; step < steps; step++ {
                img := CreateChessboard(256, 256, BoardSize, step*2-steps*2, step-steps, steps*2)
                images = append(images, img)
                delays = append(delays, 10)
        }
 
        outfile, err := os.Create("09.gif")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        gif.EncodeAll(outfile, &gif.GIF{
                Image: images,
                Delay: delays,
        })
}

10. Volba metody přechodu mezi jednotlivými snímky animace

V grafickém formátu GIF je možné u každého snímku v animaci nastavit, jakým způsobem se tento snímek vlastně vykreslí přes snímek předchozí. Pro tuto volbu byly rezervovány tři bity; hodnota tohoto bitového pole potom určuje provedenou operaci:

Hodnota Význam
0 žádná operace není specifikována, prohlížeč si může vybrat, jak příkaz interpretovat
1 použije se původní snímek, nový přes něj bude překreslen
2 snímek se vymaže barvou pozadí
3 obnoví se původní obsah snímku
4–7 prozatím (téměř třicet let) není definováno

V dalším příkladu vytvoříme GIF se šesti snímky, přičemž v každém snímku se do animace přidá další barevný čtverec:

Obrázek 22: Výchozí parametr disposal a jeho vliv na výslednou animaci.

Přitom žádným způsobem nenastavujeme metodu přechodu mezi snímky:

package main
 
import (
        "image"
        "image/color"
        "image/draw"
        "image/gif"
        "os"
)
 
func CreateImage(width int, height int, imageIndex int) *image.Paletted {
        var palette = []color.Color{
                color.RGBA{150, 150, 150, 255},
                color.RGBA{250, 0, 0, 255},
        }
 
        img := image.NewPaletted(image.Rect(0, 0, width, height), palette)
 
        r := image.Rect(0, 0, width, height)
        draw.Draw(img, r, &image.Uniform{palette[0]}, image.ZP, draw.Src)
 
        r2 := image.Rect(imageIndex*60, 0, imageIndex*60+50, height)
        draw.Draw(img, r2, &image.Uniform{palette[1]}, image.ZP, draw.Src)
 
        return img
}
 
func main() {
        var images []*image.Paletted
        var delays []int
 
        for i := 0; i < 6; i++ {
                img := CreateImage(350, 50, i)
                images = append(images, img)
                delays = append(delays, 100)
        }
 
        outfile, err := os.Create("10.gif")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        gif.EncodeAll(outfile, &gif.GIF{
                Image: images,
                Delay: delays,
        })
}

11. Použití nastaveného pozadí GIFu

Příklad ovšem můžeme snadno upravit takovým způsobem, že jednotlivé snímky (kromě prvního) budou menší, než snímky předchozí, což je díky koncepci rámců zcela legální. Následně zvolíme, že se v průběhu animace má snímek na začátku vždy překreslit barvou pozadí:

var disposals []byte
 
for i := 0; i < 6; i++ {
        ...
        ...
        ...
        disposals = append(disposals, gif.DisposalBackground)
}

Způsob vytvoření výsledného GIFu:

gif.EncodeAll(outfile, &gif.GIF{
        Image:           images,
        Delay:           delays,
        Disposal:        disposals,
        BackgroundIndex: 0,
})

Výsledná animace:

Obrázek 23: Parametr disposal=DisposalBackground a jeho vliv na výslednou animaci.

Jaký má tato programová komplikace význam? Výsledný soubor s animací je výrazně menší – proti původním 1908 bajtům jsme dosáhli velikosti pouhých 734 bajtů.

Samozřejmě nezapomeneme ani na výpis celého zdrojového kódu příkladu:

package main
 
import (
        "image"
        "image/color"
        "image/draw"
        "image/gif"
        "os"
)
 
func CreateImage(width int, height int, imageIndex int) *image.Paletted {
        var palette = []color.Color{
                color.RGBA{150, 150, 150, 0},
                color.RGBA{250, 0, 0, 255},
        }
 
        if imageIndex == 0 {
                img := image.NewPaletted(image.Rect(0, 0, width, height), palette)
 
                r := image.Rect(0, 0, width, height)
                draw.Draw(img, r, &image.Uniform{palette[0]}, image.ZP, draw.Src)
 
                r2 := image.Rect(0, 0, 50, height)
                draw.Draw(img, r2, &image.Uniform{palette[1]}, image.ZP, draw.Src)
 
                return img
        } else {
                img := image.NewPaletted(image.Rect(imageIndex*60, 0, imageIndex*60+50, height), palette)
 
                r2 := image.Rect(imageIndex*60, 0, imageIndex*60+50, height)
                draw.Draw(img, r2, &image.Uniform{palette[1]}, image.ZP, draw.Src)
 
                return img
        }
 
}
 
func main() {
        var images []*image.Paletted
        var delays []int
        var disposals []byte
 
        for i := 0; i < 6; i++ {
                img := CreateImage(350, 50, i)
                images = append(images, img)
                delays = append(delays, 100)
                disposals = append(disposals, gif.DisposalBackground)
        }
 
        outfile, err := os.Create("11.gif")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        gif.EncodeAll(outfile, &gif.GIF{
                Image:           images,
                Delay:           delays,
                Disposal:        disposals,
                BackgroundIndex: 0,
        })
}

12. Přechod na další snímek bez smazání snímku předchozího

A konečně se podívejme, co se stane v případě, kdy se oblast s animací nebude mezi snímky nijak měnit. Nový snímek se tedy vykreslí do (nesmazané) oblasti. Pokud je nový snímek menší, než celá plocha s animací, lze takto prakticky zadarmo dosáhnout zajímavých efektů:

Obrázek 24: Parametr disposal=DisposalNone a jeho vliv na výslednou animaci.

Poznámka: zajímavé je, že soubor s animací má naprosto shodnou délku s animací předchozí. Je to vlastně logické, protože se soubory odlišují pouze několika bity v bitovém poli disposal method.

Samozřejmě si opět ukážeme úplný zdrojový kód tohoto příkladu, který nyní vypadá následovně:

package main
 
import (
        "image"
        "image/color"
        "image/draw"
        "image/gif"
        "os"
)
 
func CreateImage(width int, height int, imageIndex int) *image.Paletted {
        var palette = []color.Color{
                color.RGBA{150, 150, 150, 255},
                color.RGBA{250, 0, 0, 255},
        }
 
        if imageIndex == 0 {
                img := image.NewPaletted(image.Rect(0, 0, width, height), palette)
 
                r := image.Rect(0, 0, width, height)
                draw.Draw(img, r, &image.Uniform{palette[0]}, image.ZP, draw.Src)
 
                r2 := image.Rect(0, 0, 50, height)
                draw.Draw(img, r2, &image.Uniform{palette[1]}, image.ZP, draw.Src)
 
                return img
        } else {
                img := image.NewPaletted(image.Rect(imageIndex*60, 0, imageIndex*60+50, height), palette)
 
                r2 := image.Rect(imageIndex*60, 0, imageIndex*60+50, height)
                draw.Draw(img, r2, &image.Uniform{palette[1]}, image.ZP, draw.Src)
 
                return img
        }
 
}
 
func main() {
        var images []*image.Paletted
        var delays []int
        var disposals []byte
 
        for i := 0; i < 6; i++ {
                img := CreateImage(350, 50, i)
                images = append(images, img)
                delays = append(delays, 100)
                disposals = append(disposals, gif.DisposalNone)
        }
 
        outfile, err := os.Create("12.gif")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        gif.EncodeAll(outfile, &gif.GIF{
                Image:    images,
                Delay:    delays,
                Disposal: disposals,
        })
}

13. Vytvoření GIFu s animovaným progress barem

Se znalostmi, které jsme získali v předchozích kapitolách, si nyní můžeme ukázat vytvoření GIFu s animovaným progress barem. Celá animace obsahuje celých 200 snímků a přitom má velikost pouze 6074 bajtů, tedy přibližně pouze 30 bajtů na snímek:

Obrázek 25: Animace progress baru uložená jako obrázek typu GIF.

package main
 
import (
        "image"
        "image/color"
        "image/draw"
        "image/gif"
        "os"
)
 
func CreateImage(width int, height int, imageIndex int) *image.Paletted {
        var palette = []color.Color{
                color.RGBA{50, 50, 50, 255},
                color.RGBA{0, 200, 200, 255},
        }
 
        if imageIndex == 0 {
                img := image.NewPaletted(image.Rect(0, 0, width, height), palette)
 
                r := image.Rect(0, 0, width, height)
                draw.Draw(img, r, &image.Uniform{palette[0]}, image.ZP, draw.Src)
 
                return img
        } else {
                img := image.NewPaletted(image.Rect(imageIndex, 0, imageIndex+1, height), palette)
 
                r := image.Rect(imageIndex, 0, imageIndex+1, height)
                draw.Draw(img, r, &image.Uniform{palette[1]}, image.ZP, draw.Src)
 
                return img
        }
 
}
 
func main() {
        var images []*image.Paletted
        var delays []int
        var disposals []byte
 
        for i := 0; i < 200; i++ {
                img := CreateImage(200, 10, i)
                images = append(images, img)
                delays = append(delays, 2)
                disposals = append(disposals, gif.DisposalNone)
        }
 
        outfile, err := os.Create("13.gif")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        gif.EncodeAll(outfile, &gif.GIF{
                Image:    images,
                Delay:    delays,
                Disposal: disposals,
        })
}

14. Tvorba 2D grafiky s využitím knihovny GG

V další části článku se – prozatím ovšem pouze v rychlosti – seznámíme s některými možnostmi, které nám v oblasti 2D grafiky poskytuje knihovna nazvaná GG. Možnosti poskytované touto knihovnou připomínají Cairo popř. OpenVG a EGL: 2D scéna je vykreslována v immediate (přímém) režimu, přičemž se používá koncept takzvaných cest (path).

Samotná instalace knihovny GG je stejná, jako v případě jiných externích balíčků, tedy:

$ go get -u github.com/fogleman/gg

Po spuštění tohoto příkazu v adresáři, na nějž ukazuje proměnná prostředí GOPATH by měla vzniknout tato relativně složitá adresářová struktura:

├── bin
├── pkg
│   └── linux_amd64
│       └── github.com
│           └── fogleman
└── src
    ├── github.com
    │   ├── fogleman
    │   │   └── gg
    │   │       └── examples
    │   └── golang
    │       └── freetype
    │           ├── cmd
    │           │   └── print-glyph-points
    │           ├── example
    │           │   ├── capjoin
    │           │   ├── drawer
    │           │   ├── freetype
    │           │   ├── gamma
    │           │   ├── genbasicfont
    │           │   ├── raster
    │           │   ├── round
    │           │   └── truetype
    │           ├── licenses
    │           ├── raster
    │           ├── testdata
    │           └── truetype
    ├── golang.org
    │   └── x
    │       └── image
    │           ├── bmp
    │           ├── cmd
    │           │   └── webp-manual-test
    │           ├── colornames
    │           ├── draw
    │           ├── example
    │           │   └── font
    │           ├── font
    │           │   ├── basicfont
    │           │   ├── gofont
    │           │   │   ├── gobold
    │           │   │   ├── gobolditalic
    │           │   │   ├── goitalic
    │           │   │   ├── gomedium
    │           │   │   ├── gomediumitalic
    │           │   │   ├── gomono
    │           │   │   ├── gomonobold
    │           │   │   ├── gomonobolditalic
    │           │   │   ├── gomonoitalic
    │           │   │   ├── goregular
    │           │   │   ├── gosmallcaps
    │           │   │   ├── gosmallcapsitalic
    │           │   │   └── ttfs
    │           │   ├── inconsolata
    │           │   ├── opentype
    │           │   ├── plan9font
    │           │   ├── sfnt
    │           │   └── testdata
    │           │       └── fixed
    │           ├── math
    │           │   ├── f32
    │           │   ├── f64
    │           │   └── fixed
    │           ├── riff
    │           ├── testdata
    │           ├── tiff
    │           │   └── lzw
    │           ├── vector
    │           ├── vp8
    │           ├── vp8l
    │           └── webp

15. Parametry kreslení zapamatované v kontextu

Podívejme se nejprve na velmi jednoduchý příklad, který po svém spuštění vytvoří rastrový obrázek obsahující kružnici o poloměru 100 délkových jednotek. Ve výchozím nastavení odpovídá 100 délkových jednotek jednomu stu pixelů:

package main
 
import "github.com/fogleman/gg"
 
func main() {
        const width = 320
        const height = 240
 
        dc := gg.NewContext(width, height)
 
        dc.SetRGB(0.2, 0.0, 0.8)
        dc.DrawCircle(width/2, height/2, 100)
 
        dc.Fill()
 
        dc.SavePNG("14.png")
}

Obrázek 26: Kružnice vytvořená předchozím příkladem.

Povšimněte si, že parametry vykreslování (zde barva) se stává součástí takzvaného kontextu (context). Tyto parametry se použijí až tehdy, kdy zavoláme metodu Stroke nebo Fill. To znamená, že nezáleží na tom, zda nejprve vykreslíme kružnici a poté nastavíme její barvu či tyto dvě operace prohodíme:

package main
 
import "github.com/fogleman/gg"
 
func main() {
        const width = 320
        const height = 240
 
        dc := gg.NewContext(width, height)
 
        dc.DrawCircle(width/2, height/2, 100)
 
        dc.SetRGB(0.2, 0.0, 0.8)
        dc.Fill()
 
        dc.SavePNG("15.png")
}

Obrázek 27: Naprosto stejná kružnice vytvořená předchozím příkladem.

16. Kreslení otevřených tvarů (úseček) pomocí příkazu Stroke

V případě, že budeme potřebovat vykreslit dvě úsečky, každou s odlišnou barvou, je tedy nutné postupovat následovně: použijeme vždy trojici metod SetRGB, DrawLine a Stroke. Pokud v prvním případě na Stroke zapomeneme, bude sice úsečka vykreslena, ale druhou specifikovanou barvou:

package main
 
import "github.com/fogleman/gg"
 
func main() {
        const width = 320
        const height = 240
 
        dc := gg.NewContext(width, height)
 
        dc.SetRGB(0.2, 0.0, 0.8)
        dc.DrawLine(10, 10, width-10, height-10)
        dc.Stroke()
 
        dc.SetRGB(0.8, 0.0, 0.2)
        dc.DrawLine(10, height-10, width-10, 10)
        dc.Stroke()
 
        dc.SavePNG("16.png")
}

Obrázek 28: Dvojice úseček na průhledném pozadí, každá je vykreslena odlišnou barvou (jako samostatná cesta).

17. Vyplnění uzavřené křivky příkazem Fill

Velmi často se setkáme s potřebou vykreslit uzavřené tvary. K tomuto účelu slouží metoda nazvaná Fill. My ji dnes konkrétně použijeme pro vyplnění pozadí obrázku konstantní barvou. Pro jednoduchost bude použita bílá barva:

package main
 
import "github.com/fogleman/gg"
 
func main() {
        const width = 320
        const height = 240
 
        dc := gg.NewContext(width, height)
 
        dc.DrawRectangle(0, 0, width, height)
        dc.SetRGB(1.0, 1.0, 1.0)
        dc.Fill()
 
        dc.DrawLine(10, 10, width-10, height-10)
        dc.SetRGB(0.2, 0.0, 0.8)
        dc.Stroke()
 
        dc.DrawLine(10, height-10, width-10, 10)
        dc.SetRGB(0.8, 0.0, 0.2)
        dc.Stroke()
 
        dc.SavePNG("17.png")
}

Obrázek 29: Dvojice úseček na bílém pozadí, každá je vykreslena odlišnou barvou (jako samostatná cesta).

18. Nastavení základních parametrů cest: průhlednost, šířka tahu a styl vykreslení

Nastavit je možné i další parametry vykreslování. Ty si podrobně popíšeme příště, takže následující příklady považujte za ukázku některých možností nabízených knihovnou GG.

Změna průhlednosti úseček

package main
 
import "github.com/fogleman/gg"
 
func main() {
        const width = 320
        const height = 240
 
        dc := gg.NewContext(width, height)
 
        dc.DrawRectangle(0, 0, width, height)
        dc.SetRGB(1.0, 1.0, 1.0)
        dc.Fill()
 
        for i := 0; i < 256; i += 8 {
                alpha := float64(i) / 256.0
                dc.SetRGBA(0.0, 0.0, 0.0, alpha)
 
                x := float64(i + 32)
                dc.DrawLine(x, 20, x, height-20)
 
                dc.Stroke()
        }
 
        dc.SavePNG("18.png")
}

Obrázek 30: Postupná změna průhlednosti úseček.

Změna šířky pera při kreslení úseček

package main
 
import "github.com/fogleman/gg"
 
func main() {
        const width = 320
        const height = 240
 
        dc := gg.NewContext(width, height)
 
        dc.DrawRectangle(0, 0, width, height)
        dc.SetRGB(1.0, 1.0, 1.0)
        dc.Fill()
 
        dc.SetRGBA(0.0, 0.0, 0.0, 1)
 
        for i := 0; i < 256; i += 16 {
                width := float64(i) / 20
                dc.SetLineWidth(width)
 
                x := float64(i + 32)
                dc.DrawLine(x, 20, x, height-20)
 
                dc.Stroke()
        }
 
        dc.SavePNG("19.png")
}

Obrázek 31: Postupná změna šířky pera při kreslení úseček.

CS24_early

Specifikace vzorku vykreslování

package main
 
import "github.com/fogleman/gg"
 
func main() {
        const width = 320
        const height = 240
 
        dc := gg.NewContext(width, height)
 
        dc.DrawRectangle(0, 0, width, height)
        dc.SetRGB(1.0, 1.0, 1.0)
        dc.Fill()
 
        dc.SetRGBA(0.0, 0.0, 0.0, 1)
 
        dc.DrawLine(32, 20, 288, 20)
        dc.Stroke()
 
        dc.SetDash(10)
        dc.DrawLine(32, 40, 288, 40)
        dc.Stroke()
 
        dc.SetDash(10, 10)
        dc.DrawLine(32, 60, 288, 60)
        dc.Stroke()
 
        dc.SetDash(10, 5)
        dc.DrawLine(32, 80, 288, 80)
        dc.Stroke()
 
        dc.SetDash(10, 5, 2, 5)
        dc.DrawLine(32, 100, 288, 100)
        dc.Stroke()
 
        dc.SetLineWidth(4.0)
        dc.SetLineCap(gg.LineCapButt)
 
        dc.DrawLine(32, 140, 288, 140)
        dc.Stroke()
 
        dc.SetDash(10)
        dc.DrawLine(32, 160, 288, 160)
        dc.Stroke()
 
        dc.SetDash(10, 10)
        dc.DrawLine(32, 180, 288, 180)
        dc.Stroke()
 
        dc.SetDash(10, 5)
        dc.DrawLine(32, 200, 288, 200)
        dc.Stroke()
 
        dc.SetDash(10, 5, 2, 5)
        dc.DrawLine(32, 220, 288, 220)
        dc.Stroke()
 
        dc.SavePNG("20.png")
}

Obrázek 32: Úsečky vytvořené s různými vzorky (čárkovaná a čerchovaná čára atd.).

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

Zdrojové kódy všech dnes popsaných demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně jeden megabajt), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Demonstrační příklad Popis Cesta
1 01_blending.go ukázka blendingu (viz předchozí část seriálu) https://github.com/tisnik/go-fedora/blob/master/article15/01_blen­ding.go
2 02_gif_export_rgba.go export obrázku šachovnice do formátu GIF https://github.com/tisnik/go-fedora/blob/master/article15/02_gif_ex­port_rgba.go
3 03_gif_1×1_rgba.go export obrázku o rozlišení 1×1 pixel s 256 barvovou paletou https://github.com/tisnik/go-fedora/blob/master/article15/03_gif_1×1_rgba­.go
4 04_gif_1×1_palette.go export obrázku o rozlišení 1×1 pixel s dvoubarevnou paletou https://github.com/tisnik/go-fedora/blob/master/article15/04_gif_1×1_pa­lette.go
5 05_gif_export_colors.go export se specifikací počtu barev v paletě https://github.com/tisnik/go-fedora/blob/master/article15/05_gif_ex­port_colors.go
6 06_gif_export_palette.go šachovnice vykreslená s dvoubarevnou paletou https://github.com/tisnik/go-fedora/blob/master/article15/06_gif_ex­port_palette.go
7 07_rgba_to_palette.go převod obrázku z prostoru RGBA do prostoru s paletou se snižováním počtu barev https://github.com/tisnik/go-fedora/blob/master/article15/07_rgba_to_pa­lette.go
8 08_gif_animation_blink.go jednoduchá animace se dvěma snímky o velikosti 32×32 pixelů https://github.com/tisnik/go-fedora/blob/master/article15/08_gif_a­nimation_blink.go
9 09_gif_animation.go složitější animace pohybující se šachovnice https://github.com/tisnik/go-fedora/blob/master/article15/09_gif_a­nimation.go
10 10_default_disposal_methods.go výchozí hodnota metody použité při přepínání snímků https://github.com/tisnik/go-fedora/blob/master/article15/10_de­fault_disposal_methods.go
11 11_background_disposal_methods.go nastavení metody přepínání snímků: vykreslení pozadí https://github.com/tisnik/go-fedora/blob/master/article15/11_bac­kground_disposal_methods.go
12 12_none_disposal_methods.go nastavení metody přepínání snímků: žádná modifikace snímku https://github.com/tisnik/go-fedora/blob/master/article15/12_no­ne_disposal_methods.go
13 13_progress_bar.go animace progress baru vytvořená jako animovaný GIF https://github.com/tisnik/go-fedora/blob/master/article15/13_pro­gress_bar.go
14 14_gg_basic.go základní způsob použití knihovny GG https://github.com/tisnik/go-fedora/blob/master/article15/14_gg_ba­sic.go
15 15_gg_context_properties.go použití kontextu a nastavení vlastností kreslení https://github.com/tisnik/go-fedora/blob/master/article15/15_gg_con­text_properties.go
16 16_gg_path_stroke.go vykreslení cesty https://github.com/tisnik/go-fedora/blob/master/article15/16_gg_pat­h_stroke.go
17 17_gg_filled_background.go vyplnění pozadí konstantní barvou https://github.com/tisnik/go-fedora/blob/master/article15/17_gg_fi­lled_background.go
18 18_gg_alpha_rgba.go specifikace alfa kanálu (průhlednosti) https://github.com/tisnik/go-fedora/blob/master/article15/18_gg_al­pha_rgba.go
19 19_gg_line_width.go specifikace šířky vykreslovaných cest https://github.com/tisnik/go-fedora/blob/master/article15/19_gg_li­ne_width.go
20 20_gg_set_dash.go specifikace stylu vykreslovaných cest https://github.com/tisnik/go-fedora/blob/master/article15/20_gg_set_dash­.go

20. Odkazy na Internetu

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

Byl pro vás článek přínosný?

Autor článku

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