Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk

Pavel Tišnovský 12. 5. 2015

V dnešní části seriálu o programovacím jazyku Clojure i o knihovnách, které mohou programátoři pracující v Clojure využít, si popíšeme zajímavě koncipovanou knihovnu nazvanou Clisk. Tato knihovna slouží pro programovou syntézu textur a její největší předností je podpora interaktivního vytváření a modifikace textur.

Obsah

1. Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk

2. Koncepty, na nichž je postavena knihovna Clisk

3. Funkce show a checker

4. Úplný zdrojový kód demonstračního příkladu clisktest1

5. Převod vzorku na rastrový obraz a uložení obrazu do souboru

6. Jednoduché uživatelsky definované vzorky

7. Složitější uživatelsky definované vzorky

8. Úplný zdrojový kód demonstračního příkladu clisktest2

9. Generátory, které jsou součástí knihovny Clisk

10. Funkce compose a její použití při tvorbě textur

11. Vnořené vzorky, funkce offset, rotatescale

12. Úplný zdrojový kód demonstračního příkladu clisktest3

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

14. Odkazy na předchozí části seriálu

15. Odkazy na Internetu

1. Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk

V dnešní části seriálu, v němž se zabýváme různými obvyklými i poněkud neobvyklými aspekty programovacího jazyka Clojure a taktéž knihovnami, které mohou programátoři pracující v jazyku Clojure použít, si popíšeme některé vybrané vlastnosti knihovny nazvané poněkud záhadně Clisk. Jedná se o knihovnu určenou pro tvorbu (syntézu) různých typů procedurálních textur, a to nejenom klasických 2D textur reprezentovaných rastrovými obrázky, ale i trojrozměrných a dokonce i čtyřrozměrných textur, které lze použít například v několika raytracerech (čtyřrozměrné textury lze animovat a tvořit například oblaka). V knihovně Clisk jsou deklarovány různé generátory šumu, předdefinované vzorky (šachovnice, mřížka) atd. ovšem mnohem důležitější a současně i zajímavější je podpora pro tvorbu vlastních generátorů, možnost kompozice několika generátorů, programové řízení generování textur atd. Knihovna Clisk taktéž obsahuje podporu pro rendering fraktálů různých typů, například i slavné Mandelbrotovy množiny (lze však deklarovat i jakýkoli jiný fraktál vykreslovaný v komplexní rovině).

Zajímavý je taktéž fakt, že knihovna Clisk poměrně důsledně dodržuje některá pravidla funkcionálního programování (zejména předávání funkcí v parametrech jiným funkcím, kompozice funkcí atd.), takže se může jednat o velmi užitečnou a pro studenty i poměrně přitažlivou učební pomůcku při vysvětlování tohoto na mnoha školách stále opomíjeného paradigmatu. K tomu dopomáhá i možnost zobrazit si vygenerovanou texturu ihned po její deklaraci s využitím funkce show (viz též kapitolu číslo 3), což mj. znamená, že pro experimentování s možnostmi nabízenými knihovnou Click je více než vhodné používat interaktivní smyčku REPL, kterou je programovací jazyk Clojure samozřejmě vybaven. V praxi je však mnohem efektivnější použití vylepšené smyčky REPL nabízené nástrojem Leiningen, ideálně propojené s textovým editorem a/nebo vhodně nakonfigurovaným integrovaným vývojovým prostředím (příkladem může být relativně úspěšné Counterclockwise).

2. Koncepty, na nichž je postavena knihovna Clisk

Jak jsme si již řekli v první kapitole, dokáže knihovna Clisk generovat dvourozměrné, trojrozměrné i čtyřrozměrné procedurální textury, ovšem v dnešním článku se pro jednoduchost budeme zabývat „pouze“ texturami dvourozměrnými, tj. běžnými rastrovými obrázky. Toto zjednodušení nám mj. umožní snazší zobrazení vygenerované textury bez nutnosti použití raytraceru. Obrázky reprezentující výslednou texturu jsou tvořeny běžným dvourozměrným polem pixelů, přičemž každý pixel je specifikován trojicí barvových složek [red, green, blue], což je z pohledu programátora vektor. V knihovně Clisk se dvourozměrné textury tvoří vlastně velmi jednoduše – deklarováním funkce, která pro předané souřadnice [x, y] vrátí vektor s trojicí prvků [red, green, blue]. Nezávisle na rozlišení výsledného rastrového obrázku nabývá souřadnice x hodnot v rozsahu <0, 1>. Totéž pravidlo platí i pro souřadnici y. Dokonce i hodnoty barvových složek red, green a blue leží v rozsahu <0, 1>.

Pro zjednodušení aplikací je možné, aby uživatelská funkce použitá pro tvorbu textury nevracela vektor s trojicí hodnot [red, green, blue], ale pouze jedinou skalární hodnotu. Výsledkem pak bude obrázek ve stupních šedi, popř. lze takovou funkci vhodným způsobem zkombinovat s funkcí jinou. V knihovně Clisk je již mnoho funkcí vhodných pro tvorbu procedurálních textur deklarováno; některé z nich si popíšeme v navazujících kapitolách. Další zjednodušení představuje několik nových funkcí a taktéž operátorů, které mohou pracovat s operandy typu vektor. Na rozdíl od běžných skalárních operátorů typu +, -, * a / mají nové vektorové operátory názvy v+, v-, v*, vdivide, vpow, vmod a vsqrt. (připomeňme si jen, že v programovacím jazyce Clojure se všechny operátory zapisují stejným způsobem jako volání funkcí, protože Clojure de facto pojem „operátor“ v běžném významu známém z matematiky ani nepoužívá). Použití funkcí a operátorů pracujících s vektory si ukážeme v dnešním druhém demonstračním příkladu.

