Hlavní navigace

Programovací jazyk TCL (9)

13. 9. 2005
Doba čtení: 12 minut

Sdílet

V předchozím pokračování seriálu o programovacím jazyku Tcl a k němu příslušejícímu toolkitu Tk jsme si uvedli postupy, které je možné použít při zobrazování grafiky založené jak na rastrových bitmapách a pixmapách, tak i na vektorových obrazcích vkládaných na takzvané plátno - canvas. V dnešním dílu se zaměříme na podrobnější popis objektů vkládaných na plátno a na způsob programové tvorby rastrové grafiky.

Obsah

1. Objekty vkládané na plátno
2. Práce s neuzavřenými křivkami
3. Uzavřené obrazce
4. Práce s textem na plátnu
5. Bitmapy a pixmapy
6. Pokročilejší příklady s objektem image
7. Přímočaré vykreslení Mandelbrotovy množiny
8. Vylepšení aplikace vykreslující Mandelbrotovu množinu
9. Obsah dalšího pokračování tohoto seriálu

1. Objekty vkládané na plátno

Minule jsme si ukazovali, jakým způsobem se vkládají grafické objekty, tj. programové objekty ve smyslu OOP, které mají i svoji geometrickou reprezentaci, na widget nazvaný canvas – plátno. Grafické objekty se v nejjednodušším případě vkládají pomocí příkazu jméno_plátna create jméno_objektu, kde jméno_plátna je definované programátorem a jméno_objektu určuje, jaký objekt se bude na plátno vkládat. Kromě pojmenování objektu se může každému objektu přiřadit buď jeden, nebo více takzvaných tagů, pomocí kterých je možné objekty rozčleňovat do skupin. Návratovou hodnotou tohoto příkazu je systémově jednoznačný identifikátor objektu. Je možné použít následující typy objektů (typem je zde současně myšleno i jméno objektu předávané příkazucreate):

Objekty, které lze pokládat na plátno (canvas)
Jméno objektu Význam
arc kruhový nebo eliptický oblouk
bitmap bitmapový obrázek, ke kterému může příslušet druhá bitmapa definující průhlednost jednotlivých pixelů
image obecně vícebarevný rastrový obrázek – i zde je možné pracovat s průhledností
line úsečka, lomená úsečka zadaná více body (polyčára) nebo hladká spline křivka
oval uzavřená a případně i vyplněná kružnice nebo elipsa
polygon uzavřený polygon (omezený polyčárou) či tvar vytvořený ze spline křivek
rectangle čtverec nebo obdélník
text textový řetězec
window vnořené okno se samostatným řízením zobrazování a ořezávání objektů

Na plátnu je nejzajímavější fakt, že autonomně uchovává všechny vlastnosti na něm uložených objektů, tj. na rozdíl od jiných grafických knihoven se objekt pouze nerozrastruje do výsledného rastrového obrázku, ale také se zaznamená do interní hierarchické (většinou čistě stromové) struktury, takže k objektům lze programově přistupovat a číst či přepisovat jejich vlastnosti, což se ihned projeví na grafické reprezentaci objektů na obrazovce. Objekty lze také z plátna odstraňovat pomocí příkazu jméno_plátna delete identifikátor_ob­jektu. V následujících kapitolách si uvedeme příklady použití jednotlivých grafických objektů. Ve druhé kapitole bude popsána práce s obecně neuzavřenými a nevyplněnými křivkami, třetí kapitola bude věnována uzavřeným a případně i vyplněným obrazcům, kapitola čtvrtá vysvětluje práci s textem a konečně v kapitole páté si ukážeme vkládání rastrových obrázků na plátno.

2. Práce s neuzavřenými křivkami

Pro práci s neuzavřenými křivkami lze použít objekt line. Na první pohled se jedná o velmi jednoduchý objekt, ve skutečnosti s ním však lze vytvářet i velmi složité obrazce složené například ze spline křivek. V nejjednodušším případě se pomocí objektu line vykreslí pouze jedna úsečka:

jméno_plátna create line x1 y1 x2 y2 další_volby 

