Hlavní navigace

Programovací jazyk Go a počítačová grafika (úvod)

Pavel Tišnovský

Dnes si popíšeme balíčky ze základní knihovny určené pro práci s rastrovou grafikou. Jedná se o zpracování rastrových obrázků společně s možností jejich exportu a importu, k dispozici jsou ovšem i rastrové operace.

Doba čtení: 39 minut

11. Barvové prostory RGBA a NRGBA

12. Barvový prostor CMYK

13. Význam složky K v barvovém prostoru CMYK

14. Balíček image/draw

15. Trik – vyplnění obrázku konstantní barvou s využitím obrazových operací

16. Vykreslení šachovnice

17. Vykreslení složitějších tvarů do rastrových obrázků

18. Implementace Bresenhamova algoritmu pro vykreslování úseček

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

20. Odkazy na Internetu

1. Programovací jazyk Go a počítačová grafika

Programovací jazyk Go se sice primárně (minimálně v současnosti) používá pro tvorbu síťových aplikRGBA, popř. utilit a služeb, v nichž je možné využít možností poskytovaných gorutinami a kanály, ovšem můžeme ho poměrně elegantně použít i při tvorbě (renderingu) a úpravách rastrových obrázků. Jazyk Go již ve své základní knihovně totiž obsahuje balíček nazvaný image, který je možné pro tyto účely využít. Navíc mají vývojáři k dispozici i podbalíčky určené pro kódování a dekódování rastrových obrázků z/do formátů GIF (Graphics Interchange Format), PNG (Portable Network Graphics) a JPEG (Joint Photographic Experts Group, přesněji však JFIF, JPEG File Interchange Format), tj. přesně těch formátů, které jsou používány na webu.

Podrobnější informace o rastrových formátech GIF, PNG a JPEG již byly (nutno dodat, že v dosti dávné minulosti :-) na Rootu zveřejněny:

  1. Případ GIF
    https://www.root.cz/clanky/pripad-gif/
  2. Pravda a mýty o GIFu
    https://www.root.cz/clanky/pravda-a-myty-o-gifu/
  3. Anatomie grafického formátu GIF
    https://www.root.cz/clanky/anatomie-grafickeho-formatu-gif/
  4. GIF: animace a konkurence
    https://www.root.cz/clanky/gif-animace-a-konkurence/
  5. PNG is Not GIF
    https://www.root.cz/clanky/png-is-not-gif/
  6. Anatomie grafického formátu PNG
    https://www.root.cz/clanky/anatomie-grafickeho-formatu-png/
  7. PNG – bity, byty, chunky
    https://www.root.cz/clanky/png-bity-byty-chunky/
  8. Řádkové filtry v PNG
    https://www.root.cz/clanky/radkove-filtry-v-png/
  9. Nepovinné chunky v PNG a kontrola pomocí CRC
    https://www.root.cz/clanky/nepovinne-chunky-v-png-a-kontrola-pomoci-crc/
  10. Finišujeme s PNG – Textové metainformace a kalibrační data
    https://www.root.cz/clanky/finisujeme-s-png-textove-metainformace-a-kalibracni-data/
  11. JPEG – král rastrových grafických formátů?
    https://www.root.cz/clanky/jpeg-kral-rastrovych-grafickych-formatu/
  12. Ztrátová komprese obrazových dat pomocí JPEG
    https://www.root.cz/clanky/ztratova-komprese-obrazovych-dat-pomoci-jpeg/
  13. Programujeme JPEG: transformace a podvzorkování barev
    https://www.root.cz/clanky/pro­gramujeme-jpeg-transformace-a-podvzorkovani-barev/
  14. Programujeme JPEG: diskrétní kosinová transformace (DCT)
    https://www.root.cz/clanky/pro­gramujeme-jpeg-diskretni-kosinova-transformace-dct/
  15. Programujeme JPEG: Kvantizace DCT koeficientů
    https://www.root.cz/clanky/pro­gramujeme-jpeg-kvantizace-dct-koeficientu/
  16. Programujeme JPEG: Huffmanovo kódování kvantovaných DCT složek
    https://www.root.cz/clanky/pro­gramujeme-jpeg-huffmanovo-kodovani-kvantovanych-dct-slozek/
  17. Programujeme JPEG: Interní struktura souborů typu JFIF/JPEG
    https://www.root.cz/clanky/pro­gramujeme-jpeg-interni-struktura-souboru-typu-jfifjpeg/
  18. Programujeme JPEG: Načtení informací ze souborů typu JFIF/JPEG
    https://www.root.cz/clanky/pro­gramujeme-jpeg-nacteni-informaci-ze-souboru-typu-jfifjpeg/
  19. Programujeme JPEG: Progresivní JPEG a informace EXIF
    https://www.root.cz/clanky/pro­gramujeme-jpeg-progresivni-jpeg-a-informace-exif/

Ve druhé části článku si popíšeme i základní možnosti poskytované module draw, který umožňuje provádění rastrových operací nad dvojicí či trojicí obrázků. Ovšem tento modul není určen pro kreslení složitějších grafických tvarů – k tomuto účelu je nutné použít nějakou externí knihovnu (o nichž se samozřejmě taktéž zmíníme).

2. Inicializace prázdného rastrového obrázku s jeho uložením na disk

V demonstračních příkladech popsaných v navazujících kapitolách budeme používat takové reprezentace rastrových obrázků, které jsou plně kompatibilní se základní knihovnou (přesněji řečeno s balíčky základní knihovny) programovacího jazyka Go. Při použití této knihovny může být samotný rastrový obrázek reprezentován různými datovými strukturami, například RGBA, NRGBA, CMYK, Gray atd. – podle toho, jaký barvový model je použit. Ovšem nezávisle na tom, o jakou konkrétní datovou strukturu (a tím pádem o jaký barvový model) se jedná, bude vždy implementováno rozhraní nazvané Image, pro nějž jsou v balíčku image předepsány pouhé tři metody:

type Image interface {
        ColorModel() color.Model
        Bounds() Rectangle
        At(x, y int) color.Color
}