3. Funkce show a checker

V této kapitole se seznámíme s dvojicí funkcí deklarovaných v knihovně Clisk. První z těchto funkcí se jmenuje show. Název již malinko napovídá, co funkce show provádí – jako parametr se této funkci předá deklarace textury (taktéž ve formě funkce), textura se vypočítá a následně zobrazí v samostatném okně. Druhá důležitá funkce se jmenuje checker. Jedná se o takzvaný pattern, tj. o funkci používanou pro výpočet barvy jednotlivých pixelů textury na základě přečtených hodnot x a y. Pro zajímavost se podívejme, jak je funkce checker deklarována. Jejími parametry je dvojice barev, kterými budou vybarvena políčka šachovnice, uvnitř se pak pracuje s hodnotami x a y:

(defn checker
  "Checker pattern in (x,y) space, with 2*2 grid in [0..1,0..1] range"
  ([a b]
    (vif '(clojure.core/*
            (clojure.core/- (clisk.functions/frac x) 0.5)
            (clojure.core/- (clisk.functions/frac y) 0.5))
         a
         b)))

Použití funkcí show a checker je velmi jednoduché, samozřejmě ve chvíli, kdy je projekt správně nastaven a je proveden import všech funkcí nabízených knihovnou Clisk (viz následující kapitolu):

(show (checker black white))

Obrázek 1: Výsledek vyhodnocení (show (checker black white)). Okno s grafickým uživatelským rozhraním bylo vytvořeno knihovnou Clisk.

Základní rozlišení dvourozměrné textury je 256×256 pixelů, ovšem s využitím pojmenovaného parametru :size lze tuto velikost (rozlišení) jednoduše změnit:

(show (checker yellow blue) :size 512)

Obrázek 2: Výsledek vyhodnocení (show (checker yellow blue) :size 512). Okno s grafickým uživatelským rozhraním bylo vytvořeno knihovnou Clisk

Poznámka: deklarace základních barev je samozřejmě taktéž součástí knihovny Clisk.

4. Úplný zdrojový kód demonstračního příkladu clisktest1

V této kapitole bude uveden výpis úplného zdrojového kódu dnešního prvního demonstračního příkladu nazvaného clisktest1, z něhož jsme používali úryvky v předchozích kapitolách:

Obsah souboru project.clj:

(defproject clisktest1 "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [net.mikera/clisk "0.10.0"]]
  :main ^:skip-aot clisktest1.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}})

Obsah souboru core.clj:

(ns clisktest1.core
    (:gen-class)
    (:use clisk.live))
 
(defn -main
    "Začínáme..."
    [& args]
    ; funkce "show" zobrazí vzorek
    (show (checker black white))
    (show (checker yellow blue) :size 512)
    ; nutno odkomentovat v případě, že se má aplikace automaticky ukončit
    ;(System/exit 0)
)

5. Převod vzorku na rastrový obraz a uložení obrazu do souboru

Funkce show, kterou jsme použili v dnešním prvním demonstračním příkladu, je sice vhodná pro interaktivní tvorbu textur s využitím interaktivní smyčky REPL, ovšem pro „opravdovou“ práci musíme mít možnost vygenerovanou texturu uložit do souboru ve vhodném rastrovém formátu. Ve skutečnosti je to poměrně jednoduché, protože v knihovně Clisk existuje funkce nazvaná image, která na svém vstupu očekává vzorek (například již zmíněnou funkci checker) a na výstup vrátí obrázek reprezentovaný objektem typu BufferedImage (používá se zde tedy Java interop). Můžeme si tedy vytvořit pomocnou funkci pro převod vzorku na rastrový obrázek a zavolání jiné funkce pro uložení rastrového obrázku do souboru:

(defn write-pattern
    "Vytvoření rastrového obrázku na základě předaného patternu."
    [pattern file-name]
    (write-image (image pattern) file-name))

Pro uložení rastrového obrázku do souboru opět využijeme Java interop, zde konkrétně statickou metodu nazvanou write, která je deklarována ve třídě javax.imageio.ImageIO. Metoda write akceptuje tři parametry – rastrový obrázek typu BufferedImage či WritableRenderedImage, řetězec s názvem výstupního formátu a objekt typu java.io.File reprezentující cestu k výslednému souboru. Naše druhá pomocná funkce je tedy opět velmi jednoduchá, o čemž se lze snadno přesvědčit z jejího výpisu:

(defn write-image
    "Uložení rastrového obrázku typu BufferedImage do souboru."
    [image file-name]
    (ImageIO/write image "png" (File. file-name)))

Lze se však obejít i bez těchto funkcí, což si můžeme snadno ukázat na příkladech:

(def test-img (image (checker black white)))
(javax.imageio.ImageIO/write test-img "png" (java.io.File. "test.png"))

Nebo ještě snadněji s využitím takzvaného threading makra:

(-> (checker black white) image (javax.imageio.ImageIO/write "png" (java.io.File. "test1.png")))