Dále je možné specifikovat více bodů (vrcholů), což značí, že se na plátno vykreslí místo jedné úsečky lomená čára (polyčára, polyline):

jméno_plátna create line x1 y1 x2 y2 ... xn yn další_volby 

popř.:

jméno_plátna create line seznam_souřadnic další_volby 

Následuje první demonstrační příklad, na kterém je ukázán základní způsob práce s objektem line:

#!/usr/bin/wish

# první demonstrační příklad

# vytvoření plátna
canvas .platno
pack .platno

# vytvoření objektů na plátnu
.platno create line 0 0 100 100
.platno create line 0 100 150 100 150 150 200 150 250 100 
TCL 9a

Obrázek 1: screenshot prvního demonstračního příkladu

Při vytváření polyčar je možné specifikovat poměrně velké množství voleb. Pravděpodobně nejpoužívanější jsou volby -fill (volba barvy úsečky), -width (tloušťka čáry), -joinstyle (způsob ukončení hran) a -arrow (vykreslení šipek na konci čar). Kromě toho je také možné specifikovat, že se má místo lomené čáry vykreslit spline křivka. To zajišťuje volba -spline s booleovskou hodnotou a -splinesteps s celočíselnou hodnotou, kterou se zadává, na kolik úsečkových segmentů má být každá část lomené čáry rozdělena. Způsob práce se spline křivkami je patrný ze druhého demonstračního příkladu. Na tomto příkladu je také ukázáno, jak lze pro specifikaci vrcholů používat seznamy.

#!/usr/bin/wish

# druhý demonstrační příklad

# vytvoření plátna
canvas .platno
pack .platno

# vytvoření objektů na plátnu
set souradnice {0 50 100 50 100 200 200 200 300 100}
.platno create line $souradnice -smooth 0 -fill black
.platno create line $souradnice -smooth 1 -splinesteps 1 -fill red
.platno create line $souradnice -smooth 1 -splinesteps 2 -fill darkgreen
.platno create line $souradnice -smooth 1 -splinesteps 3 -fill blue
.platno create line $souradnice -smooth 1 -splinesteps 4 -fill orange
# .platno create line $souradnice -smooth 1 -splinesteps 5 -fill white 
TCL 9b

Obrázek 2: screenshot druhého demonstračního příkladu

3. Uzavřené obrazce

Pro zobrazování uzavřených křivek existuje mnoho objektů. Nejdříve si popíšeme objekt arc, pomocí kterého lze, jak již název napovídá, vytvářet oblouky a také kruhové či eliptické výseče. Příkaz pro vytvoření a zobrazení oblouku vypadá následovně:

jméno_plátna create arc x1 y1 x2 y2 další_volby 

popř.:

jméno_plátna create arc seznam_souřadnic další_volby 

Pomocí souřadnic [x1, y1] a [x2, y2] se specifikuje obalový obdélník oblouku (jedná se o dva protilehlé vrcholy). Pokud se zadá obdélník se stejně dlouhými hranami, je vytvořen kruhový oblouk, v opačném případě se jedná o oblouk eliptický. Nejdůležitějšími volbami jsou-start hodnota a -extend hodnota. Těmito volbami se udává počáteční a koncový úhel – mezi zadanými úhly bude oblouk vytvořen. Další důležitou volbou je -type typ, kde lze nastavit, zda se má zobrazit kruhová či eliptická výseč (pieslice – ta může být vyplněná) nebo kruhový či eliptický oblouk (arc). Pomocí voleb -outline, -fill, -stipple atd. je možné nastavit způsob zobrazení obrysů i výplně oblouků.

Dalším jednoduchým uzavřeným obrazcem je obdélník, který je vytvořen pomocí objektu rectangle. Vytvoření se provádí následovně:

jméno_plátna create rectangle x1 y1 x2 y2 další_volby 

popř.:

jméno_plátna create rectangle seznam_souřadnic další_volby 

Podobně jako u oblouku, i zde je možné volit styl výplně a obrysu.

Dále je možné pracovat s kruhem či elipsou. Ty se vytváří pomocí objektuoval přes příkaz:

jméno_plátna create oval x1 y1 x2 y2 další_volby 