První z těchto metod vrací barvový model použitý pro reprezentaci barev jednotlivých pixelů v obrázku. Druhá metoda vrací obdélník (rectangle), kterým se určují okraje (meze) obrázku, přičemž je nutné upozornit na to, že levý horní roh obdélníku obecně nemusí začínat na souřadnicích [0, 0], což se týká například jednotlivých snímků použitých v grafickém formátu GIF ve chvíli, kdy jsou použity animace (jednotlivé rozdílové snímky jsou obecně menší, než celý obrázek). A konečně poslední metoda vrací barvu pixelu na souřadnicích [x, y]. Tato metoda je pochopitelně velmi pomalá, zejména v porovnání s přímým přístupem k polím bajtů s obsahem jednotlivých pixelů, ovšem na druhou stranu je tato metoda zcela univerzální a programátor se nemusí zabývat tím, jakým způsobem je vlastně bitmapa obrázku interně reprezentována.

Poznámka: podrobnější informace o tomto rozhraní získáte, jak je ostatně v ekosystému programovacího jazyka Go obvyklé, příkazem godoc. Syntaxe tohoto příkazu je následující:
$ godoc image Image

Dalším důležitým rozhraním, s nímž se velmi často setkáme, je rozhraní nazvané Color, které je deklarováno v balíčku image/color. Toto rozhraní předepisuje jedinou metodu RGBA, která vrací barvu ve formátu čtveřice R (red), G (green), B (blue) a A (alpha, průhlednost):

type Color interface {
        RGBA() (r, g, b, a uint32)
}

Zajímavé je, že všechny tři barvové složky i průhlednost jsou vráceny jako hodnoty typu uint32, i když se v běžných rastrových formátech každá složka ukládá do jediného bajtu. Je tomu tak z toho důvodu, že barvové složky jsou vynásobeny hodnotou alfa (průhledností) a jejich hodnoty tedy leží v rozsahu 0 až 65025. Pokud se na dva obrázky aplikují rastrové operace popsané ve druhé části článku, je zaručeno, že díky rozsahu datového typu uint32 (0 až 232-1) nikdy nedojde k přetečení hodnot barvových složek.

Poznámka: rozhraní Color vždy předepisuje metodu RGBA, a to nezávisle na tom, jaký barvový model je použit pro uložení samotného obrázku. Interně tedy bude docházet k přepočtům barev pixelů (což není vždy zcela přesná operace).

3. Inicializace prázdného rastrového obrázku s jeho uložením na disk

V praktické části článku se nejprve podívejme na způsob inicializace prázdného rastrového obrázku. Rozlišení obrázku, přesněji řečeno počet obrazových řádků a počet pixelů na každém obrazovém řádku, bude pro jednoduchost uloženo v konstantách se jmény width a height:

const width = 256
const height = 256

Dále budeme muset vytvořit a inicializovat datovou strukturu typu Rectangle, v níž bude uložen obdélník určující rozměry obrázku i jeho umístění v rámci roviny. Pro jednoduchost bude levý horní roh obdélníku ležet na souřadnicích [0, 0] a pravý dolní roh na souřadnicích [width, heigh]. Struktura typu Rectangle sice obsahuje dva prvky typu Point, takže je inicializace takové struktury relativně složitá, ovšem namísto toho můžeme použít pomocnou funkci Rect(), které se předají čtyři celočíselné hodnoty – souřadnice levého horního rohu a souřadnice pravého dolního rohu. Inicializace obdélníku tedy bude vypadat takto:

r := image.Rect(0, 0, width, height))
Poznámka: datová struktura Rectangle bude podrobněji popsána v deváté kapitole.

Nyní již máme připraveno vše potřebné pro vytvoření prázdného obrázku, resp. přesněji řečeno takového obrázku, v němž budou jednotlivé pixely obsahovat barvu (0, 0, 0, 0), protože kromě barvových složek R, G, B musíme správně nastavit i průhlednost, neboli alfa kanál. Prázdný obrázek vytvoříme jednoduše:

img := image.NewRGBA(r)

Prozatím sice neumíme obrázek přímo zobrazit na displeji, ale dokážeme ho velmi snadno uložit například do souboru typu PNG. Nejprve otevřeme soubor pojmenovaný „01.png“ pro zápis a poté využijeme možnosti poskytované balíčkem image/png pro zakódování obrázku do formátu PNG::

outfile, err := os.Create("01.png")
if err != nil {
        panic(err)
}
defer outfile.Close()
png.Encode(outfile, img)

To je vše – úplný zdrojový kód dnešního prvního demonstračního příkladu naleznete na adrese https://github.com/tisnik/go-fedora/blob/master/article14/01_em­pty_image.go:

package main
 