6. Jednoduché uživatelsky definované vzorky

Uživatelskou funkci nazvanou write-pattern, s níž jsme se seznámili v předchozí kapitole, můžeme použít pro vytvoření první série jednoduchých textur. Podívejme se na jednotlivé volání této funkce s krátkými komentáři:

(defn basic-patterns-test
    "Jednoduché uživatelsky definované vzorky."
    []
    ; nezávisle na hodnotách x a y se vrací konstanta
    ; black je symbol reprezentující vektor představující černou barvu
    (write-pattern black        "basic0.png")
 
    ; nezávisle na hodnotách x a y se vrací konstanta
    ; 1/2 je skalární hodnota: šedá barva
    (write-pattern 1/2          "basic1.png")
 
    ; nezávisle na hodnotách x a y se vrací konstanta
    ; 2/3 červené barvy + 2/3 modré barvy
    (write-pattern [2/3 0 2/3]  "basic2.png")
 
    ; červená složka odpovídá hodnotě souřadnice x
    ; modrá složka odpovídá hodnotě souřadnice y
    (write-pattern [x 1/2 y]    "basic3.png")
 
    ; vytvoření gradientního přechodu
    (write-pattern [y y x]      "basic4.png")
 
    ; symbol pos lze použít namísto [x y z]
    ; zde nás složka z nezajímá (je nulová), tudíž je modrá složka taktéž nulová
    (write-pattern pos          "basic5.png"))

Obrázek 3: Textura tvořená vzorkem black. Celý obrázek je vyplněn černou barvou.

Obrázek 4: Textura tvořená vzorkem 1/2. Celý obrázek je vyplněn šedou barvou.

Obrázek 5: Textura tvořená vzorkem [2/3 0 2/3]. Celý obrázek je vyplněn fialovou barvou.

Obrázek 6: Textura tvořená vzorkem [x 1/2 y]. Výsledkem je gradientní přechod.

Obrázek 7: Textura tvořená vzorkem [y y x]. Výsledkem je gradientní přechod

Obrázek 8: Textura tvořená vzorkem pos. Výsledkem je opět gradientní přechod

7. Složitější uživatelsky definované vzorky

Podívejme se nyní na poněkud složitější vzorky definované uživatelem. V těchto vzorcích se využijí některé pomocné funkce z knihovny Clisk, například již zmíněný operátor v+ (vektorový součet), v* (násobení prvků se stejným indexem) či vsin (aplikace funkce sinus na jednotlivé prvky vektoru). Nejprve si musíme deklarovat pomocné symboly – nikoli funkce!:

(def sin-wave1 (v* 0.7 (vsin (v* 11 x))))
(def sin-wave2 (v* 0.5 (vsin (v* 17 y))))
(def sin-wave3 (v* 0.3 (vsin (v* 19 (v+ x y)))))

Dále vytvoříme pomocnou funkci nazvanou x2, která provádí tento výpočet: (x-1/2)2:

(defn x2
    "Pomocná funkce při výpočtu vzorků."
    [x]
    (v* (v- x 1/2) (v- x 1/2)))

Tyto symboly a funkce jsou použity v dalším programovém kódu pro vytvoření zajímavějších textur:

(defn trickier-patterns-test
    "Složitější uživatelsky definované vzorky."
    []
    ; diagonální gradientní přechod od černé k bílé (počítá se se skaláry)
    ; (zde ve skutečnosti není zapotřebí používat vektorové operace :-)
    (write-pattern (v* (v+ x y) 1/2) "tricky1.png")
 
    ; složitější gradientní přechod, zde již v prostoru RGB
    (write-pattern [(v* (v+ x y) 1/2) 0 (vabs (v- x y))] "tricky2.png")
 
    ; využití výše deklarovaného symbolu sin-wave1
    ; který ovlivňuje zelenou barvovou složku
    (write-pattern [x sin-wave1 2/3]                     "tricky3.png")
 
    ; využití výše deklarovaných symbolů sin-wave1, sin-wave2 a sin-wave3
    ; které ovlivňují všechny tři barvové složky
    (write-pattern [sin-wave1 sin-wave2 sin-wave3]       "tricky4.png")
 
    ; kruhový vzorek - vlny
    (write-pattern (vsin (v* 100 (v+ (x2 x) (x2 y))))    "tricky5.png")
 
    ; funkce rgb-from-hsl provádí převod z barvového prostoru HSL do RGB
    (write-pattern (rgb-from-hsl (v+ [100 100 100] [x y z]))         "tricky6.png")
 
    ; funkce rgb-from-hsl provádí převod z barvového prostoru HSL do RGB
    (write-pattern (rgb-from-hsl (v* 3/2 [sin-wave1 sin-wave2 sin-wave3])) "tricky7.png"))

Obrázek 9: Textura tvořená vzorkem (v* (v+ x y) 1/2). Výsledkem je gradientní přechod od černé barvy k barvě bílé.

Obrázek 10: Textura tvořená vzorkem [(v* (v+ x y) 1/2) 0 (vabs (v- x y))].

Obrázek 11: Textura tvořená vzorkem [x sin-wave1 2/3].

Obrázek 12: Textura tvořená vzorkem [sin-wave1 sin-wave2 sin-wave3]. Povšimněte si i modrých diagonálních pruhů.

