Hlavní navigace

Knihovna Gift pro zpracování rastrových obrázků v Go

Dnes si popíšeme knihovnu Gift, která slouží ke zpracování rastrových obrázků. Ostatně i samotný název této knihovny je zkratkou získanou z „Go Image Filtering Toolkit“. Použití je podobné balíčku Pillow pro Python.
Pavel Tišnovský
Doba čtení: 44 minut

Sdílet

11. Filtry pracující s nejbližším okolím pixelů

12. Výběr lokálního minima, maxima, průměru a mediánu

13. Konvoluční filtry

14. Příklady jednoduchých 2D konvolučních filtrů

15. Sobelův operátor

16. Obecný konvoluční filtr

17. Automatická normalizace pixelu po aplikaci kernelu

18. Obsah následující části seriálu

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

20. Odkazy na Internetu

1. Knihovna Gift pro zpracování rastrových obrázků

V předchozích dvou částech [1] [2] seriálu o programovacím jazyce Go jsme se zabývali problematikou spolupráce mezi překladačem jazyka Go a vestavěným assemblerem. Dnes se tímto tématem (zdánlivě) nebudeme zabývat, protože si namísto toho popíšeme knihovnu nazvanou Gift, která slouží ke zpracování rastrových obrázků. Ostatně i samotný název této knihovny je vlastně zkratkou získanou z „Go Image Filtering Toolkit “. Jak toto téma souvisí s assemblerem si vysvětlíme příště, takže jen v krátkosti – velkou část operací prováděných nad rastrovými obrázky lze velmi dobře (a mnohdy i významně) urychlit použitím SIMD instrukcí, což konkrétně na platformě x86–64 znamená použití instrukcí ze sady SSE, SSE2, SSE3 či AVX. Knihovna Gift tato rozšíření přímo nepoužívá, protože je kompletně psána v programovacím jazyce Go bez použití assembleru a možnosti Go při automatické vektorizaci prozatím nejsou příliš velké. Ovšem příště si tyto metody optimalizace popíšeme a ukážeme.

Samotnou knihovnu Gift lze nainstalovat stejně snadno jako jakýkoli jiný balíček určený pro ekosystém programovacího jazyka Go:

$ go get -u github.com/disintegration/gift
Důležitá poznámka: Gift je knihovna nabízející pouze základní funkce pro zpracování obrazu. Není v žádném případě určena jako náhrada za OpenCV a podobné komplikované nástroje, ovšem naopak může sloužit jako základ pro sofistikovanější aplikace. Navíc Gift nenabízí některé často používané funkce, jako je decimace, snižování počtu barev s aplikací ditheringu atd.

2. Získání testovacího obrázku používaného demonstračními příklady

Před popisem jednotlivých operací, které knihovna Gift podporuje, a před spuštěním demonstračních příkladů je nutné získat testovací obrázek, který bude do příkladů načítán a dále zpracováván. Vzhledem k tomu, že si budeme mj. popisovat i různé konvoluční filtry aplikované na rastrové obrázky, použijeme dnes již legendární testovací obrázek s fotkou Lenny (Leny), který se v oblasti počítačové grafiky a zpracování obrazu používá již několik desetiletí, konkrétně od roku 1973 (více viz stránka Lenna 97: A Complete Story of Lenna).

Obrázek 1: Klasický testovací obrázek Lenny v původním rozlišení 512×512 pixelů.

Testovací obrázek, který má dnes již „klasické“ rozlišení 512×512 pixelů, je možné získat například z Wikipedie, a to následujícím jednoduchým skriptem. Skript je vhodné spustit ve stejném adresáři, kde se nachází i demonstrační příklady získané z repositáře popsaného v devatenácté kapitole:

$ original_image_address="https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png"
 
$ wget -v -O Lenna.png $original_image_address

Po spuštění tohoto skriptu by se měl v pracovním adresáři objevit nový soubor nazvaný Lenna.png. O tom se samozřejmě můžeme velmi snadno přesvědčit pomocí příkazů ls a file:

$ ls -l Lenna.png
 
-rw-rw-r--. 1 ptisnovs ptisnovs 473831 10. kvě 17.16 Lenna.png
$ file Lenna.png
 
Lenna.png: PNG image data, 512 x 512, 8-bit/color RGB, non-interlaced

Pro účely tohoto článku však obrázek zmenšíme na polovinu v obou směrech. Výsledkem této operace tedy bude bitmapa o rozměrech 256×256 pixelů:

$ convert Lenna.png -resize 256x256 Lenna.png

Zkontrolujeme výsledek:

$ ls -l Lenna.png
-rw-r--r-- 1 tester tester 118234 úno  4 21:27 Lenna.png
 
$ file Lenna.png 
Lenna.png: PNG image data, 256 x 256, 8-bit/color RGB, non-interlaced

Obrázek 2: Testovací obrázek Lenny zmenšený na rozlišení 256×256 pixelů, tedy tak, jak je použitý ve všech dnešních demonstračních příkladech.

3. Kostra demonstračních příkladů

Všechny dnešní demonstrační příklady budou postaveny na podobném základu, který je zobrazen a popsán níže. Ve zdrojovém kódu příkladu můžeme vidět dvojici pomocných funkcí určených pro načtení rastrového obrázku ve formátu PNG a pro uložení výsledného obrázku, taktéž do formátu PNG. Jedná se o funkce se jmény loadImage a saveImage.

Poznámka: zajímavé je, že tyto funkce jsem napsal ještě předtím, než jsem se podíval na existující příklady. Pro programovací jazyk Go je typické, že oba kódy jsou nakonec prakticky totožné.

Důležitou součástí všech demonstračních příkladů je však aplikace filtrů, která vypadá takto:

g := gift.New()
 
destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
g.Draw(destinationImage, sourceImage)

Můžeme zde vidět konstrukci „pipeline“ s filtry, zde konkrétně prázdné „pipeline“, v níž se žádné filtry nenachází:

g := gift.New()

Dále se na základě analýzy filtrů vytvoří nový prázdný obrázek a následně se do tohoto obrázku provede vykreslení s využitím aplikací filtru/filtrů na zdrojový obrázek. Velikost cílového obrázku může být odlišná, což platí například po aplikaci filtru pro otočení atd. Právě z tohoto důvodu je důležité velikost zjistit zavoláním:

g.Bounds(sourceImage.Bounds())

Nakonec je filtr/filtry aplikován a obrázek je vykreslen metodou Draw:

g.Draw(destinationImage, sourceImage)

Výsledek bude vypadat takto (bude se jednat o kopii původního obrázku):

Úplná kostra dnešních příkladů vypadá následovně:

package main
 
import (
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileName = "01.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        g := gift.New()
 
        destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
        g.Draw(destinationImage, sourceImage)
 
        err = saveImage(DestinationImageFileName, destinationImage)
        if err != nil {
                log.Fatal(err)
        }
}

4. Typy obrazových filtrů nabízených knihovnou Gift

V knihovně Gift najdeme několik typů filtrů, které se od sebe odlišují zejména tím, jaké manipulace s rastrovými obrázky jsou prováděny:

  1. Filtry, které pouze modifikují barvy jednotlivých pixelů na základě jejich původní barvy (tedy bez ohledu na to, jak vypadá okolí měněného pixelu). Jedná se o nejjednodušší a obecně nejrychlejší typ filtrů, které slouží například pro změnu kontrastu, k barevné korekci, negaci obrázku, převodu obrázku na stupně šedi atd. Z hlediska implementace (o které se budeme bavit příště) je možné zpracování výrazně zparalelizovat.
  2. Druhé filtry jsou konvoluční. Ty již pracují nejenom s barvou měněného pixelu, ale i s jeho nejbližším okolím, jehož velikost je typicky nastavena na 3×3 pixely, 5×5 pixelů, ale a u některých filtrů může být i větší. Tyto filtry slouží k detekci hran, zaostření, rozostření atd.
  3. Třetí typ filtrů obrázek (jako celek) převrací či otáčí. Výsledkem je v obecném případě nový obrázek s odlišným rozlišením, což platí zejména pro otáčení obrázků a samozřejmě taktéž pro filtry typu resize a crop.

5. Konverze obrázku na stupně šedi a použití filtru „sepia“

V prvním skutečném demonstračním příkladu je ukázána konverze původně plnobarevného (truecolor) obrázku na stupně šedi. Pro tento účel se používá filtr pojmenovaný Grayscale, jenž se aplikuje následovně:

g := gift.New(
        gift.Grayscale())

Výsledkem je skutečně černobílý obrázek:

Druhý implementovaný filtr je podobný filtru prvnímu, ovšem používá se zde efekt známý pod jménem sepia, který se snaží navodit dojem starých fotografií. Parametr udává míru „zastarávání“ fotografie:

g = gift.New(
        gift.Sepia(50.0))

Výsledek v tomto případě vypadá následovně:

Ukažme si nyní úplný zdrojový kód tohoto demonstračního příkladu:

package main
 
import (
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileName1 = "02_grayscale.png"
const DestinationImageFileName2 = "02_sepia.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        g := gift.New(
                gift.Grayscale())
 
        destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
        g.Draw(destinationImage, sourceImage)
 
        err = saveImage(DestinationImageFileName1, destinationImage)
        if err != nil {
                log.Fatal(err)
        }
 
        g = gift.New(
                gift.Sepia(50.0))
 
        destinationImage = image.NewRGBA(g.Bounds(sourceImage.Bounds()))
        g.Draw(destinationImage, sourceImage)
 
        err = saveImage(DestinationImageFileName2, destinationImage)
        if err != nil {
                log.Fatal(err)
        }
}

6. Inverze obrázku

Další demonstrační příklad je jednoduchý, protože v něm pouze provedeme inverzi obrázku. Z hlediska interní reprezentace je možné právě tuto operaci plně a bez problémů paralelizovat, a to velmi úspěšně (mnohem lépe, než je tomu u ostatních filtrů):

g := gift.New(
        gift.Invert())

Výsledek aplikace tohoto filtru vypadá následovně:

Následuje výpis úplného zdrojového kódu tohoto demonstračního příkladu, v němž se filtr Invert používá:

package main
 
import (
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileName = "03_invert.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        g := gift.New(
                gift.Invert())
 
        destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
        g.Draw(destinationImage, sourceImage)
 
        err = saveImage(DestinationImageFileName, destinationImage)
        if err != nil {
                log.Fatal(err)
        }
}

7. Kombinace několika filtrů postupně aplikovaných na jediný vstupní obrázek

Jednotlivé filtry poskytované knihovnou Gift, popř. uživatelem definované filtry, je možné zřetězit. Všechny aplikované filtry, popř. i jejich parametry se předávají do konstruktoru gift.New:

g := gift.New(
        gift.Grayscale(),
        gift.Invert())

S výsledkem, který je očekávatelný – obrázek je nejdříve převeden na stupně šedi a posléze invertován:

Poznámka: obecně záleží na posloupnosti aplikace filtrů, ta tedy nemůže být libovolná. Počet aplikovaných filtrů není prakticky omezen, pouze se (podle očekávání) prodlouží doba běhu konvertoru.

Opět se podívejme na to, jak vypadá zdrojový kód takto upraveného příkladu:

package main
 
import (
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileName = "04_grayscale_invert.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        g := gift.New(
                gift.Grayscale(),
                gift.Invert())
 
        destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
        g.Draw(destinationImage, sourceImage)
 
        err = saveImage(DestinationImageFileName, destinationImage)
        if err != nil {
                log.Fatal(err)
        }
}

8. Změna světlosti, kontrastu a saturace barev

Další jednoduchý filtr se jmenuje Brightness a používá se ke změně celkové světlosti všech pixelů v obrázku. Tento filtr akceptuje parametr, kterým může být hodnota typu float32, přičemž záporné hodnoty značí, že se obrázek ztmaví a kladné hodnoty slouží k jeho zesvětlení. Příklad aplikace filtru s parametry –50, –30, –10, 10, 30 a 50:






Tyto obrázky byly získány kódem:

package main
 
import (
        "fmt"
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileNameTemplate = "05_brightness_%d.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        for brightness := -50; brightness <= 50; brightness += 20 {
                g := gift.New(
                        gift.Brightness(float32(brightness)))
 
                destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
                g.Draw(destinationImage, sourceImage)
 
                filename := fmt.Sprintf(DestinationImageFileNameTemplate, brightness)
                err = saveImage(filename, destinationImage)
                if err != nil {
                        log.Fatal(err)
                }
        }
}