popř.:

jméno_plátna create oval seznam_souřadnic další_volby 

Souřadnice [x1, y1] a [x2, y2] udávají protilehlé vrcholy obalového obdélníka, podobně jako u oblouku. Pokud má obdélník stejně dlouhé hrany, vykreslí se kruh či kružnice (podle nastavení vyplňování), v opačném případě se vykreslí elipsa. Způsob vyplnění tohoto typu objektu je stejný jako u předchozích dvou typů.

Nejsložitější uzavřené tvary se tvoří pomocí objektu polygon. Při vytváření tohoto objektu se může zadat prakticky libovolné množství souřadnic vrcholů. Buď se všechny souřadnice specifikují přímo:

jméno_plátna create polygon x1 y1 ... xn yn další_volby 

nebo (a v praxi mnohem častěji) pomocí seznamu souřadnic:

jméno_plátna create polygon seznam_souřadnic další_volby 

Polygon může být buď vyplněný, nebo prázdný. Kromě klasického polygonu, jehož hrany jsou tvořeny polyčárou, je možné vytvářet i tvar ohraničený spline křivkami. Nastavení spline křivek se děje pomocí voleb -smooth (povoluje či zakazuje spline křivky) a -splinesteps hodnota (způsob rozdělení ideální křivky na úsečkové segmenty). Následuje příklad vykreslení dvou polygonů:

#!/usr/bin/wish

# třetí demonstrační příklad

# vytvoření plátna
canvas .platno
pack .platno

# vytvoření objektů na plátnu
.platno create polygon {100 100 100 200 200 200 200 100}
.platno create polygon {10 10 150 200 200 250 200 100} -smooth 1 -fill red 
TCL 9c

Obrázek 3: screenshot třetího demonstračního příkladu

4. Práce s textem na plátnu

Na plátno lze samozřejmě vkládat i text. Pro tento účel se používá objekt nazvaný jak jinak než text. Způsob vytvoření tohoto objektu je velmi jednoduchý:

jméno_plátna create text x y další_volby 

kde se pomocí hodnot [x, y] zadává souřadnice počátku textu. Mezi nejpoužívanější volby patří -text (vlastní řetězec, který má být zobrazen), -fill (barva textu), -font (specifikace fontu) a -anchor (způsob umístění textu vůči vkládacímu bodu). Řetězec i další parametry textu lze samozřejmě při běhu aplikace měnit, ale pro uživatelem prováděnou editaci je mnohem výhodnější používat widget text, který bude popsán v další části tohoto seriálu.

5. Bitmapy a pixmapy

Na plátno je možné vkládat i rastrové obrazce ve formě bitmap a pixmap. Bitmapy jsou zadávány pomocí příkazu:

jméno_plátna create bitmap x y další_volby 

resp.

jméno_plátna create bitmap seznam_souřadnic další_volby 

Pixmapy se vytvářejí podobným způsobem, ale místo objektu bitmap je použit objekt image:

jméno_plátna create image x y další_volby
jméno_plátna create image seznam_souřadnic další_volby 

Práce s pixmapami vytvořenými pomocí objektu image je ukázána v dalších kapitolách, u bitmap se většinou používá volba -bitmap, kterou se bitmapa specifikuje. Samotná bitmapa se vytváří příkazem:

image create bitmap jméno_bitmapy další_volby 

Bitmapy lze načítat z disku (volba -file) nebo z programově vytvořených dat (volba -data). Dále je možné specifikovat barvu popředí i pozadí (volby -background a-foreground) a maskovací bitmapu, kterou se specifikuje průhlednost jednotlivých pixelů (volby -maskdata a -maskfile).

6. Pokročilejší příklady s objektem image

Už v předchozím pokračování tohoto seriálu jsme si ukázali, jakým způsobem je možné programově vytvořit rastrový obrázek a tento obrázek zobrazit na plátnu. Pro zopakování si tento příklad uvedeme ještě jednou. Asi nejzajímavější částí je přitom způsob zápisu barev jednotlivých pixelů, barvy jsou zde reprezentovány řetězcem složeným ze šesti hexadecimálních číslic, podobně jako v HTML.