Obrázek 13: Textura tvořená vzorkem (vsin (v* 100 (v+ (x2 x) (x2 y)))). Pixely se stejnou vzdáleností od středu obrázku mají vždy stejnou barvu.

Obrázek 14: Textura tvořená vzorkem (rgb-from-hsl (v+ [100 100 100] [x y z])).

Obrázek 15: Textura tvořená vzorkem (rgb-from-hsl (v* 3/2 [sin-wave1 sin-wave2 sin-wave3])).

8. Úplný zdrojový kód demonstračního příkladu clisktest2

Všechny funkce popsané v předchozích třech kapitolách byly zakomponovány do dnešního druhého demonstračního příkladu nazvaného clisktest2. Následuje výpis obou nejdůležitějších souborů tvořících tento příklad – jak projektového souboru project.clj, tak i vlastního „jádra“ aplikace tvořeného souborem core.clj.

Obsah souboru project.clj:

(defproject clisktest2 "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [net.mikera/clisk "0.10.0"]]
  :main ^:skip-aot clisktest2.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}})

Obsah souboru core.clj:

(ns clisktest2.core
    (:gen-class)
    (:use clisk.live))
 
(import java.io.File)
(import javax.imageio.ImageIO)
 
(defn write-image
    "Uložení rastrového obrázku typu BufferedImage do souboru."
    [image file-name]
    (ImageIO/write image "png" (File. file-name)))
 
(defn write-pattern
    "Vytvoření rastrového obrázku na základě předaného patternu."
    [pattern file-name]
    (write-image (image pattern) file-name))
 
(defn basic-patterns-test
    "Jednoduché uživatelsky definované vzorky."
    []
    (write-pattern black        "basic0.png")
    (write-pattern 1/2          "basic1.png")
    (write-pattern [2/3 0 2/3]  "basic2.png")
    (write-pattern [x 1/2 y]    "basic3.png")
    (write-pattern [y y x]      "basic4.png")
    (write-pattern pos          "basic5.png"))
 
(def sin-wave1 (v* 0.7 (vsin (v* 11 x))))
(def sin-wave2 (v* 0.5 (vsin (v* 17 y))))
(def sin-wave3 (v* 0.3 (vsin (v* 19 (v+ x y)))))
 
(defn x2
    "Pomocná funkce při výpočtu vzorků."
    [x]
    (v* (v- x 1/2) (v- x 1/2)))
 
(defn trickier-patterns-test
    "Složitější uživatelsky definované vzorky."
    []
    (write-pattern (v* (v+ x y) 1/2) "tricky1.png")
    (write-pattern [(v* (v+ x y) 1/2) 0 (vabs (v- x y))] "tricky2.png")
    (write-pattern [x sin-wave1 2/3]                     "tricky3.png")
    (write-pattern [sin-wave1 sin-wave2 sin-wave3]       "tricky4.png")
    (write-pattern (vsin (v* 100 (v+ (x2 x) (x2 y))))    "tricky5.png")
    (write-pattern (rgb-from-hsl (v+ [100 100 100] [x y z]))         "tricky6.png")
    (write-pattern (rgb-from-hsl (v* 3/2 [sin-wave1 sin-wave2 sin-wave3])) "tricky7.png"))
 
(defn -main
    [& args]
    (try
        (println "Basic patterns test...")
        (basic-patterns-test)
        (println "Trickier patterns test...")
        (trickier-patterns-test)
        (println "Done")
        (catch Throwable e
            (println (.toString e)))
        (finally ; jistota, že program vždy korektně skončí
            (System/exit 0))))

9. Generátory, které jsou součástí knihovny Clisk

V knihovně Clisk je již definováno poměrně velké množství funkcí, které samy o sobě mohou sloužit pro tvorbu textury, popř. je možné tyto funkce zkombinovat s využitím funkce compose popsané v navazující kapitole. Mezi funkcemi určenými pro přímou i nepřímou tvorbu textur patří již dříve popsaná funkce checker, ale například i funkce turbulence, perlin-noise (generuje klasický Perlinův šum), perlin-snoise, plasma atd. Některé z těchto funkcí si otestujeme v následujícím úryvku kódu:

(defn predefined-patterns-test-
    "Otestování funkcí pro vytváření vzorků/textur 2D obrázků."
    []
    (write-pattern (checker black white)             "pattern_0.png")
    (write-pattern (scale 1/4 (checker black white)) "pattern_1.png")
    (write-pattern turbulence                        "pattern_2.png")
    (write-pattern (scale 1/10 perlin-noise)         "pattern_3.png")
    (write-pattern (scale 1/40 perlin-noise)         "pattern_4.png")
    (write-pattern (scale 1/10 perlin-snoise)        "pattern_5.png")
    (write-pattern (scale 1/10 simplex-noise)        "pattern_6.png")
    (write-pattern (scale 1/10 simplex-snoise)       "pattern_7.png")
    (write-pattern (scale 1/10 plasma)               "pattern_8.png")
    (write-pattern (scale 1/10 splasma)              "pattern_9.png"))

Osobně však preferuji přepis predefined-patterns-test- s využitím smyčky představované makrem doseq a pomocnou (zde velmi užitečnou) funkcí map-indexed, pomocí níž lze imlementovat průchod vektorem a současně i použití čítače (tato funkce vrátí pro každý prvek vektoru patterns dvouprvkový vektor obsahující index a původní prvek):