Podobně existuje filtr Contrast pro změnu kontrastu. Následuje příklad aplikace filtru s parametry –50, –30, –10, 10, 30 a 50:






A příslušný zdrojový kód:

package main
 
import (
        "fmt"
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileNameTemplate = "06_contrast_%d.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        for contrast := -50; contrast <= 50; contrast += 20 {
                g := gift.New(
                        gift.Contrast(float32(contrast)))
 
                destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
                g.Draw(destinationImage, sourceImage)
 
                filename := fmt.Sprintf(DestinationImageFileNameTemplate, contrast)
                err = saveImage(filename, destinationImage)
                if err != nil {
                        log.Fatal(err)
                }
        }
}

A konečně si ukažme filtr pro změnu barevné saturace obrázku, opět nejdříve na několika výsledcích, tentokrát ovšem pro hodnoty od –80 (nižší saturace) po +80 (vysoká saturace):





Tyto obrázky byly vytvořeny příkladem:

package main

import (
        "fmt"
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileNameTemplate = "07_saturation_%d.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        for saturation := -80; saturation <= 80; saturation += 40 {
                g := gift.New(
                        gift.Saturation(float32(saturation)))
 
                destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
                g.Draw(destinationImage, sourceImage)
 
                filename := fmt.Sprintf(DestinationImageFileNameTemplate, saturation)
                err = saveImage(filename, destinationImage)
                if err != nil {
                        log.Fatal(err)
                }
        }
}

9. Gamma korekce

Dalším často používaným filtrem (zejména při zpracování obrázků získaných na jiném zařízení) je takzvaná gamma korekce. Jedná se o nelineární změnu jasu pixelů, kterou budeme aplikovat na černobílý obrázek, kde je efekt gamma korekce lépe viditelný:





V těchto obrázcích se hodnota gamma měnila podle smyčky:

for gamma := 0.25; gamma <= 4.0; gamma *= 2 {
        g := gift.New(
                gift.Grayscale(),
                gift.Gamma(float32(gamma)))

Úplný zdrojový kód příkladu:

package main
 
import (
        "fmt"
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileNameTemplate = "08_gamma_%4.2f.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        for gamma := 0.25; gamma <= 4.0; gamma *= 2 {
                g := gift.New(
                        gift.Grayscale(),
                        gift.Gamma(float32(gamma)))
 
                destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
                g.Draw(destinationImage, sourceImage)
 
                filename := fmt.Sprintf(DestinationImageFileNameTemplate, gamma)
                err = saveImage(filename, destinationImage)
                if err != nil {
                        log.Fatal(err)
                }
        }
}

10. Posun v barvovém prostoru

Posledním jednoduchým filtrem je filtr, který provádí posun barev pixelů v barvovém prostoru. Nemění se tedy ani kontrast ani celková světlost, ale „jen“ barvový odstín, tak, jak je to patrné na následujících obrázcích:







Tento filtr se nastavuje s využitím funkce gift.Hue, které se předá hodnota typu float32 určující otočení v barvovém prostoru (HSV, HLS):

g := gift.New(
        gift.Hue(float32(hue)))

Celý příklad, který vygeneroval předchozí sedmici obrázků:

package main
 
import (
        "fmt"
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileNameTemplate = "09_hue_%d.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        for hue := -120; hue <= 120; hue += 40 {
                g := gift.New(
                        gift.Hue(float32(hue)))
 
                destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
                g.Draw(destinationImage, sourceImage)
 
                filename := fmt.Sprintf(DestinationImageFileNameTemplate, hue)
                err = saveImage(filename, destinationImage)
                if err != nil {
                        log.Fatal(err)
                }
        }
}

11. Filtry pracující s nejbližším okolím pixelů

Základním filtrem, který pracuje nejenom s jedním pixelem, ale i s jeho okolím, je filtr pro efekt „pixelize“ či „pixelate“. U tohoto filtru se zadává velikost čtverců se shodnou barvou:




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

package main
 
import (
        "fmt"
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileNameTemplate = "10_pixelate_%02d.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        for size := 2; size <= 16; size *= 2 {
                g := gift.New(
                        gift.Pixelate(size))
 
                destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
                g.Draw(destinationImage, sourceImage)
 
                filename := fmt.Sprintf(DestinationImageFileNameTemplate, size)
                err = saveImage(filename, destinationImage)
                if err != nil {
                        log.Fatal(err)
                }
        }
}

12. Výběr lokálního minima, maxima, průměru a mediánu

Další filtry pracují podobně jako filtr z předchozí kapitoly. Filtr vybírá barvu pixelů na základě lokálního minima, maxima, průměru, popř. mediánu zjištěného v okolí právě zpracovávaného pixelu. Okolí může být buď čtvercové nebo kruhové.

Efekt výběru lokálního maxima pro postupně se zvětšující okolí:




Efekt výběru lokálního minima pro postupně se zvětšující okolí:




Efekt výpočtu a výběru průměru pro postupně se zvětšující okolí:




Efekt výpočtu a výběru mediánu pro postupně se zvětšující okolí:




Následující demonstrační příklad vypočte a vykreslí všech šestnáct předchozích obrázků:

package main
 
import (
        "fmt"
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileNameTemplateMin = "11_min_%02d.png"
const DestinationImageFileNameTemplateMax = "11_max_%02d.png"
const DestinationImageFileNameTemplateMean = "11_mean_%02d.png"
const DestinationImageFileNameTemplateMedian = "11_median_%02d.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        for size := 2; size <= 16; size *= 2 {
                g := gift.New(gift.Minimum(size, false))
                destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
                g.Draw(destinationImage, sourceImage)
                filename := fmt.Sprintf(DestinationImageFileNameTemplateMin, size)
                err = saveImage(filename, destinationImage)
                if err != nil {
                        log.Fatal(err)
                }
 
                g = gift.New(gift.Maximum(size, false))
                destinationImage = image.NewRGBA(g.Bounds(sourceImage.Bounds()))
                g.Draw(destinationImage, sourceImage)
                filename = fmt.Sprintf(DestinationImageFileNameTemplateMax, size)
                err = saveImage(filename, destinationImage)
                if err != nil {
                        log.Fatal(err)
                }
 
                g = gift.New(gift.Mean(size, false))
                destinationImage = image.NewRGBA(g.Bounds(sourceImage.Bounds()))
                g.Draw(destinationImage, sourceImage)
                filename = fmt.Sprintf(DestinationImageFileNameTemplateMean, size)
                err = saveImage(filename, destinationImage)
                if err != nil {
                        log.Fatal(err)
                }
 
                g = gift.New(gift.Median(size, false))
                destinationImage = image.NewRGBA(g.Bounds(sourceImage.Bounds()))
                g.Draw(destinationImage, sourceImage)
                filename = fmt.Sprintf(DestinationImageFileNameTemplateMedian, size)
                err = saveImage(filename, destinationImage)
                if err != nil {
                        log.Fatal(err)
                }
 
        }
}