import (
        "image"
        "image/png"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewRGBA(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("01.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
        png.Encode(outfile, img)
}

Povšimněte si, že obrázek obsahuje bitmapu o rozměrech 256×256 pixelů, které jsou průhledné (a mají černou barvu, to však kvůli průhlednosti nevidíme). To je výchozí chování datového typu NewRGBA.

Obrázek 1: Výsledek prvního demonstračního příkladu. Všechny pixely jsou ve skutečnosti průhledné – šachovnice je výsledkem zobrazení bitmapy v prohlížeči obrázků.

Alternativně samozřejmě můžeme příklad přepsat takovým způsobem, aby se datová struktura Rectangle vytvořila přímo „konstruktorem“ struktur (zápis se složenými závorkami):

package main
 
import (
        "image"
        "image/png"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        rect := image.Rectangle{image.Point{0, 0},
                                image.Point{width, height}}
 
        img := image.NewNRGBA(rect)
 
        outfile, err := os.Create("02.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
        png.Encode(outfile, img)
}

4. Vyplnění všech pixelů rastrového obrázku konstantní barvou

První manipulací s obsahem rastrového obrázku, s níž se dnes seznámíme, bude vyplnění všech pixelů bitmapy nějakou barvou. Prozatím budeme používat barvový model RGBA. Pro tento datový typ existuje metoda určená pro změnu barvy pixelu. Tato metoda se jmenuje příznačně SetRGBA a její hlavička vypadá následovně:

func (p *RGBA) SetRGBA(x, y int, c color.RGBA)

Této metodě se předávají tři parametry:

  1. x-ová souřadnice pixelu
  2. y-ová souřadnice pixelu
  3. barva pixelu, což je struktura typu color.RGBA (color je neúplné jméno balíčku image/color), v němž je každá barvová komponenta reprezentována jediným bajtem (tak, jak jsme zvyklí z většiny grafických knihoven)

Tato struktura je deklarována následujícím způsobem:

type RGBA struct {
        R, G, B, A uint8
}

Strukturu RGBA naplníme naprosto stejným způsobem, jako jakýkoli jiný záznam. Nastavíme přitom čistě zelenou barvu, která bude neprůhledná (poslední hodnota 255 určuje neprůhlednost pixelů):

c := color.RGBA{0, 255, 0, 255}

Jakmile máme připravenou barvu pixelů, můžeme jí vyplnit všechny pixely tvořící rastrový obrázek:

for x := 0; x < width; x++ {
        for y := 0; y < height; y++ {
                img.SetRGBA(x, y, c)
        }
}
Poznámka: tento program bude poměrně pomalý, což sice u obrázku s rozlišením 256×256 pixelů nebude příliš patrné, ale u obrázků s větším rozlišením je většinou lepší použít nízkoúrovňový přístup popsaný v dalších kapitolách.

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

package main
 
import (
        "image"
        "image/color"
        "image/png"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewRGBA(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("03.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        c := color.RGBA{0, 255, 0, 255}
        for x := 0; x < width; x++ {
                for y := 0; y < height; y++ {
                        img.SetRGBA(x, y, c)
                }
        }
        png.Encode(outfile, img)
}

Obrázek 2: Výsledek běhu demonstračního příkladu, v němž jsou pixely vybarveny metodou SetRGBA.

I tento příklad je možné nepatrně modifikovat, a to takovým způsobem, že se namísto metody SetRGBA, která je přímo navázána na barvový prostor RGBA, použije univerzální metoda Set, jíž se opět předají souřadnice pixelu a jeho barva. Výsledkem je program, v němž je změna barvového prostoru přeci jen snazší:

package main
 
import (
        "image"
        "image/color"
        "image/png"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewRGBA(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("04.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        c := color.RGBA{0, 255, 0, 255}
        for x := 0; x < width; x++ {
                for y := 0; y < height; y++ {
                        img.Set(x, y, c)
                }
        }
        png.Encode(outfile, img)
}

5. Alfa kanál: rozdíl mezi barvovými prostory RGBA a NRGBA

Nyní si vyzkoušejme zdrojový kód předchozích příkladů nepatrně upravit, a to takovým způsobem, že barva všech pixelů sice bude stále čistě zelená, ovšem průhlednost se bude měnit v horizontálním směru od 0 (zcela průhledná) do 255 (zcela neprůhledná). Barva tedy bude vytvářena uvnitř vnější programové smyčky:

for x := 0; x < width; x++ {
        alpha := byte(x)
        c := color.RGBA{0, 255, 0, alpha}
        for y := 0; y < height; y++ {
                img.SetRGBA(x, y, c)
        }
}

Opět si samozřejmě ukážeme, jak byla tato změna zakomponována do úplného demonstračního příkladu:

package main
 
import (
        "image"
        "image/color"
        "image/png"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewRGBA(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("05.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        for x := 0; x < width; x++ {
                alpha := byte(x)
                c := color.RGBA{0, 255, 0, alpha}
                for y := 0; y < height; y++ {
                        img.SetRGBA(x, y, c)
                }
        }
        png.Encode(outfile, img)
}

Pokud se však podíváte na vytvořený rastrový obrázek, je patrné, že neodpovídá očekávanému výsledku:

Obrázek 3: Výsledek činnosti předchozího příkladu. Povšimněte si, že průhlednost – oproti očekávání – neroste lineárně od levého okraje k okraji pravému.

Proč tomu tak je? V barvovém prostoru RGBA (přesněji řečeno ve verzi podporované knihovnami jazyka Go) jsou jednotlivé barvové složky (R, G, B) přednásobeny hodnotou uloženou do alfa kanálu, což například umožňuje efektivní provádění operace blendingu (zjednodušeně řečeno – násobení se provede pouze jednou při změně hodnot pixelů, ovšem nikoli při provádění blendingu). Ovšem kvůli přednásobení dvou hodnot v rozsahu 0..255 dochází k přetečení přes rozsah typu byte/uint8, což je v našem případě patrné na hodnotách zelené barvové složky (další dvě složky jsou nulové, takže tam při přednásobení k žádnému přetečení samozřejmě nedochází).

Řešení tohoto problému je ve skutečnosti velmi snadné – namísto barvového prostoru RGBA použijte barvový prostor NRGBA, což vyžaduje jen nepatrné změny ve zdrojovém kódu:

package main
 
import (
        "image"
        "image/color"
        "image/png"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewNRGBA(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("06.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        for x := 0; x < width; x++ {
                alpha := byte(x)
                c := color.NRGBA{0, 255, 0, alpha}
                for y := 0; y < height; y++ {
                        img.SetNRGBA(x, y, c)
                }
        }
        png.Encode(outfile, img)
}

Výsledek již bude v tuto chvíli v pořádku, o čemž se můžeme snadno přesvědčit:

Obrázek 4: Upravený příklad využívající barvový model NRGBA již dává korektní výsledky.

Poznámka: ve skutečnosti nás bude rozdíl mezi RGBA a NRGBA zajímat pouze ve chvíli, kdy se pracuje s poloprůhlednými pixely. V případě práce s rastrovými obrázky, v nichž jsou všechny pixely neprůhledné, je možné barvový prostor RGBA bez problémů použít.

Jen pro úplnost si ukažme, jak vypadá stejný příklad, ovšem využívající metodu Set pro změnu barev pixelů v rastrovém obrázku:

package main
 
import (
        "image"
        "image/color"
        "image/png"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewNRGBA(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("07.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        for x := 0; x < width; x++ {
                alpha := byte(x)
                c := color.NRGBA{0, 255, 0, alpha}
                for y := 0; y < height; y++ {
                        img.Set(x, y, c)
                }
        }
        png.Encode(outfile, img)
}

6. Interní struktura záznamu s rastrovým obrázkem

Interní struktura záznamu, který nese informace o rastrovém obrázku, nejsou ve skutečnosti nijak tajné. Jedná se o běžný záznam s viditelnými prvky (jsou pojmenovány s velkým písmenem na začátku). Pro obrázky využívající barvový prostor RGBA vypadá záznam následovně:

type RGBA struct {
        Pix []uint8
        Stride int
        Rect Rectangle
}

Kupodivu není žádný rozdíl mezi obrázky s barvovým modelem RGBA a jinými barvovými modely. Jen pro představu se podívejme na prvky záznamu nesoucího informace o obrázcích s barvovým prostorem NRGBA:

type NRGBA struct {
        Pix []uint8
        Stride int
        Rect Rectangle

Či RGBA64, v němž jsou barvové složky reprezentovány 16bitovými hodnotami:

type RGBA64 struct {
        Pix []uint8
        Stride int
        Rect Rectangle
}

Co to pro nás znamená z praktického hlediska? Implementace některých nízkoúrovňových operací může být unifikována a nemusíme se starat o to, jak jsou vlastně interpretovány barvy jednotlivých pixelů.

Jednotlivé prvky struktury mají tento význam:

Prvek Stručný popis
Pix hodnoty jednotlivých pixelů uložené za sebou (řádky shora dolů, pixely zleva doprava)
Stride offset reprezentovaný v bajtech mezi dvěma pixely, které leží pod sebou
Rect obdélník určující pozici a rozměry obrázku v rámci 2D roviny

Prvek Stride je poměrně důležitý, protože nám umožňuje relativně snadnou interpretaci pole bajtů Pix tak, aby byl umožněn „2D přístup“ k jednotlivým pixelům. Pokud například budeme mít obrázek s rozlišením 1024×768 pixelů a použijeme barvový model RGBA, může být hodnota Stride nastavena na 1024×4=4096, popř. na vyšší hodnotu v případě, že mezi obrazovými řádky budou umístěny výplňové bajty (což se teoreticky může stát, například pro snadnější vykreslování, použití obrázku jako textury atd. atd.).

V dalším demonstračním příkladu je ukázáno, jak lze přistupovat k jednotlivým prvkům struktury typu NRGBA:

package main
 
import (
        "image"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewNRGBA(image.Rect(0, 0, width, height))
        println("Stride: ", img.Stride)
        println("[]byte: ", len(img.Pix))
        r := img.Rect
        println("Rectangle:")
        println("    point 1: ", r.Min.X, r.Min.Y)
        println("    point 2: ", r.Max.X, r.Max.Y)
}

Výsledky jsou následující:

Stride:  1024
[]byte:  262144
Rectangle:
    point 1:  0 0
    point 2:  256 256

Jejich význam je tento:

  1. Stride obsahuje hodnotu 1024, což odpovídá horizontálnímu rozlišení 256 pixelů a čtyřem bajtům na pixel.
  2. Celková velikost pole s pixely je také snadno odvoditelná: 256×256×4 se skutečně rovná 262144.
  3. U obdélníku s rozměry a umístěním obrázku je patrné, že souřadnice pravého dolního rohu již leží mimo vlastní obrázek.

7. Přímý přístup k poli s pixely rastrového obrázku

Znalost interní struktury záznamů typu RGBA, NRGBA atd. nám umožní přímý přístup k poli Pix, které obsahuje hodnoty jednotlivých pixelů rastrového obrázku. V následujícím demonstračním příkladu se obrázek vyplní konstantní barvou, u níž se však postupně (ve směru x-ové osy) mění hodnota průhlednosti. Ve vnější programové smyčce vypočteme index prvního bajtu na y-ovém obrazovém řádku:

index := img.Stride * y

Ve vnitřní smyčce se postupně vyplní (nastaví) barvy všech pixelů na jednom obrazovém řádku – každý pixel je přitom reprezentován čtyřmi bajty:

for x := 0; x < width; x++ {
        img.Pix[index] = 0
        index++
        img.Pix[index] = 0
        index++
        img.Pix[index] = 255
        index++
        img.Pix[index] = byte(x)
        index++
}

Výsledkem bude tento obrázek:

Obrázek 5: Výsledek získaný předchozím demonstračním příkladem.

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

package main
 
import (
        "image"
        "image/png"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewNRGBA(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("06.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        for y := 0; y < height; y++ {
                index := img.Stride * y
                for x := 0; x < width; x++ {
                        img.Pix[index] = 0
                        index++
                        img.Pix[index] = 0
                        index++
                        img.Pix[index] = 255
                        index++
                        img.Pix[index] = byte(x)
                        index++
                }
        }
        png.Encode(outfile, img)
}

Pokud vám připadá výpočet indexu prvního pixelu na určitém obrazovém řádku příliš nízkoúrovňový, je možné řádek:

index := img.Stride * y

nahradit za:

index := img.PixOffset(0, y)

Zbytek příkladu by mohl zůstat nezměněný:

package main
 
import (
        "image"
        "image/png"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewNRGBA(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("07.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        for y := 0; y < height; y++ {
                index := img.PixOffset(0, y)
                for x := 0; x < width; x++ {
                        img.Pix[index] = 0
                        index++
                        img.Pix[index] = 0
                        index++
                        img.Pix[index] = 255
                        index++
                        img.Pix[index] = byte(x)
                        index++
                }
        }
        png.Encode(outfile, img)
}

Obrázek 6: Výsledek získaný upraveným demonstračním příkladem.

8. Třetí varianta využívající řez polem

Z předchozích článků s popisem vlastností programovacího jazyka Go již víme, že nemůžeme použít ukazatelovou aritmetiku, takže například není možné získat ukazatel na první pixel na řádku a poté pouze zvyšovat hodnotu tohoto ukazatele. Ovšem získat můžeme řez polem, takže se možnostem ukazatelové aritmetiky lze alespoň nepatrně přiblížit. V dalším příkladu se pro každý obrazový řádek získá řez, který začíná prvním pixelem na tomto řádku:

scanline := img.Pix[img.Stride*y:]

V interní smyčce se pak použije tento řez prakticky stejným způsobem, jako by se jednalo o pole:

for x := 0; x < width; x++ {
        scanline[i] = 0
        i++
        scanline[i] = byte(y)
        i++
        scanline[i] = 255
        i++
        scanline[i] = byte(x)
        i++
}

Podívejme se na úplný zdrojový kód tohoto příkladu:

package main
 
import (
        "image"
        "image/png"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewNRGBA(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("08.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        for y := 0; y < height; y++ {
                scanline := img.Pix[img.Stride*y:]
                i := 0
                for x := 0; x < width; x++ {
                        scanline[i] = 0
                        i++
                        scanline[i] = byte(y)
                        i++
                        scanline[i] = 255
                        i++
                        scanline[i] = byte(x)
                        i++
                }
        }
        png.Encode(outfile, img)
}

Obrázek 7: Výsledek získaný upraveným demonstračním příkladem.

9. Struktury Point a Rectangle

Při používání balíčků image a image/draw se velmi často setkáme se strukturami pojmenovanými Point a Rectangle. Struktura Point, jak již ostatně její název napovídá, obsahuje informaci o bodu v rovině, přičemž souřadnice bodu jsou reprezentovány celými čísly se znaménkem:

type Point struct {
        X, Y int
}

Naproti tomu struktura Rectangle slouží ke specifikaci osově orientovaného obdélníka, u něhož je nutné si pamatovat dva protilehlé body. Tyto body jsou reprezentovány výše zmíněnými strukturami Point:

type Rectangle struct {
        Min, Max Point
}

Tyto struktury samozřejmě můžeme inicializovat přímo, jako jakoukoli jinou datovou strukturu v Go (povšimněte si použití složených závorek):

point2 := image.Point{10, 10}
rectangle2 := image.Rectangle{image.Point{0, 0}, image.Point{320, 240}}

Ovšem můžeme – a děje se to velmi často – použít i konstruktory Pt a Rect, které se volají jako běžné funkce:

point1 := image.Pt(10, 10)
rectangle1 := image.Rect(0, 0, 320, 240)

Právě s tímto přístupem při konstrukci struktur Point a Rectangle se setkáme nejčastěji:

package main
 
import (
        "image"
)
 
func main() {
        point1 := image.Pt(10, 10)
        point2 := image.Point{10, 10}
 
        rectangle1 := image.Rect(0, 0, 320, 240)
        rectangle2 := image.Rectangle{image.Point{0, 0}, image.Point{320, 240}}
 
        println(point1.String())
        println(point2.String())
 
        println(rectangle1.String())
        println(rectangle2.String())
}

Výsledek předchozího příkladu ukazuje ekvivalenci mezi přímou inicializací struktur a použitím konstruktorů:

(10,10)
(10,10)
(0,0)-(320,240)
(0,0)-(320,240)

10. Operace, které jsou strukturami Point a Rectangle podporovány

V balíčku image taktéž existuje několik metod určených pro datové struktury Point a Rectangle. Jedná se zejména o test, zda nějaký bod leží v obdélníku či mimo něj:

point1 := image.Pt(10, 10)
rectangle1 := image.Rect(0, 0, 200, 200)
 
println(point1.In(rectangle1))

Dále lze zjistit, zda se dva obdélníky překrývají či nikoli:

rectangle1 := image.Rect(0, 0, 200, 200)
rectangle2 := image.Rect(100, 100, 300, 300)
rectangle3 := image.Rect(300, 300, 400, 400)
 
println(rectangle1.Overlaps(rectangle2))
println(rectangle1.Overlaps(rectangle3))

A konečně existují operace pro sjednocení dvou obdélníků (vznikne nový obdélník obsahující oba zdrojové obdélníky) a pro průnik dvou obdélníků (výsledkem je nový obdélník):

rectangle1 := image.Rect(0, 0, 200, 200)
rectangle2 := image.Rect(100, 100, 300, 300)
rectangle3 := image.Rect(300, 300, 400, 400)
 
println(rectangle1.Union(rectangle2).String())
println(rectangle1.Intersect(rectangle2).String())

Tyto operace jsou ukázány ve třináctém demonstračním příkladu:

package main
 
import (
        "image"
)
 
func main() {
        point1 := image.Pt(10, 10)
        point2 := image.Pt(1000, 10)
 
        rectangle1 := image.Rect(0, 0, 200, 200)
        rectangle2 := image.Rect(100, 100, 300, 300)
        rectangle3 := image.Rect(300, 300, 400, 400)
 
        println(point1.In(rectangle1))
        println(point2.In(rectangle1))
 
        println(rectangle1.Union(rectangle2).String())
        println(rectangle1.Intersect(rectangle2).String())
 
        println(rectangle1.Overlaps(rectangle2))
        println(rectangle1.Overlaps(rectangle3))
}

Výsledky:

true
false
(0,0)-(300,300)
(100,100)-(200,200)
true
false

11. Barvové prostory RGBA a NRGBA

S barvovými prostory RGBA a NRGBA jsme se již setkali v předchozích kapitolách. 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í. Pro úplnost je pod tímto odstavcem vypsán zdrojový kód příkladu, který vytvoří obrázek se čtyřmi gradientními přechody:

package main
 
import (
        "image"
        "image/color"
        "image/png"
        "os"
)
 
const width = 512
const height = 512
 
func main() {
        img := image.NewNRGBA(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("10.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        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)
                }
        }
        png.Encode(outfile, img)
}

Obrázek 8: Čtveřice gradientních přechodů, všechny pixely jsou neprůhledné.

12. Barvový prostor CMYK

V předchozích demonstračních příkladech jsme používali barvový prostor RGBA, popř. NRGBA. Tento barvový prostor je v praxi používán velmi často, mj. i proto, že jednotlivé barvové složky R, G, B přímo odpovídají způsobům zobrazení pixelů na displejích (hodnoty barvových složek se sčítají). Ovšem při přípravě barevných obrázků pro tisk je prostor RGBA či NRGBA nevýhodný, neboť barevný soutisk je prováděn zcela odlišnou technikou – odečítáním barev od barvy papíru (každá natištěná barva funguje jako filtr určité části barvového spektra). Z tohoto důvodu se při přípravě grafiky pro tisk používá barvový model CMY (Cyan, Magenta, Yellow) a tiskárny (většinou) obsahují tonery či inkousty právě těchto barev. Z praktických důvodů jsou tyto tři barvy doplněny ještě o barvu černou – vede to jak k ušetření barev při tisku textů a obrázků v odstínech šedi, tak i k možnosti tisku skutečně černou barvou. Výsledkem je barvový model či barvový prostor CMYK (Cyan, Magenta, Yellow, blacK).

I s tímto barvovým prostorem je možné v programovacím jazyku Go pracovat a to prakticky stejným způsobem, jako tomu bylo u modelů RGBA a NRGBA. Obrázek s barvami pixelů reprezentovaných v modelu CMYK je představován strukturou/záznamem se jménem CMYK:

type CMYK struct {
        Pix []uint8
        Stride int
        Rect Rectangle
}

Pro vytvoření nového obrázku, který odpovídá tomuto barvovému modelu, je možné použít konstruktor NewCMYK(), který akceptuje stejné parametry jako nám již známé konstruktory NewRGBA() a NewNRGBA():

img := image.NewCMYK(image.Rect(0, 0, width, height))

Barva v barvovém modelu CMYK je reprezentována strukturou color.CMYK, kterou je možné nastavit zcela běžným způsobem:

c := color.CMYK{c, m, y, k}

Další práce s obrázkem, například jeho vyplnění pixely s různou barvou, je již prakticky stejné, jako ve všech předchozích příkladech. V dalším úryvku kódu vyplníme obrázek o rozlišení 512×512 pixelů čtyřmi podoblastmi, v nichž každé bude jiný barvový přechod (gradient); to vše vypočtené v barvovém prostoru CMYK:

for x := 0; x < 256; x++ {
        for y := 0; y < 256; y++ {
                c := color.CMYK{byte(x), 0, 0, 0}
                img.SetCMYK(x, y, c)
 
                c = color.CMYK{0, byte(x), 0, 0}
                img.SetCMYK(x+256, y, c)
 
                c = color.CMYK{0, 0, byte(x), 0}
                img.SetCMYK(x, y+256, c)
 
                c = color.CMYK{0, 0, 0, byte(x)}
                img.SetCMYK(x+256, y+256, c)
        }
}

13. Význam složky K v barvovém prostoru CMYK

Poslední složka K (blackK) v barvovém prostoru CMYK určuje podíl černé barvy pixelu. Pokud bude tato hodnota nulová, získáme odstíny získané rozdílem mezi složkami cyan, magenta a yellow:

Obrázek 9: Postupná změna intenzity jednotlivých složek CMYK. V prvních třech oblastech je hodnota složky K nulová.

Výše uvedený obrázek byl získán tímto demonstračním příkladem:

package main
 
import (
        "image"
        "image/color"
        "image/png"
        "os"
)
 
const width = 512
const height = 512
 
func main() {
        img := image.NewCMYK(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("11.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        for x := 0; x < 256; x++ {
                for y := 0; y < 256; y++ {
                        c := color.CMYK{byte(x), 0, 0, 0}
                        img.SetCMYK(x, y, c)
 
                        c = color.CMYK{0, byte(x), 0, 0}
                        img.SetCMYK(x+256, y, c)
 
                        c = color.CMYK{0, 0, byte(x), 0}
                        img.SetCMYK(x, y+256, c)
 
                        c = color.CMYK{0, 0, 0, byte(x)}
                        img.SetCMYK(x+256, y+256, c)
                }
        }
        png.Encode(outfile, img)
}

Hodnotu K však můžeme zvýšit, například na 50%, což odpovídá hodnotě 127:

Obrázek 10: Barevné přechody v modelu CMYK, černá složka je nastavena na hodnotu 50% (127).

Zdrojový kód tohoto příkladu se změní jen nepatrně:

package main
 
import (
        "image"
        "image/color"
        "image/png"
        "os"
)
 
const width = 512
const height = 512
 
func main() {
        img := image.NewCMYK(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("12.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        for x := 0; x < 256; x++ {
                for y := 0; y < 256; y++ {
                        c := color.CMYK{byte(x), 0, 0, 127}
                        img.SetCMYK(x, y, c)
 
                        c = color.CMYK{0, byte(x), 0, 127}
                        img.SetCMYK(x+256, y, c)
 
                        c = color.CMYK{0, 0, byte(x), 127}
                        img.SetCMYK(x, y+256, c)
 
                        c = color.CMYK{0, byte(y), 0, byte(x)}
                        img.SetCMYK(x+256, y+256, c)
                }
        }
        png.Encode(outfile, img)
}

Pokud by se hodnota K nastavila na 100% (255), byl by celý obrázek černý, nezávisle na hodnotě dalších tří barevných složek.

14. Balíček image/draw

Dalším balíčkem, s nímž se dnes alespoň ve stručnosti seznámíme, se jmenuje image/draw. Název tohoto balíčku může být možná poněkud matoucí, protože v něm ve skutečnosti nenajdeme funkce pro kreslení nějakých geometrických tvarů či textů do rastrových obrázků. Balíček image/draw totiž obsahuje implementaci operací prováděných mezi dvěma obrázky – tyto operace se někdy nazývají kompozice, protože se zdrojový (source) obrázek pixel po pixelu aplikuje na obrázek cílový (target).

V tomto balíčku nalezneme především datový typ (strukturu, záznam) Drawer:

type Drawer interface {
    Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point)
}

A potom dvojici až překvapivě univerzálních funkcí implementujících rastrové operace aplikované na dvojici či trojici obrázků.

Rastrová operace mezi zdrojovým a cílovým obrázkem:

func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op)

Rastrová operace mezi zdrojovým a cílovým obrázkem s využitím třetího obrázku ve funkci masky:

func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op)

15. Trik – vyplnění obrázku konstantní barvou s využitím obrazových operací

Funkce Draw a DrawMask nám umožňují provádět množství zajímavých a užitečných triků. Například s nimi dokážeme vyplnit obdélníkovou část obrázku konstantní barvou. Provedeme to tak, že vytvoříme obrázek vyplněný konstantní barvou, který má neomezené rozměry. K vytvoření takto zvláštního obrázku slouží konstruktor NewUniform:

func NewUniform(c color.Color) *Uniform

popř. můžeme přímo inicializovat strukturu Uniform:

image.Uniform{nějaká_barva}

Dále použijeme strukturu image.ZP reprezentující bod ležící na souřadnicích [0, 0]:

image.ZP

a zavoláme funkci Draw, kterou překopírujeme obsah obrázku vyplněného konstantní barvou do obrázku cílového. Přitom se provede ořez do zvoleného obdélníka r:

r := image.Rect(x_from, y_from, x_to, y_to)
draw.Draw(img, r, &image.Uniform{nějaká_barva}, image.ZP, draw.Src)

Výsledkem bude, že se do obrázku předaného v posledním parametru draw.Src nakopíruje obdélník s konstantní barvou omezený souřadnicemi [x_from, y_from] a [x_to, y_to].

16. Vykreslení šachovnice

Výše uvedeného triku použijeme pro vykreslení šachovnice:

Obrázek 11: Šachovnice vykreslená demonstračním příkladem.

Budeme postupovat stejně, jak jsme si to ukázali v předchozí kapitole, pouze využijeme dva obrázky vyplněné konstantní barvou. Každý obrázek použije jednu z barev z této malé palety:

palette := make(map[int]color.RGBA, 2)
 
palette[0] = color.RGBA{150, 205, 50, 255}
palette[1] = color.RGBA{0, 100, 0, 255}

Následně ve vnitřní smyčce budeme vykreslovat jednotlivá políčka a nezapomeneme přitom přepínat barvu (přesněji index barvy) mezi nulou a jedničkou:

r := image.Rect(x_from, y_from, x_to, y_to)
draw.Draw(img, r, &image.Uniform{palette[index_color]}, image.ZP, draw.Src)
 
index_color = 1 - index_color

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

package main
 
import (
        "image"
        "image/color"
        "image/draw"
        "image/png"
        "os"
)
 
const width = 256
const height = 256
 
func main() {
        img := image.NewCMYK(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("17.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        palette := make(map[int]color.RGBA, 2)
 
        palette[0] = color.RGBA{150, 205, 50, 255}
        palette[1] = color.RGBA{0, 100, 0, 255}
 
        index_color := 0
        board_size := 8
        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
        }
        png.Encode(outfile, img)
}

17. Vykreslení složitějších tvarů do rastrových obrázků

Další geometrické tvary nejsou balíčkem image/draw podporovány, i když samozřejmě můžeme použít nějaký externí balíček, typicky draw2d. Ovšem (re)implementace některých základních algoritmů pro 2D tvary do jazyka Go může být minimálně poučná.

Pro vykreslení úseček bylo vyvinuto několik algoritmů, ovšem nejrychlejší (a v minulosti taktéž nejpoužívanější) je algoritmus navržený Jackem Eltonem Bresenhamem už v roce 1962. Tento algoritmus pro vykreslení úsečky, který je velmi podrobně popsán na Wikipedii, využívá pouze celočíselné operace a současně jsou v něm eliminovány aritmetické operace pro násobení a dělení, což je i dnes poměrně důležité (zejména na výpočetně slabších 32bitových ARMech). Všechny výpočty se tak zjednoduší na aritmetický posun, sčítání, odčítání a podmíněné skoky. Právě díky těmto vlastnostem se Bresenhamův algoritmus stále v některých aplikacích používá, i když v případě požadavků na co nejvyšší kvalitu vykreslování se někdy přechází na pomalejší algoritmy s antialiasingem.

18. Implementace Bresenhamova algoritmu pro vykreslování úseček

Pokud se při vykreslování úseček spokojíme s menší rychlostí celého programu, je možné Bresenhamův algoritmus implementovat poměrně přímočarým způsobem, což je ostatně patrné z výpisu kódu, který naleznete pod tímto odstavcem. Pomocné lokální proměnné sx a sy slouží pro posuny souřadnice vykreslovaného pixelu, čímž bylo možné eliminovat rozepsání tohoto algoritmu pro všech osm oktantů (díky existenci sx a sy se jakoby pohybujeme pouze v prvním oktantu, i když ve skutečnosti může vykreslování probíhat v jiném směru). Pomocné proměnné dx a dy společně s proměnnou err se používají k určení směru vykreslování. Povšimněte si, že vykreslování vždy začíná v prvním vrcholu [x1, y1] a končí přesně ve druhém vrcholu [x2, y2], nezávisle na vzájemné pozici těchto vrcholů (může se jednat i o jediný bod). Horizontální a vertikální úsečky jsou vykresleny specializovanými (a pravděpodobně i rychlejšími) funkcemi:

package main
 
import (
        "image"
        "image/color"
        "image/png"
        "os"
)
 
const width = 256
const height = 256
 
func DrawHorizontalLine(img *image.RGBA, color color.Color, x1 int, x2 int, y int) {
        if x1 > x2 {
                x1, x2 = x2, x1
        }
        for x := x1; x < x2; x++ {
                img.Set(x, y, color)
        }
}
 
func DrawVerticalLine(img *image.RGBA, color color.Color, x int, y1 int, y2 int) {
        if y1 > y2 {
                y1, y2 = y2, y1
        }
        for y := y1; y < y2; y++ {
                img.Set(x, y, color)
        }
}
 
func Abs(x int) int {
        if x < 0 {
                return -x
        }
        return x
}
 
func Step(v1 int, v2 int) int {
        if v1 < v2 {
                return 1
        } else {
                return -1
        }
}
 
func DrawLine(img *image.RGBA, color color.Color, x1 int, y1 int, x2 int, y2 int) {
        // specialni pripad - svisla usecka
        if x1 == x2 {
                DrawVerticalLine(img, color, x1, y1, y2)
                return
        }
 
        // specialni pripad - vodorovna usecka
        if y1 == y2 {
                DrawHorizontalLine(img, color, x1, x2, y1)
                return
        }
 
        // takze mame smulu a musime pouzit plnou verzi algoritmu
 
        // zrcadleni algoritmu pro dalsi oktanty
        x := x1
        y := y1
 
        // konstanty pouzite pri vykreslovani
        dx := Abs(x2 - x1)
        dy := Abs(y2 - y1)
        sx := Step(x1, x2)
        sy := Step(y1, y2)
 
        // pocatecni hodnota akumulatoru chyby
        err := dx >> 1
        if dx <= dy {
                err = -dy >> 1
        }
 
        // vse je pripraveno k vlastnimu vykresleni usecky
        for {
                img.Set(x, y, color)
                // test, zda se jiz doslo k poslednimu bodu
                if x == x2 && y == y2 {
                        break
                }
                e2 := err
                if e2 > -dx {
                        // prepocet kumulovane chyby
                        err -= dy
                        // posun na predchozi ci dalsi pixel na radku
                        x += sx
                }
                if e2 < dy {
                        // prepocet kumulovane chyby
                        err += dx
                        // posun na predchozi ci nasledujici radek
                        y += sy
                }
        }
}
 
func main() {
        img := image.NewRGBA(image.Rect(0, 0, width, height))
 
        outfile, err := os.Create("18.png")
        if err != nil {
                panic(err)
        }
        defer outfile.Close()
 
        c := color.RGBA{255, 255, 255, 255}
        for x := 0; x < width; x++ {
                for y := 0; y < height; y++ {
                        img.Set(x, y, c)
                }
        }
 
        c = color.RGBA{0, 0, 255, 255}
        DrawLine(img, c, 20, 10, 245, 10)
 
        c = color.RGBA{255, 0, 0, 255}
        DrawLine(img, c, 10, 20, 10, 245)
 
        c = color.RGBA{0, 255, 0, 255}
        DrawLine(img, c, 20, 20, width>>1, height>>1)
 
        c = color.RGBA{0, 0, 0, 255}
        for x := 10; x < width; x += 10 {
                DrawLine(img, c, width-5, x, width-x, height-5)
        }
        png.Encode(outfile, img)
}

Obrázek 12: Několik úseček vykreslených funkcí DrawLine.

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_empty_image.go vytvoření rastrového obrázku s výchozími hodnotami pixelů (zcela průhledné černé pixely) https://github.com/tisnik/go-fedora/blob/master/article14/01_em­pty_image.go
2 02_empty_image_rectangle.go alternativní způsob vytvoření obdélníku pro určení rozměrů obrázku https://github.com/tisnik/go-fedora/blob/master/article14/02_em­pty_image_rectangle.go
3 03_filled_image_setrgba.go rastrový obrázek vyplněný konstantní barvou, použití metody SetRGBA https://github.com/tisnik/go-fedora/blob/master/article14/03_fi­lled_image_setrgba.go
4 04_filled_image_set.go rastrový obrázek vyplněný konstantní barvou, použití metody Set https://github.com/tisnik/go-fedora/blob/master/article14/04_fi­lled_image_set.go
5 05_filled_image_alpha.go obrázek, v němž mají pixely různou průhlednost (nekorektní varianta) https://github.com/tisnik/go-fedora/blob/master/article14/05_fi­lled_image_alpha.go
6 06_filled_image_alpha_correct.go korektní varianta předchozího příkladu https://github.com/tisnik/go-fedora/blob/master/article14/06_fi­lled_image_alpha_correct.go
7 07_filled_image_alpha_correct.go vylepšená varianta předchozího příkladu https://github.com/tisnik/go-fedora/blob/master/article14/07_fi­lled_image_alpha_correct.go
8 08_image_internals.go interní struktura záznamu s informacemi o obrázku https://github.com/tisnik/go-fedora/blob/master/article14/08_i­mage_internals.go
9 09_raw_pixels.go přímý přístup k jednotlivým pixelům; první varianta https://github.com/tisnik/go-fedora/blob/master/article14/09_raw_pi­xels.go
10 10_raw_pixels2.go přímý přístup k jednotlivým pixelům; druhá varianta https://github.com/tisnik/go-fedora/blob/master/article14/10_raw_pi­xels2.go
11 11_raw_pixels3.go přímý přístup k jednotlivým pixelům; třetí varianta https://github.com/tisnik/go-fedora/blob/master/article14/11_raw_pi­xels3.go
12 12_point_rectangle.go datové struktury Point a Rectangle https://github.com/tisnik/go-fedora/blob/master/article14/12_po­int_rectangle.go
13 13_point_rectangle_operations.go základní operace s obdélníky a body https://github.com/tisnik/go-fedora/blob/master/article14/13_po­int_rectangle_operations.go
14 14_rgba_images.go základní vlastnosti barvového prostoru RGBA https://github.com/tisnik/go-fedora/blob/master/article14/14_rgba_i­mages.go
15 15_cmyk_images.go základní vlastnosti barvového prostoru CMYK https://github.com/tisnik/go-fedora/blob/master/article14/15_cmyk_i­mages.go
16 16_cmyk_images.go základní vlastnosti barvového prostoru CMYK https://github.com/tisnik/go-fedora/blob/master/article14/16_cmyk_i­mages.go
17 17_chessboard.go využití balíčku draw pro vykreslení šachovnice (rastrové operace) https://github.com/tisnik/go-fedora/blob/master/article14/17_ches­sboard.go
18 18_bresenham_algorithm.go implementace Bresenhamova algoritmu pro vykreslení úsečky https://github.com/tisnik/go-fedora/blob/master/article14/18_bre­senham_algorithm.go
19 19_blending.go blending v Go (opět rastrové operace) https://github.com/tisnik/go-fedora/blob/master/article14/19_blen­ding.go

20. Odkazy na Internetu

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