(defn predefined-patterns-test
    "Otestování funkcí pro vytváření vzorků/textur 2D obrázků."
    []
    (let [patterns [(checker black white)
                    (scale 1/4 (checker black white))
                    turbulence
                    spots
                    blotches
                    (scale 1/10 perlin-noise)
                    (scale 1/40 perlin-noise)
                    (scale 1/10 perlin-snoise)
                    (scale 1/10 simplex-noise)
                    (scale 1/10 simplex-snoise)
                    (scale 1/10 plasma)
                    (scale 1/10 splasma)]]
        ; postupně projít všemi prvky vektoru "patterns", vytvořit
        ; dvouprvkový vektor [index+patter] a zavolat pro tento
        ; vektor funkci write-pattern
        (doseq [ [i pattern] (map-indexed vector patterns)]
            (write-pattern pattern (str "pattern_" i ".png")))))

Obrázek 16: Textura tvořená vzorkem (checker black white).

Obrázek 17: Textura tvořená vzorkem (scale 1/4 (checker black white)).

Obrázek 18: Textura tvořená vzorkem turbulence.

Obrázek 19: Textura tvořená vzorkem spots.

Obrázek 20: Textura tvořená vzorkem blotches.

Obrázek 21: Textura tvořená vzorkem (scale 1/10 perlin-noise).

Obrázek 22: Textura tvořená vzorkem (scale 1/40 perlin-noise).

Obrázek 23: Textura tvořená vzorkem (scale 1/10 perlin-snoise).

Obrázek 24: Textura tvořená vzorkem (scale 1/10 simplex-noise).

Obrázek 25: Textura tvořená vzorkem (scale 1/10 simplex-snoise).

Obrázek 26: Textura tvořená vzorkem (scale 1/10 plasma).

Obrázek 27: Textura tvořená vzorkem (scale 1/10 splasma).

10. Funkce compose a její použití při tvorbě textur

Při tvorbě textur může být užitečná i funkce compose, která vypadá následovně:

(defn compose
  "Composes two or more vector functions"
  ([f g]
    (warp g f))
  ([f g & more]
    (compose f (apply compose g more))))

Pokud jsou této funkci předány další dvě funkce, vyhodnotí se (warp g f), kde warp je pomocná funkce určená pro manipulaci s datovou strukturou představující „předpis“ pro generování textury. V praxi se funkce compose může použít následujícím způsobem pro kompozici dvou funkcí pro generování textury:

(defn composited-patterns-test
    "Otestování funkce compose."
    []
    (let [patterns [(compose turbulence spots)
                    (compose turbulence plasma)
                    (compose (scale 1/40 (checker black white)) plasma)
                    (compose (scale 1/40 (checker black white)) turbulence)]]
        ; postupně projít všemi prvky vektoru "patterns", vytvořit
        ; dvouprvkový vektor [index+patter] a zavolat pro tento
        ; vektor funkci write-pattern
        (doseq [ [i pattern] (map-indexed vector patterns)]
            (write-pattern pattern (str "composite_" i ".png")))))

Obrázek 28: Vzorek získaný výrazem (compose turbulence spots).

Obrázek 29: Vzorek získaný výrazem (compose turbulence plasma).

Obrázek 30: Vzorek získaný výrazem (compose (scale 1/40 (checker black white)) plasma).

Obrázek 31: Vzorek získaný výrazem (compose (scale 1/40 (checker black white)) turbulence).

11. Vnořené vzorky, funkce offset, rotatescale

Posledními dvěma vlastnostmi knihovny Clisk, s nimiž se v dnešním článku seznámíme, jsou funkce offset, rotate, scale a taktéž možnost tvorby takzvaných vnořených vzorků (nested patterns). Tuto možnost si můžeme nejlépe ukázat na textuře šachovnice, protože namísto dvoubarevných políček může být každé políčko (například původně každé bílé políčko šachovnice) nahrazeno jiným vzorkem. Vliv funkcí offset, rotate a scale je zřejmý: slouží k posunu vzorku, jeho rotaci či změně měřítka (a to samozřejmě bez ztráty kvality, neboť tyto transformace jsou prováděny během výpočtu a nikoli na výsledné bitmapě). Vliv těchto funkcí je možné skládat, protože interně se textura podrobuje libovolné afinní transformaci. Podívejme se na následující příklady, z nichž bude patrný jak způsob tvorby vnořených vzorků, tak i způsob použití afinních transformací:

(defn nested-patterns-test
    []
    (let [patterns [(checker black white)
                    (checker black (scale 1/4 (checker blue yellow)))
                    (checker (scale 1/4 (checker white black)) (scale 1/4 (checker blue yellow)))
                    (checker (rotate 45 (scale 1/4 (checker white black))) (scale 1/4 (checker blue yellow)))
                    (rotate 20 (checker (rotate 20 (scale 1/4 (checker white black)))
                                        (rotate 40 (scale 1/4 (checker blue yellow)))))
                    ]]
        ; postupně projít všemi prvky vektoru "patterns", vytvořit
        ; dvouprvkový vektor [index+patter] a zavolat pro tento
        ; vektor funkci write-pattern
        (doseq [ [i pattern] (map-indexed vector patterns)]
            (write-pattern pattern (str "nested_" i ".png")))))

Obrázek 32: Vzorek získaný výrazem (checker black white). Výsledkem je nám již známá šachovnice.