13. Konvoluční filtry

Potenciálně velmi užitečná funkce z balíčku (knihovny) Gift se skrývá pod jménem Convolution. Ta slouží k aplikaci konvolučního filtru na zdrojové obrázky. Pod pojmem filtrace si přitom můžeme představit soubor lokálních transformací rastrového obrazu, kterými se v případě monochromatických obrazů převádí hodnoty jasu původního obrazu na nové hodnoty jasu obrazu výstupního. Barevný obraz si můžeme pro účely filtrace představit jako tři monochromatické obrazy, z nichž každý obsahuje jas jedné barvové složky (ovšem interní reprezentace obrázku je odlišná). Podle vlastností funkčního vztahu pro výpočet jasu výsledného okolí na základě okolí O ve vstupním obrazu můžeme rozdělit metody filtrace na lineární a nelineární.

Lineární operace vyjadřují výslednou hodnotu jasu jako konvoluci okolí O příslušného bodu [i, j] a takzvaného konvolučního jádra (kernel). Mezi postupy předzpracování obrazu patří i algoritmy obnovení, které se obvykle také vyjadřují ve formě konvoluce. Okolím O, které se používá k výpočtu, je ale obecně celý obraz. Jedná se tedy o výpočetně velmi náročnou operaci. Obnovení se používá pro odstranění poruch s předem známými vlastnostmi jako například rozostření objektivu (též zrcadla) nebo rozmazání vlivem pohybu při snímání.

V dalším textu se však budeme zabývat pouze velmi jednoduchými konvolučními filtry, které pracují nad poměrně malým okolím zpracovávaných pixelů. Velikost konvolučního jádra určuje i velikost zpracovávaného okolí.

Nejpoužívanějším konvolučním filtrem je při práci s rastrovými obrazy bezesporu dvojdimenzionální konvoluční filtr. Jeho užitečnost spočívá především ve velkých možnostech změny rastrového obrazu, které přesahují možnosti jednodimenzionálních filtrů. Pomocí dvojdimenzionálních konvolučních filtrů je možné provádět ostření obrazu, rozmazávání, zvýrazňování hran nebo tvorbu reliéfů (vytlačeného vzoru) ze zadaného rastrového obrazu. Navíc je možné filtry řetězit a dosahovat tak různých složitějších efektů (což vše knihovna Gift pochopitelně podporuje).

14. Příklady jednoduchých 2D konvolučních filtrů

Podívejme se nyní na několik často používaných konvolučních filtrů, které většinou mají jádro o velikosti 3×3.

Obyčejné průměrování filtruje obraz tím, že nová hodnota jasu se spočítá jako aritmetický průměr jasu čtvercového nebo (méně často) obdélníkového okolí. Velikost skvrn šumu by měla být menší než velikost okolí a to by mělo být menší než nejmenší významný detail v obrazu, což je sice pěkná teorie, ovšem těžko dosažitelná. Při aplikaci tohoto filtru vždy dojde k rozmazání hran (alespoň v minimální míře podle velikosti jádra).

Konvoluční jádro filtru velikosti 3×3 pro obyčejné průměrování má tvar:

   1 |1 1 1|
h= - |1 1 1|
   9 |1 1 1|

Jednoduchým rozšířením obyčejného průměrování je průměrování s Gaussovským rozložením. Toto rozložení samozřejmě nelze použít bez dalších úprav, protože by velikost konvoluční masky byla nekonečná. Proto se konvoluční maska filtru vytvoří tak, že se zvýší váhy středového bodu masky a/nebo i jeho 4-okolí (tj. bodů, které mají se středovým bodem společnou jednu souřadnici, druhá se o jednotku liší). Jedna z možných podob konvoluční masky má tvar:

    1 |1 2 1|
h= -- |2 4 2|
   16 |1 2 1|

Všimněte si, že součet všech položek konvoluční matice dává po vynásobení vahou před maticí výslednou hodnotu 1. To zjednodušeně znamená, že se nemění celková světlost obrázku.

Mezi filtry používané pro zvýraznění hran patří Sobelův operátor. Pomocí tohoto operátoru jsou aproximovány první parciální derivace 2D funkce představované obrazem, jedná se tedy o operátor směrově závislý. Směr se u těchto operátorů udává podle světových stran. Sobelův operátor ve směru „sever“ má například tvar:

   | 1  2  1|
h= | 0  0  0|
   |-1 -2 -1|

Sobelův operátor v jiném směru lze získat rotací této matice.

15. Sobelův operátor

Sobelův operátor se na rastrový obrázek aplikuje takto:

g := gift.New(
        gift.Sobel())

S výsledkem:

Vidíme, že tento filtr skutečně dokáže zvýraznit hrany:

package main

import (
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)

const SourceImageFileName = "Lenna.png"
const DestinationImageFileName = "12_sobel.png"

func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()

        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}

func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()

        png.Encode(outfile, img)
        return nil
}

func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }

        g := gift.New(
                gift.Sobel())

        destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
        g.Draw(destinationImage, sourceImage)

        err = saveImage(DestinationImageFileName, destinationImage)
        if err != nil {
                log.Fatal(err)
        }
}

16. Obecný konvoluční filtr

Obecný konvoluční filtr se vytváří funkcí gift.Convolution, které se musí předat konvoluční jádro a další parametry – zda se má provádět normalizace, aplikovat filtr na alfa složku, posouvat výslednou barvovou složku o nějakou hodnotu atd. Typickým příkladem je filtr, který vytváří efekt vytlačeného vzorku, jehož jádro lze zadat následujícím způsobem:

filter := gift.Convolution([]float32{
        -1, -1,  0,
        -1,  1,  1,
         0,  1,  1,
}, false, false, false, 0.0)
Poznámka: filtr existuje i v dalších podobách – otočení atd.

Výsledek aplikace takového filtru:

Příklad s aplikací filtru:

package main
 
import (
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileName = "13_emboss.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        filter := gift.Convolution([]float32{
                -1, -1, 0,
                -1, 1, 1,
                0, 1, 1,
        }, false, false, false, 0.0)
 
        g := gift.New(filter)
 
        destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
        g.Draw(destinationImage, sourceImage)
 
        err = saveImage(DestinationImageFileName, destinationImage)
        if err != nil {
                log.Fatal(err)
        }
}

Můžeme samozřejmě aplikovat i filtr, kde součet položek v jádru nebude roven jedné, což je příklad následujícího ostřícího filtru:

filter := gift.Convolution([]float32{
        0, -1, 0,
        -1, 5, 1,
        0, -1, 0,
}, false, false, false, 0.0)

Výsledkem ovšem bude obraz, jehož barvy budou posunuty směrem k bílé (protože součet složek je větší než jedna):

Zdrojový kód příkladu:

package main
 
import (
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileName = "14_sharpen.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        filter := gift.Convolution([]float32{
                0, -1, 0,
                -1, 5, 1,
                0, -1, 0,
        }, false, false, false, 0.0)
 
        g := gift.New(filter)
 
        destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
        g.Draw(destinationImage, sourceImage)
 
        err = saveImage(DestinationImageFileName, destinationImage)
        if err != nil {
                log.Fatal(err)
        }
}

17. Automatická normalizace pixelu po aplikaci kernelu

Jedním z parametrů funkce Convolution je i přepínač určující, jestli se má provést automatická normalizace vah hodnot v jádru filtru. V případě, že je normalizace povolena, bude výsledný obrázek zaostřený a bude mít shodné barvy s obrázkem zdrojovým:

Root linux

package main
 
import (
        "github.com/disintegration/gift"
        "image"
        "image/png"
        "log"
        "os"
)
 
const SourceImageFileName = "Lenna.png"
const DestinationImageFileName = "15_sharpen.png"
 
func loadImage(filename string) (image.Image, error) {
        infile, err := os.Open(filename)
        if err != nil {
                return nil, err
        }
        defer infile.Close()
 
        src, _, err := image.Decode(infile)
        if err != nil {
                return nil, err
        }
        return src, nil
}
 
func saveImage(filename string, img image.Image) error {
        outfile, err := os.Create(filename)
        if err != nil {
                return err
        }
        defer outfile.Close()
 
        png.Encode(outfile, img)
        return nil
}
 
func main() {
        sourceImage, err := loadImage(SourceImageFileName)
        if err != nil {
                log.Fatal(err)
        }
 
        filter := gift.Convolution([]float32{
                0, -1, 0,
                -1, 5, 1,
                0, -1, 0,
        }, true, false, false, 0.0)
 
        g := gift.New(filter)
 
        destinationImage := image.NewRGBA(g.Bounds(sourceImage.Bounds()))
        g.Draw(destinationImage, sourceImage)
 
        err = saveImage(DestinationImageFileName, destinationImage)
        if err != nil {
                log.Fatal(err)
        }
}

18. Obsah následující části seriálu