#!/usr/bin/wish

# čtvrtý demonstrační příklad

# rozměry obrázku
set width  256
set height 256

# vytvoření plátna
canvas .canvas -width $width -height $height
pack   .canvas -fill both

# vytvoření obrázku
set image [image create photo -width $width -height $height]
$image blank

# vložení obrázku na plátno
.canvas create image 0 0 -image $image -anchor nw

# naplnění pixelů v obrázku
for {set row 0} {$row<$height} {incr row} {
    for {set col 0} {$col<$width} {incr col} {
        # barva obrázku v HTML stylu
        set colour [format "#%02X%02X%02X" $col $row 0]
        # zapsat barvu pixelu
        $image put $colour -to $col $row
    }
} 

7. Přímočaré vykreslení Mandelbrotovy množiny

Použití widgetu image si ukážeme na příkladu, ve kterém se vypočítá a vykreslí základní pohled na takzvanou Mandelbrotovu množinu, což je asi nejznámější dynamický fraktál vytvořený v komplexní rovině. Vykreslování probíhá po jednotlivých pixelech, přičemž každému pixelu může být přiřazena jedna z deseti barev uložených v seznamu – viz proceduru setpixel. Způsob práce s barvou je zde tedy mírně odlišný od příkladu, ve kterém se pracovalo přímo s kódem barev. První verze programu pro vykreslení Mandelbrotovy množiny vypadá následovně:

#!/usr/bin/wish

# ---------------------------------------------------------------------
# Vykreslení Mandelbrotovy množiny
# první verze
# ---------------------------------------------------------------------

# vykreslení pixelu na základě počtu iterací
proc setpixel { image xpix ypix iter } {
    # nastavení barvy pixelu
    if { $iter >= 0 } {
        # máme vytvořen seznam s omezeným počtem barev
        set iter [expr {$iter%10}]
        set colour [lindex {white lightblue blue green yellow orange red purple magenta black} $iter]
    } else {
        set colour black
    }
    # vykreslení pixelu do obrázku
    $image put $colour -to $xpix $ypix
}

# iterace pro jeden bod v komplexní rovině
proc iteration { cx cy maxiter } {
    set zx 0
    set zy 0
    set iter -1
    for { set i 0 } { $i < $maxiter } { incr i } {
        # pomocné proměnné pro urychlení výpočtu
        set zx2 [expr $zx*$zx]
        set zy2 [expr $zy*$zy]
        # při překročení hranice ukončit smyčku
        if { $zx2+$zy2 > 4.0 } {
            set iter $i
            break
        }
        set zy [expr {2.0*$zx*$zy+$cy}]
        set zx [expr {$zx2-$zy2+$cx}]
    }
    return $iter
}

# konstanty ovlivňující velikost obrázku a počet iterací
set width  256
set height 256
set maxiter 50

# vytvoření plátna a obrázku
canvas .canvas -width $width -height $height
pack   .canvas -fill both
set image [image create photo -width $width -height $height]
$image blank

# navázání plátna a obrázku
.canvas create image 0 0 -image $image -anchor nw

# vykreslení celé Mandelbrotovy množiny
proc mandelbrot {width height maxiter} {
    # konstanty pro krok v horizontálním i vertikálním směru
    set cdx [expr {4.0/$width}]
    set cdy [expr {4.0/$height}]
    set cy -2.0
    # smyčka pro všechny řádky v obrázku
    for {set row 0} {$row<$height} {incr row} {
        set cx -2.0
        # smyčka pro všechny sloupce na řádku
        for {set col 0} {$col<$width} {incr col} {
            set iter [iteration $cx $cy $maxiter]
            setpixel $::image $col $row $iter
            set cx [expr {$cx+$cdx}]
        }
        set cy [expr {$cy+$cdy}]
        puts $cy
    }
}

# spuštění vykreslení celé Mandelbrotovy množiny
mandelbrot $width $height $maxiter 
TCL 9d

Obrázek 4: screenshot příkladu vykreslujícího Mandelbrotovu množinu

8. Vylepšení aplikace vykreslující Mandelbrotovu množinu