Obrázek 33: Vzorek získaný výrazem (checker black (scale 1/4 (checker blue yellow))). Vliv funkce scale je zřejmý – došlo ke zmenšení rozměrů vzorku na 1/4 původní hodnoty. Navíc jsou původně bílá políčka nahrazena dalším vzorkem zmenšeným právě o onu 1/4.

Obrázek 34: Vzorek získaný výrazem (checker (scale 1/4 (checker white black)) (scale 1/4 (checker blue yellow))) Variace na předchozí příklad, zde ovšem došlo k náhradě jak bílých, tak i černých políček za jiné vzorky.

Obrázek 35: Vzorek získaný výrazem (checker (rotate 45 (scale 1/4 (checker white black))) (scale 1/4 (checker blue yellow))). „Vnitřní“ vzorek je otočen o 45 stupňů.

Obrázek 36: Vzorek získaný výrazem (rotate 20 (checker (rotate 20 (scale 1/4 (checker white black))) (rotate 40 (scale 1/4 (checker blue yellow))))).

12. Úplný zdrojový kód demonstračního příkladu clisktest3

Všechny funkce popsané v předchozích třech kapitolách jsou součástí dnešního třetího a současně i posledního demonstračního příkladu nazvaného jednoduše clisktest3. Opět si uvedeme výpis obou nejdůležitějších souborů, z nichž se tento příklad skládá: projektového souboru i „jádra“ aplikace.

Obsah souboru project.clj:

widgety

(defproject clisktest2 "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [net.mikera/clisk "0.10.0"]]
  :main ^:skip-aot clisktest3.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}})

Obsah souboru core.clj:

(ns clisktest3.core
    (:gen-class)
    (:use clisk.live))
 
(import java.io.File)
(import javax.imageio.ImageIO)
 
(defn write-image
    "Uložení rastrového obrázku typu BufferedImage do souboru."
    [image file-name]
    (ImageIO/write image "png" (File. file-name)))
 
(defn write-pattern
    "Vytvoření rastrového obrázku na základě předaného patternu."
    [pattern file-name]
    (write-image (image pattern) file-name))
 
(defn predefined-patterns-test-
    "Otestování funkcí pro vytváření vzorků/textur 2D obrázků."
    []
    (write-pattern (checker black white)             "pattern_0.png")
    (write-pattern (scale 1/4 (checker black white)) "pattern_1.png")
    (write-pattern turbulence                        "pattern_2.png")
    (write-pattern (scale 1/10 perlin-noise)         "pattern_3.png")
    (write-pattern (scale 1/40 perlin-noise)         "pattern_4.png")
    (write-pattern (scale 1/10 perlin-snoise)        "pattern_5.png")
    (write-pattern (scale 1/10 simplex-noise)        "pattern_6.png")
    (write-pattern (scale 1/10 simplex-snoise)       "pattern_7.png")
    (write-pattern (scale 1/10 plasma)               "pattern_8.png")
    (write-pattern (scale 1/10 splasma)              "pattern_9.png"))
 
(defn predefined-patterns-test
    "Otestování funkcí pro vytváření vzorků/textur 2D obrázků."
    []
    (let [patterns [(checker black white)
                    (scale 1/4 (checker black white))
                    turbulence
                    spots
                    blotches
                    (scale 1/10 perlin-noise)
                    (scale 1/40 perlin-noise)
                    (scale 1/10 perlin-snoise)
                    (scale 1/10 simplex-noise)
                    (scale 1/10 simplex-snoise)
                    (scale 1/10 plasma)
                    (scale 1/10 splasma)]]
        ; postupně projít všemi prvky vektoru "patterns", vytvořit
        ; dvouprvkový vektor [index+patter] a zavolat pro tento
        ; vektor funkci write-pattern
        (doseq [ [i pattern] (map-indexed vector patterns)]
            (write-pattern pattern (str "pattern_" i ".png")))))
 
(defn composited-patterns-test
    "Otestování funkce compose."
    []
    (let [patterns [(compose turbulence spots)
                    (compose turbulence plasma)
                    (compose (scale 1/40 (checker black white)) plasma)
                    (compose (scale 1/40 (checker black white)) turbulence)]]
        ; postupně projít všemi prvky vektoru "patterns", vytvořit
        ; dvouprvkový vektor [index+patter] a zavolat pro tento
        ; vektor funkci write-pattern
        (doseq [ [i pattern] (map-indexed vector patterns)]
            (write-pattern pattern (str "composite_" i ".png")))))
 
(defn nested-patterns-test
    []
    (let [patterns [(checker black white)
                    (checker black (scale 1/4 (checker blue yellow)))
                    (checker (scale 1/4 (checker white black)) (scale 1/4 (checker blue yellow)))
                    (checker (rotate 45 (scale 1/4 (checker white black))) (scale 1/4 (checker blue yellow)))
                    (rotate 20 (checker (rotate 20 (scale 1/4 (checker white black)))
                                        (rotate 40 (scale 1/4 (checker blue yellow)))))
                    ]]
        ; postupně projít všemi prvky vektoru "patterns", vytvořit
        ; dvouprvkový vektor [index+patter] a zavolat pro tento
        ; vektor funkci write-pattern
        (doseq [ [i pattern] (map-indexed vector patterns)]
            (write-pattern pattern (str "nested_" i ".png")))))
 