V další části seriálu o programovacím jazyce Go nejdříve dokončíme popis knihovny Gift a posléze si ukážeme využití vybraných „vektorových“ instrukcí pro urychlení operací s rastrovými obrázky (ovšem i s jinými typy dat).

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

Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (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ě šest až sedm megabajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady, které naleznete v následující tabulce:

# Příklad Stručný popis Cesta
1 01_no_op_filter.go pouhá kopie obrázku bez použití filtrů https://github.com/tisnik/go-root/blob/master/article55/01_no_op_fil­ter.go
2 02_grayscale.go filtr typu grayscale a sepia https://github.com/tisnik/go-root/blob/master/article55/02_gra­yscale.go
3 03_invert.go inverze obrázku https://github.com/tisnik/go-root/blob/master/article55/03_in­vert.go
4 04_grayscale_invert.go převod na stupně šedi a inverze obrázku https://github.com/tisnik/go-root/blob/master/article55/04_gra­yscale_invert.go
5 05_brightness.go změna světlosti obrázku https://github.com/tisnik/go-root/blob/master/article55/05_brig­htness.go
6 06_contrast.go změna kontrastu obrázku https://github.com/tisnik/go-root/blob/master/article55/06_con­trast.go
7 07_saturation.go změna saturace https://github.com/tisnik/go-root/blob/master/article55/07_sa­turation.go
8 08_gamma.go gamma korekce https://github.com/tisnik/go-root/blob/master/article55/08_gam­ma.go
9 09_hue.go posun pixelů v barvovém prostoru https://github.com/tisnik/go-root/blob/master/article55/09_hue.go
10 10_pixelate.go „pixelizace“ celého obrázku https://github.com/tisnik/go-root/blob/master/article55/10_pi­xelate.go
11 11_min_max_mean_median.go obarvení pixelu na základě lokálního minima, maxima, průměru a mediánu https://github.com/tisnik/go-root/blob/master/article55/11_min_max_me­an_median.go
12 12_sobel.go Sobelův operátor https://github.com/tisnik/go-root/blob/master/article55/12_so­bel.go
13 13_emboss.go vytlačený vzorek (vlastní filtr) https://github.com/tisnik/go-root/blob/master/article55/13_em­boss.go
14 14_sharpen.go zaostření obrázku bez normalizace pixelů https://github.com/tisnik/go-root/blob/master/article55/14_shar­pen.go
15 15_sharpen.go zaostření obrázku s normalizací pixelů https://github.com/tisnik/go-root/blob/master/article55/15_shar­pen.go

20. Odkazy na Internetu

  1. Repositář projektu Gift
    https://github.com/disinte­gration/gift
  2. Dokumentace k projektu Gift
    https://godoc.org/github.com/di­sintegration/gift
  3. Online x86 / x64 Assembler and Disassembler
    https://defuse.ca/online-x86-assembler.htm#disassembly2
  4. The Design of the Go Assembler
    https://talks.golang.org/2016/as­m.slide#1
  5. A Quick Guide to Go's Assembler
    https://golang.org/doc/asm
  6. AssemblyPolicy
    https://github.com/golang/go/wi­ki/AssemblyPolicy
  7. Geohash in Golang Assembly
    https://mmcloughlin.com/posts/geohash-assembly
  8. Command objdump
    https://golang.org/cmd/objdump/
  9. Assembly
    https://goroutines.com/asm
  10. Go & Assembly
    http://www.doxsey.net/blog/go-and-assembly
  11. A Foray Into Go Assembly Programming
    https://blog.sgmansfield.com/2017/04/a-foray-into-go-assembly-programming/
  12. Golang Capturing log.Println And fmt.Println Output
    https://medium.com/@hau12a1/golang-capturing-log-println-and-fmt-println-output-770209c791b4
  13. Stránka projektu plotly
    https://plot.ly/
  14. Plotly JavaScript Open Source Graphing Library
    https://plot.ly/javascript/
  15. Domain coloring
    https://en.wikipedia.org/wi­ki/Domain_coloring
  16. Michael Fogleman's projects
    https://www.michaelfogleman­.com/projects/tagged/grap­hics/
  17. Color Graphs of Complex Functions
    https://web.archive.org/web/20120511021419/htt­p://w.american.edu/cas/mat­hstat/lcrone/ComplexPlot.html
  18. A Gallery of Complex Functions
    http://wismuth.com/complex/ga­llery.html
  19. package glot
    https://godoc.org/github.com/A­rafatk/glot
  20. Gnuplotting: Output terminals
    http://www.gnuplotting.org/output-terminals/
  21. Introducing Glot the plotting library for Golang
    https://medium.com/@Arafat­./introducing-glot-the-plotting-library-for-golang-3133399948a1
  22. Introducing Glot the plotting library for Golang
    https://blog.gopheracademy.com/advent-2018/introducing-glot/
  23. Glot is a plotting library for Golang built on top of gnuplot
    https://github.com/Arafatk/glot
  24. Example plots (gonum/plot)
    https://github.com/gonum/plot/wi­ki/Example-plots
  25. A repository for plotting and visualizing data (gonum/plot)
    https://github.com/gonum/plot
  26. golang library to make https://chartjs.org/ plots
    https://github.com/brentp/go-chartjs
  27. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  28. The Gonum Numerical Computing Package
    https://www.gonum.org/pos­t/introtogonum/
  29. Gomacro na GitHubu
    https://github.com/cosmos72/gomacro
  30. gophernotes – Use Go in Jupyter notebooks and nteract
    https://github.com/gopher­data/gophernotes
  31. gonum
    https://github.com/gonum
  32. go-gota/gota – DataFrames and data wrangling in Go (Golang)
    https://porter.io/github.com/go-gota/gota
  33. A repository for plotting and visualizing data
    https://github.com/gonum/plot
  34. Gonum Numerical Packages
    https://www.gonum.org/
  35. Stránky projektu MinIO
    https://min.io/
  36. MinIO Quickstart Guide
    https://docs.min.io/docs/minio-quickstart-guide.html
  37. MinIO Go Client API Reference
    https://docs.min.io/docs/golang-client-api-reference
  38. MinIO Python Client API Reference
    https://docs.min.io/docs/python-client-api-reference.html
  39. Performance at Scale: MinIO Pushes Past 1.4 terabits per second with 256 NVMe Drives
    https://blog.min.io/performance-at-scale-minio-pushes-past-1–3-terabits-per-second-with-256-nvme-drives/
  40. Benchmarking MinIO vs. AWS S3 for Apache Spark
    https://blog.min.io/benchmarking-apache-spark-vs-aws-s3/
  41. MinIO Client Quickstart Guide
    https://docs.min.io/docs/minio-client-quickstart-guide.html
  42. Analýza kvality zdrojových kódů Minia
    https://goreportcard.com/re­port/github.com/minio/minio
  43. This is MinIO
    https://www.youtube.com/wat­ch?v=vF0lQh0XOCs
  44. Running MinIO Standalone
    https://www.youtube.com/wat­ch?v=dIQsPCHvHoM
  45. „Amazon S3 Compatible Storage in Kubernetes“ – Rob Girard, Principal Tech Marketing Engineer, Minio
    https://www.youtube.com/wat­ch?v=wlpn8K0jJ4U
  46. Ginkgo
    http://onsi.github.io/ginkgo/
  47. Gomega
    https://onsi.github.io/gomega/
  48. Ginkgo's Preferred Matcher Library na GitHubu
    https://github.com/onsi/gomega/
  49. Provided Matchers
    http://onsi.github.io/gomega/#provided-matchers
  50. Dokumentace k balíčku goexpect
    https://godoc.org/github.com/go­ogle/goexpect
  51. Balíček goexpect
    https://github.com/google/goexpect
  52. Balíček go-expect
    https://github.com/Netflix/go-expect
  53. Balíček gexpect
    https://github.com/Thomas­Rooney/gexpect
  54. Expect (originál naprogramovaný v TCL)
    https://core.tcl-lang.org/expect/index
  55. Expect (Wikipedia)
    https://en.wikipedia.org/wiki/Expect
  56. Pexpect
    https://pexpect.readthedoc­s.io/en/stable/
  57. Golang SSH Client: Multiple Commands, Crypto & Goexpect Examples
    http://networkbit.ch/golang-ssh-client/
  58. goblin na GitHubu
    https://github.com/franela/goblin
  59. Mocha framework
    https://mochajs.org/
  60. frisby na GitHubu
    https://github.com/verdverm/frisby
  61. package frisby
    https://godoc.org/github.com/ver­dverm/frisby
  62. Frisby alternatives and similar packages (generováno)
    https://go.libhunt.com/frisby-alternatives
  63. Cucumber for golang
    https://github.com/DATA-DOG/godog
  64. How to Use Godog for Behavior-driven Development in Go
    https://semaphoreci.com/com­munity/tutorials/how-to-use-godog-for-behavior-driven-development-in-go
  65. Comparative Analysis Of GoLang Testing Frameworks
    https://www.slideshare.net/Dushy­antBhalgami/comparative-analysis-of-golang-testing-frameworks
  66. A Quick Guide to Testing in Golang
    https://caitiem.com/2016/08/18/a-quick-guide-to-testing-in-golang/
  67. Tom's Obvious, Minimal Language.
    https://github.com/toml-lang/toml
  68. xml.org
    http://www.xml.org/
  69. Soubory .properties
    https://en.wikipedia.org/wi­ki/.properties
  70. Soubory INI
    https://en.wikipedia.org/wi­ki/INI_file
  71. JSON to YAML
    https://www.json2yaml.com/
  72. Data Format Converter
    https://toolkit.site/format.html
  73. Viper na GitHubu
    https://github.com/spf13/viper
  74. GoDotEnv na GitHubu
    https://github.com/joho/godotenv
  75. The fantastic ORM library for Golang
    http://gorm.io/
  76. Dokumentace k balíčku gorilla/mux
    https://godoc.org/github.com/go­rilla/mux
  77. Gorilla web toolkitk
    http://www.gorillatoolkit.org/
  78. Metric types
    https://prometheus.io/doc­s/concepts/metric_types/
  79. Histograms with Prometheus: A Tale of Woe
    http://linuxczar.net/blog/2017/06/15/pro­metheus-histogram-2/
  80. Why are Prometheus histograms cumulative?
    https://www.robustperception.io/why-are-prometheus-histograms-cumulative
  81. Histograms and summaries
    https://prometheus.io/doc­s/practices/histograms/
  82. Instrumenting Golang server in 5 min
    https://medium.com/@gsisi­mogang/instrumenting-golang-server-in-5-min-c1c32489add3
  83. Semantic Import Versioning in Go
    https://www.aaronzhuo.com/semantic-import-versioning-in-go/
  84. Sémantické verzování
    https://semver.org/
  85. Getting started with Go modules
    https://medium.com/@fonse­ka.live/getting-started-with-go-modules-b3dac652066d
  86. Create projects independent of $GOPATH using Go Modules
    https://medium.com/mindorks/create-projects-independent-of-gopath-using-go-modules-802260cdfb51o
  87. Anatomy of Modules in Go
    https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16
  88. Modules
    https://github.com/golang/go/wi­ki/Modules
  89. Go Modules Tutorial
    https://tutorialedge.net/golang/go-modules-tutorial/
  90. Module support
    https://golang.org/cmd/go/#hdr-Module_support
  91. Go Lang: Memory Management and Garbage Collection
    https://vikash1976.wordpres­s.com/2017/03/26/go-lang-memory-management-and-garbage-collection/
  92. Golang Internals, Part 4: Object Files and Function Metadata
    https://blog.altoros.com/golang-part-4-object-files-and-function-metadata.html
  93. What is REPL?
    https://pythonprogramminglan­guage.com/repl/
  94. What is a REPL?
    https://codewith.mu/en/tu­torials/1.0/repl
  95. Programming at the REPL: Introduction
    https://clojure.org/guides/re­pl/introduction
  96. What is REPL? (Quora)
    https://www.quora.com/What-is-REPL
  97. Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
    https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/
  98. Read-eval-print loop (Wikipedia)
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  99. Vim as a Go (Golang) IDE using LSP and vim-go
    https://octetz.com/posts/vim-as-go-ide
  100. gopls
    https://github.com/golang/go/wi­ki/gopls
  101. IDE Integration Guide
    https://github.com/stamble­rre/gocode/blob/master/doc­s/IDE_integration.md
  102. How to instrument Go code with custom expvar metrics
    https://sysdig.com/blog/golang-expvar-custom-metrics/
  103. Golang expvar metricset (Metricbeat Reference)
    https://www.elastic.co/gu­ide/en/beats/metricbeat/7­.x/metricbeat-metricset-golang-expvar.html
  104. Package expvar
    https://golang.org/pkg/expvar/#NewInt
  105. Java Platform Debugger Architecture: Overview
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jpda/jpda­.html
  106. The JVM Tool Interface (JVM TI): How VM Agents Work
    https://www.oracle.com/technet­work/articles/javase/index-140680.html
  107. JVM Tool Interface Version 11.0
    https://docs.oracle.com/en/ja­va/javase/11/docs/specs/jvmti­.html
  108. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  109. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  110. IBM JVMTI extensions
    http://publib.boulder.ibm­.com/infocenter/realtime/v2r0/in­dex.jsp?topic=%2Fcom.ibm.sof­trt.doc%2Fdiag%2Ftools%2Fjvmti_ex­tensions.html
  111. Go & cgo: integrating existing C code with Go
    http://akrennmair.github.io/golang-cgo-slides/#1
  112. Using cgo to call C code from within Go code
    https://wenzr.wordpress.com/2018/06/07/u­sing-cgo-to-call-c-code-from-within-go-code/
  113. Package trace
    https://golang.org/pkg/runtime/trace/
  114. Introducing HTTP Tracing
    https://blog.golang.org/http-tracing
  115. Command trace
    https://golang.org/cmd/trace/
  116. A StreamLike, Immutable, Lazy Loading and smart Golang Library to deal with slices
    https://github.com/wesovilabs/koazee
  117. Funkce vyššího řádu v knihovně Underscore
    https://www.root.cz/clanky/funkce-vyssiho-radu-v-knihovne-underscore/
  118. Delve: a debugger for the Go programming language.
    https://github.com/go-delve/delve
  119. Příkazy debuggeru Delve
    https://github.com/go-delve/delve/tree/master/Do­cumentation/cli
  120. Debuggery a jejich nadstavby v Linuxu
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/
  121. Debuggery a jejich nadstavby v Linuxu (2. část)
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/
  122. Debuggery a jejich nadstavby v Linuxu (3): Nemiver
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/
  123. Debuggery a jejich nadstavby v Linuxu (4): KDbg
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/
  124. Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
    http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/
  125. Debugging Go Code with GDB
    https://golang.org/doc/gdb
  126. Debugging Go (golang) programs with gdb
    https://thornydev.blogspot­.com/2014/01/debugging-go-golang-programs-with-gdb.html
  127. GDB – Dokumentace
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/
  128. GDB – Supported Languages
    http://sourceware.org/gdb/cu­rrent/onlinedocs/gdb/Suppor­ted-Languages.html#Supported-Languages
  129. GNU Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Debugger
  130. The LLDB Debugger
    http://lldb.llvm.org/
  131. Debugger (Wikipedia)
    https://en.wikipedia.org/wi­ki/Debugger
  132. 13 Linux Debuggers for C++ Reviewed
    http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817
  133. Go is on a Trajectory to Become the Next Enterprise Programming Language
    https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e
  134. Go Proverbs: Simple, Poetic, Pithy
    https://go-proverbs.github.io/
  135. Handling Sparse Files on Linux
    https://www.systutorials.com/136652/han­dling-sparse-files-on-linux/
  136. Gzip (Wikipedia)
    https://en.wikipedia.org/wiki/Gzip
  137. Deflate
    https://en.wikipedia.org/wiki/DEFLATE
  138. 10 tools written in Go that every developer needs to know
    https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/
  139. Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
    https://www.root.cz/clanky/he­xadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/
  140. Hex dump
    https://en.wikipedia.org/wi­ki/Hex_dump
  141. Rozhraní io.ByteReader
    https://golang.org/pkg/io/#ByteReader
  142. Rozhraní io.RuneReader
    https://golang.org/pkg/io/#RuneReader
  143. Rozhraní io.ByteScanner
    https://golang.org/pkg/io/#By­teScanner
  144. Rozhraní io.RuneScanner
    https://golang.org/pkg/io/#Ru­neScanner
  145. Rozhraní io.Closer
    https://golang.org/pkg/io/#Closer
  146. Rozhraní io.Reader
    https://golang.org/pkg/io/#Reader
  147. Rozhraní io.Writer
    https://golang.org/pkg/io/#Writer
  148. Typ Strings.Reader
    https://golang.org/pkg/strin­gs/#Reader
  149. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  150. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  151. go-cron
    https://github.com/rk/go-cron
  152. gocron
    https://github.com/jasonlvhit/gocron
  153. clockwork
    https://github.com/whiteShtef/cloc­kwork
  154. clockwerk
    https://github.com/onatm/clockwerk
  155. JobRunner
    https://github.com/bamzi/jobrunner
  156. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  157. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  158. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  159. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  160. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  161. go-prompt
    https://github.com/c-bata/go-prompt
  162. readline
    https://github.com/chzyer/readline
  163. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  164. go-readline
    https://github.com/fiorix/go-readline
  165. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  166. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  167. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  168. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  169. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  170. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  171. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  172. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  173. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  174. Editline Library (libedit)
    http://thrysoee.dk/editline/
  175. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  176. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  177. WinEditLine
    http://mingweditline.sourceforge.net/
  178. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  179. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  180. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  181. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  182. history(3) – Linux man page
    https://linux.die.net/man/3/history
  183. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  184. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  185. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  186. Balíček ogletest
    https://github.com/jacobsa/ogletest
  187. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  188. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  189. package testing
    https://golang.org/pkg/testing/
  190. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  191. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  192. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  193. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  194. GoConvey
    http://goconvey.co/
  195. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  196. 5 simple tips and tricks for writing unit tests in #golang
    https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742
  197. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  198. package gg
    https://godoc.org/github.com/fo­gleman/gg
  199. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  200. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  201. The Go image package
    https://blog.golang.org/go-image-package
  202. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  203. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  204. YAML
    https://yaml.org/
  205. edn
    https://github.com/edn-format/edn
  206. Smile
    https://github.com/FasterXML/smile-format-specification
  207. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  208. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  209. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  210. Introducing JSON
    http://json.org/
  211. Package json
    https://golang.org/pkg/encoding/json/
  212. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  213. Go by Example: JSON
    https://gobyexample.com/json
  214. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  215. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  216. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  217. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  218. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  219. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  220. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  221. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  222. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  223. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  224. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  225. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  226. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  227. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  228. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  229. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  230. Algorithms to Go
    https://yourbasic.org/
  231. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  232. 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/
  233. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  234. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  235. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  236. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  237. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  238. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  239. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  240. The Go Programming Language (home page)
    https://golang.org/
  241. GoDoc
    https://godoc.org/
  242. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  243. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  244. The Go Programming Language Specification
    https://golang.org/ref/spec
  245. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  246. Package builtin
    https://golang.org/pkg/builtin/
  247. Package fmt
    https://golang.org/pkg/fmt/
  248. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  249. 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
  250. Learning Go
    https://www.miek.nl/go/
  251. Go Bootcamp
    http://www.golangbootcamp.com/
  252. 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
  253. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  254. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  255. The Go Blog
    https://blog.golang.org/
  256. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  257. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  258. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  259. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  260. 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
  261. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  262. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  263. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  264. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  265. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  266. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  267. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  268. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  269. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  270. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  271. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  272. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  273. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  274. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  275. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  276. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  277. 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/
  278. 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
  279. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  280. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  281. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  282. Go vs. Python
    https://www.peterbe.com/plog/govspy
  283. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  284. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  285. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  286. Go by Example: Slices
    https://gobyexample.com/slices
  287. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  288. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  289. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  290. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  291. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  292. nils In Go
    https://go101.org/article/nil.html
  293. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  294. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  295. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  296. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  297. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  298. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  299. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  300. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  301. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  302. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  303. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  304. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  305. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  306. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  307. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  308. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  309. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  310. 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
  311. 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
  312. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  313. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  314. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  315. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  316. Selectors
    https://golang.org/ref/spec#Selectors
  317. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  318. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  319. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  320. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  321. Part 21: Goroutines
    https://golangbot.com/goroutines/
  322. Part 22: Channels
    https://golangbot.com/channels/
  323. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  324. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  325. 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/
  326. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  327. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  328. Control Structures
    https://www.golang-book.com/books/intro/5
  329. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  330. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  331. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  332. Goroutine IDs
    https://blog.sgmansfield.com/2015/12/go­routine-ids/
  333. Different ways to pass channels as arguments in function in go (golang)
    https://stackoverflow.com/qu­estions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang
  334. justforfunc #22: using the Go execution tracer
    https://www.youtube.com/wat­ch?v=ySy3sR1LFCQ
  335. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  336. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  337. 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/
  338. Effective Go
    https://golang.org/doc/ef­fective_go.html
  339. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  340. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation
  341. The zero value of a slice is not nil
    https://stackoverflow.com/qu­estions/30806931/the-zero-value-of-a-slice-is-not-nil
  342. Go-tcha: When nil != nil
    https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic
  343. Nils in Go
    https://www.doxsey.net/blog/nils-in-go