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):
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_objektu. 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](https://i.iinfo.cz/urs/tcl9_1-112652436045742.png)
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](https://i.iinfo.cz/urs/tcl9_2-112652439490117.png)
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](https://i.iinfo.cz/urs/tcl9_3-112652442691921.png)
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](https://i.iinfo.cz/urs/tcl9_4-112652445718697.png)
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_řádku. 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.
#!/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.