(defn -main
    [& args]
    (try
        (println "Predefined patterns test...")
        ;(predefined-patterns-test-)
        (predefined-patterns-test)
        (println "Composited patterns test...")
        (composited-patterns-test)
        (println "Nested patterns test...")
        (nested-patterns-test)
        (catch Throwable e
            (println (.toString e)))
        (finally ; jistota, že program vždy korektně skončí
            (System/exit 0))))

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

Všechny tři dnes popsané demonstrační příklady byly, podobně jako v předchozích částech tohoto seriálu, uloženy do GIT repositáře dostupného na adrese https://github.com/tisnik/clojure-examples. V tabulce zobrazené pod tímto odstavcem naleznete na jednotlivé příklady přímé odkazy:

14. Odkazy na předchozí části seriálu

  1. Leiningen: nástroj pro správu projektů napsaných v Clojure
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/
  2. Leiningen: nástroj pro správu projektů napsaných v Clojure (2)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-2/
  3. Leiningen: nástroj pro správu projektů napsaných v Clojure (3)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-3/
  4. Leiningen: nástroj pro správu projektů napsaných v Clojure (4)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-4/
  5. Leiningen: nástroj pro správu projektů napsaných v Clojure (5)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-5/
  6. Leiningen: nástroj pro správu projektů napsaných v Clojure (6)
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-6/
  7. Programovací jazyk Clojure a databáze (1.část)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/
  8. Pluginy pro Leiningen
    http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/
  9. Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi/
  10. Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi-2/

15. Odkazy na Internetu

  1. Clisk
    https://github.com/mikera/clisk
  2. clojars: net.mikera/clisk
    https://clojars.org/net.mikera/clisk
  3. clojure.inspector
    http://clojure.github.io/clo­jure/clojure.inspector-api.html
  4. Clisk: wiki
    https://github.com/mikera/clisk/wiki
  5. Dokumentace vygenerovaná pro knihovnu core.matrix
    https://cloojure.github.i­o/doc/core.matrix/index.html
  6. Size and Dimensionality
    https://groups.google.com/fo­rum/#!topic/numerical-clojure/zebBCa68eTw/discussion
  7. Towards core.matrix for Clojure?
    https://clojurefun.wordpres­s.com/2013/01/05/towards-core-matrix-for-clojure/
  8. The Clojure Toolbox
    http://www.clojure-toolbox.com/
  9. Neanderthal
    http://neanderthal.uncomplicate.org/
  10. Hello world project
    https://github.com/uncompli­cate/neanderthal/blob/mas­ter/examples/hello-world/project.clj
  11. vectorz-clj
    https://github.com/mikera/vectorz-clj
  12. vectorz – Examples
    https://github.com/mikera/vectorz-clj/wiki/Examples
  13. gloss
    https://github.com/ztellman/gloss
  14. HTTP client/server for Clojure
    http://www.http-kit.org/
  15. Array Programming
    https://en.wikipedia.org/wi­ki/Array_programming
  16. Discovering Array Languages
    http://archive.vector.org­.uk/art10008110
  17. no stinking loops – Kalothi
    http://www.nsl.com/
  18. Vector (obsahuje odkazy na články, knihy a blogy o programovacích jazycích APL, J a K)
    http://www.vector.org.uk/
  19. APL Interpreters
    http://www.vector.org.uk/?a­rea=interpreters
  20. APL_(programming_language
    http://en.wikipedia.org/wi­ki/APL_(programming_langu­age
  21. APL FAQ
    http://www.faqs.org/faqs/apl-faq/
  22. APL FAQ (nejnovější verze)
    http://home.earthlink.net/~swsir­lin/apl.faq.html
  23. A+
    http://www.aplusdev.org/
  24. APLX
    http://www.microapl.co.uk/
  25. FreeAPL
    http://www.pyr.fi/apl/index.htm
  26. J: a modern, high-level, general-purpose, high-performance programming language
    http://www.jsoftware.com/
  27. K, Kdb: an APL derivative for Solaris, Linux, Windows
    http://www.kx.com
  28. openAPL (GPL)
    http://sourceforge.net/pro­jects/openapl
  29. Parrot APL (GPL)
    http://www.parrotcode.org/
  30. Learning J (Roger Stokes)
    http://www.jsoftware.com/hel­p/learning/contents.htm
  31. Rosetta Code
    http://rosettacode.org/wiki/Main_Page
  32. Why APL
    http://www.acm.org/sigapl/whyapl.htm
  33. java.jdbc API Reference
    https://clojure.github.io/java.jdbc/
  34. Hiccup
    https://github.com/weavejester/hiccup
  35. Clojure Ring na GitHubu
    https://github.com/ring-clojure/ring
  36. A brief overview of the Clojure web stack
    https://brehaut.net/blog/2011/rin­g_introduction
  37. Getting Started with Ring
    http://www.learningclojure­.com/2013/01/getting-started-with-ring.html
  38. Getting Started with Ring and Compojure – Clojure Web Programming
    http://www.myclojureadven­ture.com/2011/03/getting-started-with-ring-and-compojure.html
  39. Unit Testing in Clojure
    http://nakkaya.com/2009/11/18/unit-testing-in-clojure/
  40. Testing in Clojure (Part-1: Unit testing)
    http://blog.knoldus.com/2014/03/22/tes­ting-in-clojure-part-1-unit-testing/
  41. API for clojure.test – Clojure v1.6 (stable)
    https://clojure.github.io/clo­jure/clojure.test-api.html
  42. Leiningen: úvodní stránka
    http://leiningen.org/
  43. Leiningen: Git repository
    https://github.com/techno­mancy/leiningen
  44. leiningen-win-installer
    http://leiningen-win-installer.djpowell.net/
  45. Clojure 1: Úvod
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/
  46. Clojure 2: Symboly, kolekce atd.
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/
  47. Clojure 3: Funkcionální programování
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-3-cast-funkcionalni-programovani/
  48. Clojure 4: Kolekce, sekvence a lazy sekvence
    http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-4-cast-kolekce-sekvence-a-lazy-sekvence/
  49. Clojure 5: Sekvence, lazy sekvence a paralelní programy
    http://www.root.cz/clanky/clojure-a-bezpecne-aplikace-pro-jvm-sekvence-lazy-sekvence-a-paralelni-programy/
  50. Clojure 6: Podpora pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/
  51. Clojure 7: Další funkce pro paralelní programování
    http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/
  52. Clojure 8: Identity, stavy, neměnné hodnoty a reference
    http://www.root.cz/clanky/programovaci-jazyk-clojure-8-identity-stavy-nemenne-hodnoty-a-referencni-typy/
  53. Clojure 9: Validátory, pozorovatelé a kooperace s Javou
    http://www.root.cz/clanky/programovaci-jazyk-clojure-9-validatory-pozorovatele-a-kooperace-mezi-clojure-a-javou/
  54. Clojure 10: Kooperace mezi Clojure a Javou
    http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/
  55. Clojure 11: Generátorová notace seznamu/list comprehension
    http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/
  56. Clojure 12: Překlad programů z Clojure do bajtkódu JVM I
    http://www.root.cz/clanky/programovaci-jazyk-clojure-12-preklad-programu-z-clojure-do-bajtkodu-jvm/
  57. Clojure 13: Překlad programů z Clojure do bajtkódu JVM II
    2) http://www.root.cz/clanky/programovaci-jazyk-clojure-13-preklad-programu-z-clojure-do-bajtkodu-jvm-pokracovani/
  58. Clojure 14: Základy práce se systémem maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/
  59. Clojure 15: Tvorba uživatelských maker
    http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/
  60. Clojure 16: Složitější uživatelská makra
    http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/
  61. Clojure 17: Využití standardních maker v praxi
    http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/
  62. Clojure 18: Základní techniky optimalizace aplikací
    http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/
  63. Clojure 19: Vývojová prostředí pro Clojure
    http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/
  64. Clojure 20: Vývojová prostředí pro Clojure (Vimu s REPL)
    http://www.root.cz/clanky/programovaci-jazyk-clojure-20-vyvojova-prostredi-pro-clojure-integrace-vimu-s-repl/
  65. Clojure 21: ClojureScript aneb překlad Clojure do JS
    http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/