Nevýhodou předchozího příkladu je skutečnost, že není vidět postupné vykreslování Mandelbrotovy množiny. Řešením by bylo spustit výpočet v samostatném vláknu (threadu), my si zde však ukážeme jednodušší variantu, při které se vždy vypočítá a vykreslí jeden obrazový řádek a posléze se s výpočtem dalšího řádku program na stanovenou dobu pozastaví – to postačí k tomu, aby operační systém (resp. jeho grafická nadstavba) okno s fraktálem překreslil. Z hlediska celkové doby výpočtu se zajisté nejedná o ideální řešení, protože i při práci na rychlém počítači se provádění programu na jednotlivých řádcích pozastavuje, a doba výpočtu tak nikdy nemůže klesnout pod hodnotu počet_řádků × doba_čekání_na_řád­ku. V praxi by se předchozí příklad vyřešil jiným způsobem, například tak, že grafické uživatelské rozhraní by se vytvořilo pomocí Tcl/Tk a vlastní výpočet by proběhl v modulu napsaném například v programovacím jazyku C (tato část by vůbec nemusela do GUI zasahovat, pouze by předala výslednou pixmapu zpátky do volající procedury). Největší síla Tcl totiž spočívá v možnosti integrace modulů napsaných v různých programovacích jazycích.

CS24_early

#!/usr/bin/wish

# ---------------------------------------------------------------------
# Vykreslení Mandelbrotovy množiny
# druhá verze s vykreslením po jednotlivých řádcích
# ---------------------------------------------------------------------

# vykreslení pixelu na základě počtu iterací
proc setpixel { image xpix ypix iter } {
    # nastavení barvy pixelu
    if { $iter >= 0 } {
        # máme vytvořen seznam s omezeným počtem barev
        set iter [expr {$iter%10}]
        set colour [lindex {white lightblue blue green yellow orange red purple magenta black} $iter]
    } else {
        set colour black
    }
    # vykreslení pixelu do obrázku
    $image put $colour -to $xpix $ypix
}

# iterace pro jeden bod v komplexní rovině
proc iteration { cx cy maxiter } {
    set zx 0
    set zy 0
    set iter -1
    for { set i 0 } { $i < $maxiter } { incr i } {
        # pomocné proměnné pro urychlení výpočtu
        set zx2 [expr $zx*$zx]
        set zy2 [expr $zy*$zy]
        # při překročení hranice ukončit smyčku
        if { $zx2+$zy2 > 4.0 } {
            set iter $i
            break
        }
        set zy [expr {2.0*$zx*$zy+$cy}]
        set zx [expr {$zx2-$zy2+$cx}]
    }
    return $iter
}

# konstanty ovlivňující velikost obrázku a počet iterací
set width  256
set height 256
set maxiter 50

# vytvoření plátna a obrázku
canvas .canvas -width $width -height $height
pack   .canvas -fill both
set image [image create photo -width $width -height $height]
$image blank

# navázání plátna a obrázku
.canvas create image 0 0 -image $image -anchor nw

# konstanty pro krok v horizontálním i vertikálním směru
set cdx [expr {4.0/$width}]
set cdy [expr {4.0/$height}]

# výpočet a vykreslení jednoho řádku obrázku Mandelbrotovy množiny
proc mandelbrot_row {cdx cdy row width height maxiter} {
    set cy [expr {-2.0+$cdy*$row}]
    for {set col 0} {$col < $width} {incr col} {
        set cx [expr {-2.0+$cdx*$col}]
        set iter [iteration $cx $cy $maxiter]
        setpixel $::image $col $row $iter
    }
    # počkat malou chvíli před vykreslením dalšího řádku
    if { $row < $height } {
        after 1 [list mandelbrot_row $cdx $cdy [incr row] $width $height $maxiter]
    }
}

set row 0
after 1 [list mandelbrot_row $cdx $cdy $row $width $height $maxiter] 

9. Obsah dalšího pokračování tohoto seriálu

V dalším pokračování tohoto seriálu si popíšeme práci s widgetem text, který slouží pro vytváření a zobrazování víceřádkového textu s formátováním.

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

Autor článku

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