Našli jste v článku chybu?
DigiZone.cz: Rapl: seriál, který vás smíří s ČT

Rapl: seriál, který vás smíří s ČT

DigiZone.cz: Digi Slovakia zařazuje stanice SPI

Digi Slovakia zařazuje stanice SPI

Vitalia.cz: Tahák, jak vyzrát nad zápachem z úst

Tahák, jak vyzrát nad zápachem z úst

Vitalia.cz: Jak Ondra o astma přišel

Jak Ondra o astma přišel

Vitalia.cz: Studie porovnávala efekt homeopatie

Studie porovnávala efekt homeopatie

Lupa.cz: Další Češi si nechali vložit do těla čip

Další Češi si nechali vložit do těla čip

DigiZone.cz: Na jaká videa se vlastně díváme

Na jaká videa se vlastně díváme

Vitalia.cz: Test dětských svačinek: Tyhle ne!

Test dětských svačinek: Tyhle ne!

Lupa.cz: Jak se prodává firma za miliardu?

Jak se prodává firma za miliardu?

Lupa.cz: Blíží se konec Wi-Fi sítí bez hesla?

Blíží se konec Wi-Fi sítí bez hesla?

Podnikatel.cz: Letáky? Lidi zuří, ale ony stále fungují

Letáky? Lidi zuří, ale ony stále fungují

Podnikatel.cz: Instalatér, malíř a elektrikář. "Vymřou"?

Instalatér, malíř a elektrikář. "Vymřou"?

Vitalia.cz: Fyzioterapeutka: Chůze naboso? Rozhodně ano!

Fyzioterapeutka: Chůze naboso? Rozhodně ano!

DigiZone.cz: Technisat připravuje trojici DAB

Technisat připravuje trojici DAB

DigiZone.cz: Nova opět stahuje „milionáře“

Nova opět stahuje „milionáře“

Podnikatel.cz: Znáte už 5 novinek k #EET

Znáte už 5 novinek k #EET

Měšec.cz: TEST: Vyzkoušeli jsme pražské taxikáře

TEST: Vyzkoušeli jsme pražské taxikáře

DigiZone.cz: DVB-T2 ověřeno: seznam TV zveřejněn

DVB-T2 ověřeno: seznam TV zveřejněn

Lupa.cz: Patička e-mailu závazná jako vlastnoruční podpis?

Patička e-mailu závazná jako vlastnoruční podpis?

DigiZone.cz: Samsung EVO-S: novinka pro Skylink

Samsung EVO-S: novinka pro Skylink