Hlavní navigace

Práce s vektorovým formátem SVG ve frameworku PySide (dokončení)

Pavel Tišnovský

V dnešním článku o frameworku PySide se podruhé budeme zabývat způsobem použití formátu SVG (Scalable Vector Graphics). Minule jsme si řekli, jak se SVG vykresluje, dnes si ukážeme mj. i způsob jeho tvorby pomocí třídy QSvgGenerator.

Doba čtení: 41 minut

Obsah

1. Práce s vektorovým formátem SVG ve frameworku PySide (dokončení)

2. „Ruční“ tvorba SVG a nevýhody tohoto postupu

3. Použití tříd QSvgGenerator a QPainter při tvorbě SVG

4. První demonstrační příklad – pokus o vytvoření výkresu ve formátu SVG

5. Druhý demonstrační příklad – vytvoření prázdného SVG bez entit a role značky <g>

6. Třetí demonstrační příklad – nastavení titulku a popisku SVG výkresu

7. Velikost výkresu a pohledový box

8. Čtvrtý demonstrační příklad – nastavení velikosti a pohledového boxu výkresu

9. Výsledky vygenerované čtvrtým příkladem

10. Pátý příklad – vykreslení základních 2D entit do výkresu

11. Výsledek vygenerovaný pátým příkladem

12. Podpora rastrových operací prováděných při vykreslování 2D entit do SVG

13. Šestý demonstrační příklad – klasické rastrové operace

14. Sedmý demonstrační příklad – rastrové operace využívající alfa kanál

15. Výsledky vytvořené šestým a sedmým příkladem

16. Osmý demonstrační příklad – použití rastrového obrázku v SVG

17. Výsledek vygenerovaný osmým demonstračním příkladem

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

19. Články o možnostech a vlastnostech formátu SVG

20. Odkazy na Internetu

1. Práce s vektorovým formátem SVG ve frameworku PySide (dokončení)

V předchozí části seriálu o tvorbě aplikací s GUI v Pythonu jsme se seznámili s tím, jak je možné v knihovně PySide využívat výkresy či kresby uložené ve formátu SVG (Scalable Vector Graphics), a to jak při zobrazování jednotlivých ovládacích prvků (widgetů), tak i při rasterizaci SVG do bitmapového obrázku (QBitmap, QPixmap). Dnes na toto téma částečně navážeme, protože si ukážeme způsob vytváření nových výkresů s využitím tříd QSvgGenerator a QPainter (s touto třídou jsme se již v tomto seriálu několikrát setkali, protože se jedná o základní třídu použitou pro práci s 2D grafikou v knihovně PySide a používá se i při tvorbě GUI).

Nejprve si však řekněme, jak vlastně vypadá základní struktura prakticky každého SVG souboru. Jako v jakémkoli jiném XML dokumentu i v SVG musí být na prvním řádku uveden takzvaný prolog, ve kterém je uvedena použitá verze XML (prozatím typicky 1.0, méně často pak 1.1) a většinou také použité kódování znaků (implicitně je předpokládáno UTF-8) či informace o tom, zda dokument obsahuje reference na externí entity. Na následujícím řádku bývá dobrým zvykem uvedení odkazu na externí DTD (Document Type Declaration). Jmenný prostor SVG má identifikaci http://www.w3.org/2000/svg (ta bude použita u značky <svg>), veřejný identifikátor PUBLIC „-W3CDTD SVG 1.0EN“ a systémový identifikátor http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd pro SVG verze 1.0 popř. http://www.w3.or­g/Graphics/SVG/1.1/DTD/svg11­.dtd pro verzi 1.1.

Samotný dokument s kresbou je celý obsažen ve značce <svg>, která může mít uvedeno několik atributů. Typicky zde bývá umístěna minimálně informace o velikosti obrázku, umístění obdélníku s pohledem na obrázek (takzvaný view box) a jmenném prostoru pro značky SVG a popř. i jmenném prostoru pro značky Xlink (použité pro vytváření jednosměrných i oboustranných vazeb). Uvedením atributu xmlns se jmenným prostorem odpovídajícím SVG je umožněno, aby se všechny značky SVG mohly uvádět bez prefixu, který by celý zápis dokumentu prodloužil a také znepřehlednil. Dále se většinou specifikuje verze SVG (1.0, 1.1, 1.2) a v případě verze < 1 i profil (tiny, basic, …). Velmi často se setkáme se SVG Tiny verze 1.2. Pokud budeme brát v úvahu všechny výše uvedené informace o prologu, DTD a značce <svg>, můžeme zkonstruovat kostru použitelnou u prakticky každého SVG dokumentu:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="100"
     height="100"
     viewBox="0 0 100 100"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">
     version="1.2"
     baseProfile="tiny"
</svg>

2. „Ruční“ tvorba SVG a nevýhody tohoto postupu

Vzhledem k tomu, že výkresy a kresby uložené ve formátu SVG jsou založeny na XML, může být vygenerování takového souboru založeno na primitivních I/O funkcích, například na přímém zápisu do textového souboru. Tímto tématem jsme se již zabývali minule, takže si dnes pouze ukažme jednoduchý skript (napsaný samozřejmě v Pythonu), který ukázkovou vektorovou kresbu vytvoří. Tento skript nepoužívá žádnou specializovanou knihovnu, ale generuje SVG řádek po řádku jen s využitím základních nástrojů pro formátování řetězců. Následuje výpis zdrojového kódu tohoto krátkého skriptu:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from math import sin, cos
 
 
def main():
    size = 480
    with open("logo.svg", "w") as fout:
        fout.write("<?xml version="1.0" encoding="UTF-8"?>")
        fout.write("<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='{w}' height='{h}'>\n".format(w=size, h=size))
        green = 255
        for i, r, red, blue in zip(range(0, 128), range(128, 0, -1), range(255, 0, -2), range(0, 256, 2)):
            a = i / 12.0
            b = i + 80.0
            x = size / 2 + b * cos(a)
            y = size / 2 + b * sin(a)
            p = "<circle cx='{x}' cy='{y}' r='{r}' ".format(x=x, y=y, r=r)
            q = "fill='rgb({r}, {g}, {b})' style='fill-opacity:.06'/>\n".format(r=red, g=green, b=blue)
            r = "fill='none' stroke='black'/>\n"
            fout.write(p+q)
            fout.write(p+r)
 
        fout.write("</svg>\n")
 
 
if __name__ == '__main__':
    main()

Obrázek 1: Výsledná kresba vytvořená předchozím skriptem a uložená do formátu SVG.

Soubor typu SVG vygenerovaný tímto skriptem by měl vypadat přibližně takto (je zobrazen jen jeho začátek a konec):

<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='480' height='480'>
<circle cx='320.0' cy='240.0' r='128' fill='rgb(255, 255, 0)' style='fill-opacity:.06'/>
<circle cx='320.0' cy='240.0' r='128' fill='none' stroke='black'/>
...
...
...
</svg>
Poznámka: v první verzi tohoto skriptu nebyla do souboru přidána deklarace XML, což by však většině prohlížeček ani grafických editorů nemělo nijak vadit při zpracování výsledné kresby.

3. Použití tříd QSvgGenerator a QPainter při tvorbě SVG

Při tvorbě souborů typu SVG se v knihovně PySide používají dvě spolupracující třídy – QSvgGenerator a QPainter. Zatímco se třída QPainter (resp. přesněji řečeno instance této třídy) stará o vykreslování 2D entit, textu i rastrových obrázků, slouží instance třídy QSvgGenerator jako „plátno“ (canvas), na něž se vykreslování provádí. Nyní již tedy známe minimálně dva typy plátna:

  1. Rastrový obrázek typu QImage, QPixmap nebo QBitmap
  2. Generátor výkresů ve formátu SVG.

Ve skutečnosti existují i další plátna, například driver tiskárny atd.

S třídou QPainter jsme se již v tomto seriálu setkali. Připomeňme si, že samotný objekt QPainter (tedy instance třídy tohoto jména) provádí vykreslení 2D entit a rastrových obrázků na nějaké „plátno“, což může být buď přímo hardwarové zařízení (grafický subsystém), rastrový obrázek nebo právě soubor SVG. Podívejme se nyní na způsob vykreslení velmi jednoduché grafiky do rastrového obrázku QImage s využitím možností nabízených třídou QPainter. Jeden z klasických postupů je následující:

  1. Vytvoření instance třídy QImage, která bude tvořit kreslicí „plátno“ pro QPainter. Konstruktoru QImage se předává rozlišení (počet sloupců a řádků) rastrového obrázku i formát pixelů. Nejjednodušší (i když ne vždy nejrychlejší) je použít formát QtGui.QImage.Format_RGB32 pro plnobarevné obrázky.
  2. Vytvoření objektu typu QPainter konstruktorem QPainter() (tento konstruktor nemá v nejjednodušším případě žádné parametry).
  3. Informace instance třídy QPainter o začátku vykreslování do instance třídy QImage. To se provede zavoláním metody QPainter.begin().
  4. Provedení vlastního vykreslení (například barevné úsečky). Mezi „příkazovými závorkami“ begin a end můžete zavolat libovolné množství vykreslovacích operací.
  5. Informace QPainteru o ukončení vykreslování. To se provede zavoláním metodyQPainter.end().
  6. Konverze objektu typu QImage na QPixmap (nebo QBitmap).
  7. QPixmap či QBitmap lze již přímo vykreslit na GUI, například umístěním na návěští.

V případě, že se má vykreslování provést do souboru s formátem SVG, změní se celý postup vlastně jen nepatrně:

  1. Vytvoření instance třídy QSvgGenerator, která bude tvořit kreslicí „plátno“ pro QPainter.
  2. Specifikace jména výstupního souboru metodouQSvgGenerator.setFileName()
  3. Vytvoření objektu typu QPainter konstruktorem QPainter() (tento konstruktor nemá v nejjednodušším případě žádné parametry).
  4. Informace instance třídy QPainter o začátku vykreslování do instance třídy QSvgGenerator („plátno“). To se provede zavoláním metodyQPainter.begin(), přičemž této metodě předáme referenci na instanci třídy QSvgGenerator.
  5. Provedení vlastního vykreslení (například barevné úsečky). Mezi „příkazovými závorkami“ begin a end můžete zavolat libovolné množství vykreslovacích operací.
  6. Informace QPainteru o ukončení vykreslování. To se provede zavoláním metodyQPainter.end().

4. První demonstrační příklad – pokus o vytvoření výkresu ve formátu SVG

Pokusme se nyní použít třídu QSvgGenerator pro vytvoření výkresu či kresby ve formátu SVG. Teoreticky by pro vytvoření souboru s kostrou výkresu měl stačit tento program napsaný v Pythonu:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from PySide import QtSvg
 
# vytvoření instance třídy QSvgGenerator
generator = QtSvg.QSvgGenerator()
 
# určení typu výstupu a nastavení
# jména výsledného souboru
generator.setFileName("test1.svg")

Ve skutečnosti se po spuštění tohoto skriptu žádný soubor „test1.svg“ nevytvoří, a to z toho důvodu, že jsme vůbec nezačali vykreslování.

5. Druhý demonstrační příklad – vytvoření prázdného SVG bez entit a role značky <g>

Aby skutečně došlo k vytvoření souboru s kostrou výkresu, je nutné zahájit vykreslování. Ve skutečnosti nemusí dojít k vykreslení žádné 2D entity, protože zcela postačuje, když se vytvoří instance třídy QPainter (tu již známe) a použijí se „příkazové závorky“ QPainter.begin() a QPainter.end() společně s určením, že výsledek vykreslování bude uložen do SVG:

# inicializace instance třídy QPainter
painter = QtGui.QPainter()
 
# začátek kreslení
painter.begin(generator)
 
# ihned poté ukončíme kreslení
painter.end()

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

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from PySide import QtGui, QtSvg
 
# vytvoření instance třídy QSvgGenerator
generator = QtSvg.QSvgGenerator()
 
# určení typu výstupu a nastavení
# jména výsledného souboru
generator.setFileName("test2.svg")
 
# inicializace instance třídy QPainter
painter = QtGui.QPainter()
 
# začátek kreslení
painter.begin(generator)
 
# ihned poté ukončíme kreslení
painter.end()

Pokud příklad spustíte, měl by se vytvořit soubor „test2.svg“ s následujícím obsahem:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  version="1.2" baseProfile="tiny">
<title>Qt Svg Document</title>
<desc>Generated with Qt</desc>
<defs>
</defs>
<g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" >
 
</g>
</svg>

Povšimněte si použití párové značky <g>. Tu nalezneme v prakticky každém SVG výkresu. Již v první kapitole jsme se dozvěděli, že formát SVG je založený na obecném značkovacím jazyku XML. To mimo jiného znamená, že všechna data jsou uložena ve stromové struktuře. To je ostatně patrné i z demonstračních příkladů – značka <svg> odpovídá kořenu stromu, ostatní značky představují listy. Je však možné přidávat do tohoto stromu i další uzly, které se mohou dále větvit? Ano, SVG to umožňuje a důvod je jednoduchý: každému uzlu je možné přiřadit styl (například barvu obrysu), který bude zděděn všemi poduzly i listy této části stromu. Uzel se vytvoří právě s využitím párové značky nazvané <g> (od slova group).

Všechny grafické objekty umístěné mezi počáteční a koncovou část značky mohou mít nastavené společné vlastnosti, které jsou zapsány přímo jako atributy značky <g>. A právě z tohoto důvodu vytváří QSvgGenerator při kreslení jednu „globální“ skupinu s implicitním nastavením pera, štětce a režimu výplně.

6. Třetí demonstrační příklad – nastavení titulku a popisku SVG výkresu

Dnešní třetí demonstrační příklad je již nepatrně složitější, protože si v něm ukážeme nastavení metadat, která jsou přidružena k vlastní kresbě. V případě SVG patří mezi metadata především titulek (title) a popis. Tato metadata se nastavují metodami QSvgGenerator.setTitle() a QSvgGenerator.setDescription(). Kromě toho ještě nastavujeme fyzické rozměry výkresu metodou QSvgGenerator.setSize() a pohledový box s využitím metody QSvgGenerator.setViewBox(). Význam těchto dvou metod bude vysvětlen později. Úplný zdrojový kód příkladu vypadá následovně:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from PySide import QtCore, QtGui, QtSvg
 
# vytvoření instance třídy QSvgGenerator
generator = QtSvg.QSvgGenerator()
 
# určení typu výstupu a nastavení
# jména výsledného souboru
generator.setFileName("test3.svg")
 
# specifikace rozměrů SVG obrázku
generator.setSize(QtCore.QSize(320, 240))
 
# viditelný výřez
generator.setViewBox(QtCore.QRect(0, 0, 320, 240))
 
# nastavení titulku SVG obrázku
generator.setTitle("SVG: test 3")
 
# popis SVG obrázku
generator.setDescription("third SVG example")
 
# inicializace instance třídy QPainter
painter = QtGui.QPainter()
 
# začátek kreslení
painter.begin(generator)
 
# ihned poté ukončíme kreslení
painter.end()

Výsledkem činnosti tohoto skriptu bude soubor „test3.svg“ s následujícím obsahem. Povšimněte si, jaký je obsah značek <title> a <desc>:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="112.889mm" height="84.6667mm"
 viewBox="0 0 320 240"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  version="1.2" baseProfile="tiny">
<title>SVG: test 3</title>
<desc>third SVG example</desc>
<defs>
</defs>
<g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" >
 
</g>
</svg>

7. Velikost výkresu a pohledový box

V předchozí kapitole jsme se zmínili o rozměrech výkresu a o pohledovém boxu. Jedná se o dva důležité údaje, které ovlivňují jak způsob zobrazení SVG, tak i například rozměry SVG při tisku atd. Rozměry (nebo též viewport) jsou určeny hodnotami atributů width a height, přičemž za samotnými numerickými hodnotami můžeme uvést i délkovou jednotku. V případě, že jednotka není určena, předpokládá se, že jsou rozměry zapsány v pixelech. Podporované jednotky vychází z HTML a CSS:

Zkratka Použitá jednotka
mm milimetry
cm centimetry
   
in palce
pt typografický „bod“ neboli 1/72 palce
pc typografická jednotka „pica“ neboli 1/6 palce
   
px pixely
   
ex výška písmena „x“
em výška textového řádku (nepřesně šířka písmena „M“)

Rozměry výkresu se uplatní při jeho umístění na HTML stránku, při tisku, vkládání do vytvářených publikací apod. Další důležitou informací související s použitými délkovými jednotkami je takzvaný pohledový box neboli viewbox (neplést s viewportem). Pohledový box určuje výřez z celé kreslicí plochy a současně i způsob přepočtu bezrozměrných jednotek použitých v jednotlivých 2D entitách na fyzické délkové jednotky popř. na relativní jednotky ve chvíli, kdy je šířka a výška specifikována v % obsazené plochy stránky. Specifikován je čtyřmi hodnotami minx, miny, width a height.

Pokud například zadáme:

<svg width="10cm"
        height="10cm"
        viewBox="0 0 100 100">

znamená to, že fyzické rozměry výkresu budou nastaveny na 10×10 cm a na této ploše bude rozprostřena souřadná síť s rozsahem x od 0 do 100 (bezrozměrných jednotek) a s rozsahem y také od 0 do 100. Interně se při specifikaci pohledového boxu vytvoří transformační matice, která je aplikována na všechny souřadnice.

8. Čtvrtý demonstrační příklad – nastavení velikosti a pohledového boxu výkresu

Ve čtvrtém demonstračním příkladu vytvoříme tři výkresy, které sice budou mít stejný obsah (bílý čtverec), ovšem rozdílné fyzické rozměry. Vykreslení čtverce je snadné:

    # začátek kreslení
    painter.begin(generator)
 
    drawRectangle(painter, WHITE, 1, 1, VBOX_WIDTH-1, VBOX_HEIGHT-1)
 
    # konec kreslení
    painter.end()

Funkce nazvaná drawRectangle byla beze změn převzata z předchozích demonstračních příkladů, v nichž jsme prováděli vykreslování do GUI (a nikoli do SVG):

def drawRectangle(qPainter, color, x, y, width, height):
    setColor(qPainter, color)
 
    # vykreslení obdélníku
    qPainter.drawRect(x, y, width, height)

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

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from PySide import QtCore, QtGui, QtSvg
 
 
# konstanty s n-ticemi představujícími základní barvy
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)
CYAN = (0, 255, 255)
GREEN = (0, 255, 0)
YELLOW = (255, 255, 0)
RED = (255, 0, 0)
MAGENTA = (255, 0, 255)
WHITE = (255, 255, 255)
 
 
# nastavení barvy kreslení (pera) na zadanou barvu
def setColor(qPainter, color):
    # vytvoření pera a nastavení barvy kreslení
    pen = QtGui.QPen(QtGui.QColor(*color))
 
    # kreslit se bude právě vytvořeným perem
    qPainter.setPen(pen)
 
 
# funkce pro vykreslení obdélníku zadanou barvou
def drawRectangle(qPainter, color, x, y, width, height):
    setColor(qPainter, color)
 
    # vykreslení obdélníku
    qPainter.drawRect(x, y, width, height)
 
 
def create_svg(name, width, height):
    VBOX_WIDTH = 100
    VBOX_HEIGHT = 100
 
    # vytvoření instance třídy QSvgGenerator
    generator = QtSvg.QSvgGenerator()
 
    # určení typu výstupu a nastavení
    # jména výsledného souboru
    generator.setFileName(name)
 
    # specifikace rozměrů SVG obrázku
    generator.setSize(QtCore.QSize(width, height))
 
    # viditelný výřez
    generator.setViewBox(QtCore.QRect(0, 0, VBOX_WIDTH, VBOX_HEIGHT))
 
    # nastavení titulku SVG obrázku
    generator.setTitle("SVG: test 4")
 
    # popis SVG obrázku
    generator.setDescription("fourth SVG example")
 
    # inicializace instance třídy QPainter
    painter = QtGui.QPainter()
 
    # začátek kreslení
    painter.begin(generator)
 
    drawRectangle(painter, WHITE, 1, 1, VBOX_WIDTH-1, VBOX_HEIGHT-1)
 
    # konec kreslení
    painter.end()
 
 
def main():
    create_svg("test4.svg", 10, 10)
    create_svg("test5.svg", 100, 100)
    create_svg("test6.svg", 1000, 1000)
 
 
if __name__ == '__main__':
    main()

9. Výsledky vygenerované čtvrtým příkladem

Podívejme se nyní, jak vypadají SVG soubory vygenerované čtvrtým demonstračním příkladem. Samotné vykreslení čtverce vypadá vždy stejně:

<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M1,1 L100,1 L100,100 L1,100 L1,1"/>

Jedná se o deklaraci takzvané cesty („tahy štětcem“). Každá cesta je představována značkou <path>, která může mít nastaveno velké množství atributů ovlivňujících tvar, styl či animaci cesty. Nejdůležitější atributy této značky jsou vypsány v následující tabulce:

Atribut Význam
d vlastní geometrie cesty, bude podrobněji vysvětleno dále
class třída, do které cesta spadá, může být použito například jako selektor pro styl
style styl, kterým má být cesta vykreslena
id identifikace cesty, může být použito například při programově řízené animaci či při změně vlastností cesty pomocí DOM
transform lineární transformace aplikovaná na všechny specifikované vrcholy

Hodnota atributu d (od data či definition), která je představována řetězcem obsahujícím znaky se speciálním významem a numerické údaje, má velký význam, protože je pomocí ní zapsána celá geometrie cesty, tj. koncové body úsečkových segmentů, parametry Bézierových kvadratických i kubických křivek, parametry eliptických oblouků atd. Všechny údaje, které se vztahují k souřadnicím, jsou zapisovány pomocí absolutního či relativního pohybu grafického kurzoru.

Jednotlivé výkresy sice používají shodné cesty pro vytvoření čtverce, ovšem liší se v nastavení fyzických rozměrů (šířky a výšky).

První varianta:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="3.52778mm" height="3.52778mm"
 viewBox="0 0 100 100"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  version="1.2" baseProfile="tiny">
<title>SVG: test 4</title>
<desc>fourth SVG example</desc>
<defs>
</defs>
<g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" >
 
<g fill="none" stroke="#ffffff" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
font-family="Helvetica" font-size="12" font-weight="400" font-style="normal"
>
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M1,1 L100,1 L100,100 L1,100 L1,1"/>
</g>
</g>
</svg>

Druhá varianta:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="35.2778mm" height="35.2778mm"
 viewBox="0 0 100 100"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  version="1.2" baseProfile="tiny">
<title>SVG: test 4</title>
<desc>fourth SVG example</desc>
<defs>
</defs>
<g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" >
 
<g fill="none" stroke="#ffffff" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
font-family="Helvetica" font-size="12" font-weight="400" font-style="normal"
>
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M1,1 L100,1 L100,100 L1,100 L1,1"/>
</g>
</g>
</svg>

Třetí varianta:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="352.778mm" height="352.778mm"
 viewBox="0 0 100 100"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  version="1.2" baseProfile="tiny">
<title>SVG: test 4</title>
<desc>fourth SVG example</desc>
<defs>
</defs>
<g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" >
 
<g fill="none" stroke="#ffffff" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
font-family="Helvetica" font-size="12" font-weight="400" font-style="normal"
>
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M1,1 L100,1 L100,100 L1,100 L1,1"/>
</g>
</g>
</svg>

10. Pátý příklad – vykreslení základních 2D entit do výkresu

Do souborů typu SVG je možné ukládat značky/tagy reprezentující šest typů základních geometrických tvarů. Mezi tyto tvary patří úsečka (line), obdélník (rectangle), kružnice (circle), elipsa (ellipse), polyčára (polyline, lomená čára) a polygon (polygon). Všechny tyto tvary je možné nahradit vhodně zapsanou cestou, ale z různých důvodů (dodatečné informace pro grafické editory či další aplikace, jednodušší zápis i čtení pomocí SAX i DOM) se můžeme často setkat i s přímým zápisem základních geometrických tvarů. Definice tvarů pomocí cesty však většinou bývá kompaktnější, což vede k menšímu objemu výsledného souboru (ovšem tento rozdíl se viditelněji projeví až při velkých objemech dat, tedy při složitějších výkresech).

Zkusme nyní zjistit, jestli dvojice tříd QSvgGenerator + QPainter používá cesty nebo základní geometrické tvary. Způsob generování SVG vyzkoušíme na příkladu, v němž vykreslíme různé typy 2D entit. Výsledek by měl vypadat zhruba následovně (záleží na použité prohlížečce):

Obrázek 2: Výkres vytvořený dalším demonstračním příkladem.

Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně. Zdrojový kód je sice poněkud delší, ale není příliš složitý (pouze obsahuje pomocné funkce pro vykreslení různých typů 2D entit):

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from PySide import QtCore, QtGui, QtSvg
 
 
# nastavení barvy kreslení (pera) na zadanou barvu
def setColor(qPainter, color):
    # vytvoření pera a nastavení barvy kreslení
    pen = QtGui.QPen(QtGui.QColor(*color))
 
    # kreslit se bude právě vytvořeným perem
    qPainter.setPen(pen)
 
 
# funkce pro vykreslení úsečky zadanou barvou
def drawLine(qPainter, color, x1, y1, x2, y2):
    setColor(qPainter, color)
 
    # vykreslení úsečky
    qPainter.drawLine(x1, y1, x2, y2)
 
 
# funkce pro vykreslení obdélníku zadanou barvou
def drawRectangle(qPainter, color, x, y, width, height):
    setColor(qPainter, color)
 
    # vykreslení obdélníku
    qPainter.drawRect(x, y, width, height)
 
 
# funkce pro vykreslení obdélníku zadanou barvou a se zaoblenými rohy
def drawRoundedRectangle(qPainter, color, x, y, width, height, r):
    setColor(qPainter, color)
 
    # vykreslení obdélníku
    qPainter.drawRoundedRect(x, y, width, height, r, r)
 
 
# funkce pro vykreslení elipsy zadanou barvou
def drawEllipse(qPainter, color, x, y, width, height):
    # vytvoření pera a nastavení barvy kreslení
    pen = QtGui.QPen(QtGui.QColor(*color))
    qPainter.setPen(pen)
 
    # vykreslení elipsy
    qPainter.drawEllipse(x, y, width, height)
 
 
# funkce pro vykreslení kružnice zadanou barvou
def drawCircle(qPainter, color, cx, cy, radius):
    setColor(qPainter, color)
 
    # vykreslení kružnice
    qPainter.drawEllipse(cx-radius, cy-radius, 2*radius, 2*radius)
 
 
# funkce pro vykreslení oblouku zadanou barvou
def drawArc(qPainter, color, cx, cy, radius, angle, span):
    setColor(qPainter, color)
 
    # vykreslení kružnice
    qPainter.drawArc(cx-radius, cy-radius, 2*radius, 2*radius, 16*angle, 16*span)
 
 
# funkce pro vykreslení kruhové výseče zadanou barvou
def drawPie(qPainter, color, cx, cy, radius, angle, span):
    setColor(qPainter, color)
 
    # vykreslení kruhové výseče
    qPainter.drawPie(cx-radius, cy-radius, 2*radius, 2*radius, 16*angle, 16*span)
 
 
# funkce pro vykreslení kruhové úseče zadanou barvou
def drawChord(qPainter, color, cx, cy, radius, angle, span):
    setColor(qPainter, color)
 
    # vykreslení kruhové úseče
    qPainter.drawChord(cx-radius, cy-radius, 2*radius, 2*radius, 16*angle, 16*span)
 
 
def drawScene(painter):
    # konstanty s n-ticemi představujícími základní barvy
    BLACK = (0, 0, 0)
    BLUE = (0, 0, 255)
    CYAN = (0, 255, 255)
    GREEN = (0, 255, 0)
    YELLOW = (255, 255, 0)
    RED = (255, 0, 0)
    MAGENTA = (255, 0, 255)
    WHITE = (255, 255, 255)
 
    # okraje
    drawRectangle(painter, WHITE, 0, 0, 330, 520)
 
    # Vykreslení různých 2D entit
    drawLine(painter, GREEN, 10, 10, 80, 80)
 
    drawRectangle(painter, YELLOW, 10, 90, 70, 70)
    drawCircle(painter, RED, 125, 125, 35)
    drawEllipse(painter, CYAN, 170, 30+80, 70, 35)
    drawEllipse(painter, BLUE, 268, 10+80, 35, 70)
 
    drawRoundedRectangle(painter, MAGENTA, 10, 170, 70, 70, 1)
    drawRoundedRectangle(painter, MAGENTA, 90, 170, 70, 70, 10)
    drawRoundedRectangle(painter, MAGENTA, 170, 170, 70, 70, 20)
    drawRoundedRectangle(painter, MAGENTA, 250, 170, 70, 70, 1000)
 
    drawArc(painter, CYAN, 10+35, 260+35, 35, 0, 90)
    drawArc(painter, CYAN, 90+35, 260+35, 35, 45, 90)
    drawArc(painter, CYAN, 170+35, 260+35, 35, 45, 180)
    drawArc(painter, CYAN, 250+35, 260+35, 35, 45, 270)
 
    drawPie(painter, YELLOW, 10+35, 350+35, 35, 0, 90)
    drawPie(painter, YELLOW, 90+35, 350+35, 35, 45, 90)
    drawPie(painter, YELLOW, 170+35, 350+35, 35, 45, 180)
    drawPie(painter, YELLOW, 250+35, 350+35, 35, 45, 270)
 
    drawChord(painter, GREEN, 10+35, 440+35, 35, 0, 90)
    drawChord(painter, GREEN, 90+35, 440+35, 35, 45, 90)
    drawChord(painter, GREEN, 170+35, 440+35, 35, 45, 180)
    drawChord(painter, GREEN, 250+35, 440+35, 35, 45, 270)
 
 
def create_svg(name, width, height):
    # vytvoření instance třídy QSvgGenerator
    generator = QtSvg.QSvgGenerator()
 
    # určení typu výstupu a nastavení
    # jména výsledného souboru
    generator.setFileName(name)
 
    # specifikace rozměrů SVG obrázku
    generator.setSize(QtCore.QSize(width, height))
 
    # viditelný výřez
    generator.setViewBox(QtCore.QRect(0, 0, 330, 520))
 
    # nastavení titulku SVG obrázku
    generator.setTitle("SVG: test 7")
 
    # popis SVG obrázku
    generator.setDescription("fifth SVG example")
 
    # inicializace instance třídy QPainter
    painter = QtGui.QPainter()
 
    # začátek kreslení
    painter.begin(generator)
 
    drawScene(painter)
 
    # konec kreslení
    painter.end()
 
 
def main():
    create_svg("test7.svg", 320, 320)
 
 
if __name__ == '__main__':
    main()

11. Výsledek vygenerovaný pátým příkladem

SVG soubor vytvořený pátým příkladem je již poněkud rozsáhlý, ovšem nás bude zajímat jen způsob zápisu 2D entit, takže si uvedeme pouze vybrané části.

Úsečka je kupodivu zapsána jako polyčára se dvěma vrcholy:

<polyline fill="none" vector-effect="non-scaling-stroke"
    points="10,10 80,80 " />

Obdélník/čtverec je reprezentován cestou (bez příkazu pro její uzavření):

<path vector-effect="non-scaling-stroke" fill-rule="evenodd"
    d="M10,90 L80,90 L80,160 L10,160 L10,90"/>

Kružnice je zapsána formou čtyř oblouků:

<path vector-effect="non-scaling-stroke" fill-rule="evenodd"
    d="M160,125 C160,144.33 144.33,160 125,160 C105.67,160 90,144.33 90,125 C90,105.67 105.67,90 125,90 C144.33,90 160,105.67 160,125 "/>

Elipsa taktéž používá čtyři oblouky:

<path vector-effect="non-scaling-stroke" fill-rule="evenodd"
    d="M240,127.5 C240,137.165 224.33,145 205,145 C185.67,145 170,137.165 170,127.5 C170,117.835 185.67,110 205,110 C224.33,110 240,117.835 240,127.5 "/>

Obdélník/čtverec se zaoblenými rohy je opět reprezentován cestou:

<path vector-effect="non-scaling-stroke" fill-rule="evenodd"
    d="M10,171 C10,170.448 10.4477,170 11,170 L79,170 C79.5523,170 80,170.448 80,171 L80,239 C80,239.552 79.5523,240 79,240 L11,240 C10.4477,240 10,239.552 10,239 L10,171"/>

Kruhový oblouk je nutné zapsat jako cestu:

<path vector-effect="non-scaling-stroke" fill-rule="evenodd"
    d="M80,295 C80,275.67 64.33,260 45,260 "/>

Kruhová výseč a kruhová úseč:

<path vector-effect="non-scaling-stroke" fill-rule="evenodd"
    d="M45,385 L80,385 C80,365.67 64.33,350 45,350 L45,385"/>
<path vector-effect="non-scaling-stroke" fill-rule="evenodd"
    d="M80,475 C80,455.67 64.33,440 45,440 L80,475"/>

Vysvětlení jednotlivých jednoznakových příkazů v definicích cesty:

Příkaz Popis
M absolutní pohyb bez kreslení
m relativní pohyb bez kreslení
L absolutní pohyb na souřadnice s kreslením úsečkového segmentu
l relativní pohyb o souřadnice s kreslením úsečkového segmentu
H horizontální posun (vykreslení vodorovné úsečky)
h relativní posun v horizontálním směru (vykreslení vodorovné úsečky)
V vertikální posun (vykreslení svislé úsečky)
v relativní posun (vykreslení svislé úsečky)
Z uzavření cesty úsečkovým segmentem
z má stejný význam jako příkaz Z
Q kvadratická Bézierova křivka zadaná trojicí řídicích bodů
q stejný význam jako Q s tím rozdílem, že souřadnice řídicích bodů jsou zadány relativně
T hladce navazující kvadratická Bézierova křivka
t stejný význam jako T s tím rozdílem, že souřadnice koncového bodu kvadratické křivky jsou zadány relativně
C kubická Bézierova křivka zadaná čtyřmi řídicími body
c stejný význam jako C s tím rozdílem, že souřadnice řídicích bodů jsou zadány relativně
S podobný příkazu T, ovšem s tím rozdílem, že se vytvoří kubická Bézierova křivka
s podobné příkazu S, ale všechny souřadnice řídicích bodů jsou zadány relativně
A eliptický oblouk zadaný absolutními souřadnicemi
a eliptický oblouk zadaný relativními souřadnicemi

12. Podpora rastrových operací prováděných při vykreslování 2D entit do SVG

Z předchozích částí tohoto seriálu již víme, že při vykreslování (přesněji řečeno rasterizaci) 2D entit na plátno typu QImage můžeme aplikovat různé operace prováděné nad jednotlivými pixely. Při vykreslování se totiž postupuje zhruba následujícím způsobem:

  1. Vrcholy popř. řídicí body dvourozměrných entit jsou podrobeny vybrané lineární transformaci (zvětšení, zmenšení, otočení, zkosení, posun atd.).
  2. Zjistí se, zda je entita vůbec viditelná, tj. zda transformované vrcholy leží ve viditelné oblasti.
  3. Dále se provede takzvaná rasterizace, tj. výpočet barev pixelů ležících na hranici entity (pero) a popř. i v ploše, kterou entita tvoří (štětec).
  4. V průběhu rasterizace se s každým vypočteným pixelem provádí další operace, což je téma dnešního článku.

Mezi prováděné rastrové operace patří:

  1. Test vůči masce, zda se má pixel vykreslit. Maska může být představována bitmapou, tj. její funkce odpovídá jedné konfigurovatelné vlastnostistencil bufferu v OpenGL.
  2. Dále se zkombinuje původní barva pixelu na plátně s barvou vypočtenou. Kombinací těchto dvou barev vznikne barva třetí, která je na plátno zapsána. Výchozí operací je pouhé přepsání staré barvy, ovšem je možné zvolit i jiné metody.

Problém spočívá v tom, že rasterizaci vektorového výkresu SVG neřídí framework PySide, ale obecně libovolná SVG prohlížečka, která může být zabudovaná například do webového browseru apod. Sice je možné při vykreslování aplikovat několik filtrů, ale tyto filtry neodpovídají klasickým rastrovým operacím. Proto bude zajímavé zjistit, co se vlastně stane, pokud vezmeme skripty použité pro vykreslení následujících obrázků a použijeme ho pro vygenerování SVG:

Obrázek 3: Vzorník základních rastrových operací.

Obrázek 4: Vzorník dalších rastrových operací.

Obrázek 5: Modré čtverce mají nastavenou poloviční průhlednost (vykresleno variantou čtvrtého příkladu).

Obrázek 6: Modré i zelené čtverce mají nastavenou poloviční průhlednost (vykresleno variantou čtvrtého příkladu).

13. Šestý demonstrační příklad – klasické rastrové operace

V šestém příkladu jsou použity základní rastrové operace podobné těm, které jsou zobrazeny na obrázku 3 a 4. Ovšem nyní provedeme export do SVG, nikoli vykreslení do rastrového obrázku (nad čímž má framework PySide mnohem větší kontrolu):

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from PySide import QtCore, QtGui, QtSvg
 
 
# nastavení barvy kreslení (pera) na zadanou barvu
def setColor(qPainter, color):
    # vytvoření pera a nastavení barvy kreslení
    pen = QtGui.QPen(QtGui.QColor(*color))
 
    # kreslit se bude právě vytvořeným perem
    qPainter.setPen(pen)
 
 
# funkce pro vykreslení obdélníku zadanou barvou
def drawRectangle(qPainter, color, x, y, width, height):
    setColor(qPainter, color)
 
    # vykreslení obdélníku
    qPainter.drawRect(x, y, width, height)
 
 
# funkce pro vykreslení obdélníku zadanou barvou a se specifikovaným štětcem
def drawRectangleUsingBrush(qPainter, color, x, y, width, height, brush_style,
                            pen_width=0):
    # vytvoření pera a nastavení barvy kreslení
    pen = QtGui.QPen(QtGui.QColor(*color))
 
    # změna šířky pera
    pen.setWidth(pen_width)
    qPainter.setPen(pen)
 
    # změna tvaru štětce
    brush = QtGui.QBrush(QtGui.QColor(*color))
    brush.setStyle(brush_style)
    qPainter.setBrush(brush)
 
    # vykreslení obdélníku
    qPainter.drawRect(x, y, width, height)
 
 
def twoOverlappingSquares(qPainter, color1, color2, x, y, compositionMode):
    # nastavení výchozího režimu míchání barev
    qPainter.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver)
 
    # první čtverec
    drawRectangleUsingBrush(qPainter, color1, x, y, 100, 100,
                            QtCore.Qt.SolidPattern)
 
    # nastavení režimu míchání barev
    qPainter.setCompositionMode(compositionMode)
 
    # druhý čtverec
    drawRectangleUsingBrush(qPainter, color2, x+50, y+50, 100, 100,
                            QtCore.Qt.SolidPattern)
 
 
def drawScene(painter, width, height):
    # konstanty s n-ticemi představujícími základní barvy
    BLACK = (0, 0, 0)
    BLUE = (0, 0, 255)
    CYAN = (0, 255, 255)
    GREEN = (0, 255, 0)
    YELLOW = (255, 255, 0)
    RED = (255, 0, 0)
    MAGENTA = (255, 0, 255)
    WHITE = (255, 255, 255)
 
    # okraje
    drawRectangle(painter, WHITE, 0, 0, width, height)
 
    BLUE_50_ALPHA = (0, 0, 255, 128)
    GREEN_50_ALPHA = (0, 255, 0, 128)
 
    # umístění čtverců na kreslicí ploše
    HORIZONTAL_DISTANCE = 200
    VERTICAL_DISTANCE = 200
 
    COLUMN_1 = 10
    COLUMN_2 = COLUMN_1 + HORIZONTAL_DISTANCE
    COLUMN_3 = COLUMN_2 + HORIZONTAL_DISTANCE
 
    ROW_1 = 10
    ROW_2 = ROW_1 + HORIZONTAL_DISTANCE
    ROW_3 = ROW_2 + HORIZONTAL_DISTANCE
    ROW_4 = ROW_3 + HORIZONTAL_DISTANCE
 
    # vykreslení sady překrývajících se čtverců
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_1,
                          QtGui.QPainter.CompositionMode_SourceOver)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_2,
                          QtGui.QPainter.CompositionMode_Source)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_2,
                          QtGui.QPainter.CompositionMode_DestinationIn)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_3, ROW_2,
                          QtGui.QPainter.CompositionMode_SourceOut)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_3,
                          QtGui.QPainter.CompositionMode_Xor)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_3,
                          QtGui.QPainter.CompositionMode_Plus)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_3, ROW_3,
                          QtGui.QPainter.CompositionMode_Screen)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_4,
                          QtGui.QPainter.CompositionMode_HardLight)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_4,
                          QtGui.QPainter.CompositionMode_SoftLight)
 
    twoOverlappingSquares(painter, WHITE, BLUE_50_ALPHA, COLUMN_3, ROW_4,
                          QtGui.QPainter.CompositionMode_ColorBurn)
 
 
def create_svg(name, width, height):
    VBOX_WIDTH = 600
    VBOX_HEIGHT = 800
    # vytvoření instance třídy QSvgGenerator
    generator = QtSvg.QSvgGenerator()
 
    # určení typu výstupu a nastavení
    # jména výsledného souboru
    generator.setFileName(name)
 
    # specifikace rozměrů SVG obrázku
    generator.setSize(QtCore.QSize(width, height))
 
    # viditelný výřez
    generator.setViewBox(QtCore.QRect(0, 0, VBOX_WIDTH, VBOX_HEIGHT))
 
    # nastavení titulku SVG obrázku
    generator.setTitle("SVG: test 8")
 
    # popis SVG obrázku
    generator.setDescription("sixth SVG example")
 
    # inicializace instance třídy QPainter
    painter = QtGui.QPainter()
 
    # začátek kreslení
    painter.begin(generator)
 
    drawScene(painter, VBOX_WIDTH, VBOX_HEIGHT)
 
    # konec kreslení
    painter.end()
 
 
def main():
    create_svg("test8.svg", 320, 320)
 
 
if __name__ == '__main__':
    main()

14. Sedmý demonstrační příklad – rastrové operace využívající alfa kanál

Příklad sedmý by měl vytvořit obrázky podobné těm ze screenshotu 5 či 6, ovšem opět s omezením na možnosti SVG:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
from PySide import QtCore, QtGui, QtSvg
 
 
# nastavení barvy kreslení (pera) na zadanou barvu
def setColor(qPainter, color):
    # vytvoření pera a nastavení barvy kreslení
    pen = QtGui.QPen(QtGui.QColor(*color))
 
    # kreslit se bude právě vytvořeným perem
    qPainter.setPen(pen)
 
 
# funkce pro vykreslení obdélníku zadanou barvou
def drawRectangle(qPainter, color, x, y, width, height):
    setColor(qPainter, color)
 
    # vykreslení obdélníku
    qPainter.drawRect(x, y, width, height)
 
 
# funkce pro vykreslení obdélníku zadanou barvou a se specifikovaným štětcem
def drawRectangleUsingBrush(qPainter, color, x, y, width, height, brush_style,
                            pen_width=0):
    # vytvoření pera a nastavení barvy kreslení
    pen = QtGui.QPen(QtGui.QColor(*color))
 
    # změna šířky pera
    pen.setWidth(pen_width)
    qPainter.setPen(pen)
 
    # změna tvaru štětce
    brush = QtGui.QBrush(QtGui.QColor(*color))
    brush.setStyle(brush_style)
    qPainter.setBrush(brush)
 
    # vykreslení obdélníku
    qPainter.drawRect(x, y, width, height)
 
 
def twoOverlappingSquares(qPainter, color1, color2, x, y, compositionMode):
    # nastavení výchozího režimu míchání barev
    qPainter.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver)
 
    # první čtverec
    drawRectangleUsingBrush(qPainter, color1, x, y, 100, 100,
                            QtCore.Qt.SolidPattern)
 
    # nastavení režimu míchání barev
    qPainter.setCompositionMode(compositionMode)
 
    # druhý čtverec
    drawRectangleUsingBrush(qPainter, color2, x+50, y+50, 100, 100,
                            QtCore.Qt.SolidPattern)
 
 
def drawScene(painter, width, height):
    # konstanty s n-ticemi představujícími základní barvy
    BLACK = (0, 0, 0)
    BLUE = (0, 0, 255)
    CYAN = (0, 255, 255)
    GREEN = (0, 255, 0)
    YELLOW = (255, 255, 0)
    RED = (255, 0, 0)
    MAGENTA = (255, 0, 255)
    WHITE = (255, 255, 255)
 
    # okraje
    drawRectangle(painter, WHITE, 0, 0, width, height)
 
    BLUE_50_ALPHA = (0, 0, 255, 128)
    GREEN_50_ALPHA = (0, 255, 0, 128)
 
    # umístění čtverců na kreslicí ploše
    HORIZONTAL_DISTANCE = 200
    VERTICAL_DISTANCE = 200
 
    COLUMN_1 = 10
    COLUMN_2 = COLUMN_1 + HORIZONTAL_DISTANCE
    COLUMN_3 = COLUMN_2 + HORIZONTAL_DISTANCE
 
    ROW_1 = 10
    ROW_2 = ROW_1 + HORIZONTAL_DISTANCE
    ROW_3 = ROW_2 + HORIZONTAL_DISTANCE
    ROW_4 = ROW_3 + HORIZONTAL_DISTANCE
 
    # vykreslení sady překrývajících se čtverců
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_1,
                          QtGui.QPainter.CompositionMode_SourceOver)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_2,
                          QtGui.QPainter.RasterOp_SourceOrDestination)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_2,
                          QtGui.QPainter.RasterOp_SourceAndDestination)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_3, ROW_2,
                          QtGui.QPainter.RasterOp_SourceXorDestination)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_3,
                          QtGui.QPainter.RasterOp_NotSourceAndNotDestination)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_3,
                          QtGui.QPainter.RasterOp_NotSourceOrNotDestination)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_3, ROW_3,
                          QtGui.QPainter.RasterOp_NotSourceXorDestination)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_4,
                          QtGui.QPainter.RasterOp_NotSource)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_4,
                          QtGui.QPainter.RasterOp_NotSourceAndDestination)
 
    twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_3, ROW_4,
                          QtGui.QPainter.RasterOp_SourceAndNotDestination)
 
 
def create_svg(name, width, height):
    VBOX_WIDTH = 600
    VBOX_HEIGHT = 800
    # vytvoření instance třídy QSvgGenerator
    generator = QtSvg.QSvgGenerator()
 
    # určení typu výstupu a nastavení
    # jména výsledného souboru
    generator.setFileName(name)
 
    # specifikace rozměrů SVG obrázku
    generator.setSize(QtCore.QSize(width, height))
 
    # viditelný výřez
    generator.setViewBox(QtCore.QRect(0, 0, VBOX_WIDTH, VBOX_HEIGHT))
 
    # nastavení titulku SVG obrázku
    generator.setTitle("SVG: test 9")
 
    # popis SVG obrázku
    generator.setDescription("seventh SVG example")
 
    # inicializace instance třídy QPainter
    painter = QtGui.QPainter()
 
    # začátek kreslení
    painter.begin(generator)
 
    drawScene(painter, VBOX_WIDTH, VBOX_HEIGHT)
 
    # konec kreslení
    painter.end()
 
 
def main():
    create_svg("test9.svg", 320, 320)
 
 
if __name__ == '__main__':
    main()

15. Výsledky vytvořené šestým a sedmým příkladem

Podívejme se nyní na SVG obrázky vytvořené šestým a sedmým příkladem po jejich zobrazení v prohlížečce (konkrétně je použita prohlížečka gThumb):

Obrázek 7: Výkres vytvořený šestým demonstračním příkladem.

Obrázek 8: Výkres vytvořený sedmým demonstračním příkladem.

Můžeme vidět, že většina rastrových operací byla ignorována. Můžeme se o tom přesvědčit i pohledem do SVG (zde zkráceno), kde je patrné pouze nastavení průhlednosti, ovšem per-pixel operace se neprovádí:

<g fill="#00ff00" fill-opacity="0.501961" stroke="#00ff00" stroke-opacity="0.501961" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
font-family="Helvetica" font-size="12" font-weight="400" font-style="normal"
>
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M10,10 L110,10 L110,110 L10,110 L10,10"/>
</g>
 
<g fill="#0000ff" fill-opacity="0.501961" stroke="#0000ff" stroke-opacity="0.501961" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
font-family="Helvetica" font-size="12" font-weight="400" font-style="normal"
>
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M460,660 L560,660 L560,760 L460,760 L460,660"/>
</g>

Sedmý příklad vygenerovat tento SVG (opět zkrácený):

<g fill="none" stroke="#ffffff" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
font-family="Helvetica" font-size="12" font-weight="400" font-style="normal"
>
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M0,0 L600,0 L600,800 L0,800 L0,0"/>
</g>
 
<g fill="#00ff00" fill-opacity="0.501961" stroke="#00ff00" stroke-opacity="0.501961" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
font-family="Helvetica" font-size="12" font-weight="400" font-style="normal"
>
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M10,10 L110,10 L110,110 L10,110 L10,10"/>
</g>

16. Osmý demonstrační příklad – použití rastrového obrázku v SVG

V osmém a současně i dnešním posledním demonstračním příkladu je ukázáno, jak se do vektorového výkresu může vložit rastrový obrázek (se všemi důsledky, které to s sebou nese). Především je nutné provést inicializaci třídy QApplication. Pokud inicializaci neprovedeme, dojde k pádu aplikace(!):

app = QtGui.QApplication(sys.argv)

Dále již můžeme rastrový obrázek načíst (bude reprezentován objektem typu QImage) a vykreslit metodou QPainter.drawImage():

def drawScene(painter, width, height):
    # konstanty s n-ticemi představujícími základní barvy
    WHITE = (255, 255, 255)
 
    # okraje
    drawRectangle(painter, WHITE, 0, 0, width, height)
 
    image = QtGui.QImage("pixmaps/pysidelogo.png")
 
    rect1 = QtCore.QRectF(10, 10, 199, 102)
    painter.drawImage(rect1, image)
 
    rect2 = QtCore.QRectF(10, 120, 199*3, 102*3)
    painter.drawImage(rect2, image)
Poznámka: do výkresu vkládáme dva obrázky, které sice mají stejný zdroj, ovšem jejich rozměry budou odlišné. Povšimněte si, že rozměry jsou specifikovány pomocí QRectF (x, y, šířka, výška).

Obrázek 9: Výkres vytvořený osmým demonstračním příkladem.

Jak je již zvykem, opět následuje výpis úplného zdrojového kódu tohoto příkladu:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
from PySide import QtCore, QtGui, QtSvg
 
 
# nastavení barvy kreslení (pera) na zadanou barvu
def setColor(qPainter, color):
    # vytvoření pera a nastavení barvy kreslení
    pen = QtGui.QPen(QtGui.QColor(*color))
 
    # kreslit se bude právě vytvořeným perem
    qPainter.setPen(pen)
 
 
# funkce pro vykreslení obdélníku zadanou barvou
def drawRectangle(qPainter, color, x, y, width, height):
    setColor(qPainter, color)
 
    # vykreslení obdélníku
    qPainter.drawRect(x, y, width, height)
 
 
def drawScene(painter, width, height):
    # konstanty s n-ticemi představujícími základní barvy
    WHITE = (255, 255, 255)
 
    # okraje
    drawRectangle(painter, WHITE, 0, 0, width, height)
 
    image = QtGui.QImage("pixmaps/pysidelogo.png")
 
    rect1 = QtCore.QRectF(10, 10, 199, 102)
    painter.drawImage(rect1, image)
 
    rect2 = QtCore.QRectF(10, 120, 199*3, 102*3)
    painter.drawImage(rect2, image)
 
 
def create_svg(name, width, height):
    VBOX_WIDTH = 600
    VBOX_HEIGHT = 440
 
    # vytvoření instance třídy QSvgGenerator
    generator = QtSvg.QSvgGenerator()
 
    # určení typu výstupu a nastavení
    # jména výsledného souboru
    generator.setFileName(name)
 
    # specifikace rozměrů SVG obrázku
    generator.setSize(QtCore.QSize(width, height))
 
    # viditelný výřez
    generator.setViewBox(QtCore.QRect(0, 0, VBOX_WIDTH, VBOX_HEIGHT))
 
    # nastavení titulku SVG obrázku
    generator.setTitle("SVG: test 10")
 
    # popis SVG obrázku
    generator.setDescription("eight SVG example")
 
    # inicializace instance třídy QPainter
    painter = QtGui.QPainter()
 
    # začátek kreslení
    painter.begin(generator)
 
    drawScene(painter, VBOX_WIDTH, VBOX_HEIGHT)
 
    # konec kreslení
    painter.end()
 
 
def main():
    create_svg("test10.svg", 320, 320)
 
 
if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    main()

17. Výsledek vygenerovaný osmým demonstračním příkladem

Zajímavé bude zjistit, jak se vlastně rastrový obrázek uloží do souboru typu SVG, který je založen na XML a tudíž i na běžném (čitelném) textovém formátu:

<image x="10"
       y="10"
       width="199"
       height="102"
       preserveAspectRatio="none"
       xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUg
       AAAMcAAABmCAYAAAB/RYWNAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAA
       ...
       ...
       ...
       AOxAAADsQBlSsOGABJRU5ErkJggg==" />

Můžeme vidět, že data rastrového obrázku jsou uložena přímo do SVG (XML), ovšem aby byla zachována jeho textová podstata, bylo nutné pro zakódování binárních dat použít kódování Base64. Nevýhodou je, že oproti binárním datům se velikost zvýší o více než 33%, protože vždy tři původní bajty jsou zakódovány do čtyř znaků a na konci je většinou nutné použít výplň tvořenou znaky =. Dále stojí za povšimnutí, že ve výsledném SVG je celý řetězec zapsán na jediném řádku, i když se běžně v Base64 používají relativně krátké řádky s 64 či 76 znaky (znak pro konec řádku nemá žádný význam a je čtečkami ignorován).

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

Zdrojové kódy všech osmi dnes popsaných demonstračních příkladů společně s vygenerovanými výkresy SVG byly, podobně jako tomu bylo i v předchozích článcích, uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/pre­sentations. Pokud nechcete klonovat celý repositář, můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

Výkresy ve formátu SVG, které byly vytvořeny dnešními demonstračními příklady:

19. Články o možnostech a vlastnostech formátu SVG

  1. Vektorový grafický formát SVG
    http://www.root.cz/clanky/vektorovy-graficky-format-svg/
  2. Cesty v souborech typu Scalable Vector Graphics
    http://www.root.cz/clanky/cesty-v-souborech-typu-scalable-vector-graphics/
  3. Scalable Vector Graphics a základní geometrické tvary
    http://www.root.cz/clanky/scalable-vector-graphics-a-zakladni-geometricke-tvary/
  4. Vlastnosti cest a základních geometrických tvarů v SVG
    http://www.root.cz/clanky/vlastnosti-cest-a-zakladnich-geometrickych-tvaru-v-svg/
  5. SVG – styly výplní a značky připojované ke křivkám
    http://www.root.cz/clanky/svg-styly-vyplni-a-znacky-pripojovane-ke-krivkam/
  6. Gradientní výplně a textové objekty v SVG
    http://www.root.cz/clanky/gradientni-vyplne-a-textove-objekty-v-svg/
  7. Grafický formát SVG a animace
    http://www.root.cz/clanky/graficky-format-svg-a-animace/
  8. Pokročilejší animace ve formátu SVG
    http://www.root.cz/clanky/pokrocilejsi-animace-ve-formatu-svg/
  9. Podpora skriptování v grafickém formátu SVG
    http://www.root.cz/clanky/podpora-skriptovani-v-grafickem-formatu-svg/
  10. Zpracování událostí při skriptování výkresů SVG
    http://www.root.cz/clanky/zpracovani-udalosti-pri-skriptovani-vykresu-svg/

20. Odkazy na Internetu

  1. QSvgWidget
    https://pyside.github.io/doc­s/pyside/PySide/QtSvg/QSvgWid­get.html
  2. QByteArray
    https://pyside.github.io/doc­s/pyside/PySide/QtCore/QBy­teArray.html
  3. Python Bytes, Bytearray
    https://www.w3resource.com/pyt­hon/python-bytes.php
  4. psep-0101.txt (mj. popis mapování typů Pythonu na třídy v PySide)
    https://github.com/techto­nik/pseps/blob/master/psep-0101.txt
  5. QSvgRenderer
    https://pyside.github.io/doc­s/pyside/PySide/QtSvg/QSvgRen­derer.html
  6. QSvgGenerator
    https://pyside.github.io/doc­s/pyside/PySide/QtSvg/QSvgGe­nerator.html
  7. QIcon
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QIcon­.html
  8. PySide 1.2.1 documentation
    https://pyside.github.io/doc­s/pyside/index.html
  9. QStyle
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QSty­le.html
  10. QCommonStyle
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QCom­monStyle.html
  11. QPlastiqueStyle
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QPlas­tiqueStyle.html
  12. QMacStyle
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QMac­Style.html
  13. QCleanlooksStyle
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QCle­anlooksStyle.html
  14. QGtkStyle
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QGtkSty­le.html
  15. QCDEStyle
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QCDES­tyle.html
  16. QMotifStyle
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QMo­tifStyle.html
  17. QWindowsStyle
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QWin­dowsStyle.html
  18. QStyleFactory
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QSty­leFactory.html
  19. QStyleOptionHeader
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QSty­leOptionHeader.html
  20. QAbstractSlider
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/Abstrac­tSlider.html
  21. QScrollBar
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/Scro­llBar.html
  22. QSlider
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/Sli­der.html
  23. QDial
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/Dial­.html
  24. QImage
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QIma­ge.html
  25. QPixmap
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QPix­map.html
  26. QBitmap
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QBit­map.html
  27. QPaintDevice
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QPa­intDevice.html
  28. QPicture
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QPic­ture.html
  29. QPainter
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QPa­inter.html
  30. QPainterPath
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QPa­interPath.html
  31. QGradient
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QGra­dient.html
  32. QLinearGradient
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QLi­nearGradient.html
  33. QRadialGradient
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QRa­dialGradient.html
  34. QTableWidget
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QTa­bleWidget.html
  35. QTableWidgetItem
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QTa­bleWidgetItem.html
  36. QTreeWidget
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QTre­eWidget.html
  37. QTreeWidgetItem
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QTre­eWidgetItem.html
  38. Afinní zobrazení
    https://cs.wikipedia.org/wi­ki/Afinn%C3%AD_zobrazen%C3%AD
  39. Differences Between PySide and PyQt
    https://wiki.qt.io/Differen­ces_Between_PySide_and_PyQt
  40. PySide 1.2.1 tutorials
    https://pyside.github.io/doc­s/pyside/tutorials/index.html
  41. PySide tutorial
    http://zetcode.com/gui/py­sidetutorial/
  42. Drawing in PySide
    http://zetcode.com/gui/py­sidetutorial/drawing/
  43. Qt Core
    https://pyside.github.io/doc­s/pyside/PySide/QtCore/Qt­.html
  44. QLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QLa­yout.html
  45. QValidator
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QVa­lidator.html
  46. QStackedLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QStac­kedLayout.html
  47. QFormLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QFor­mLayout.html
  48. QBoxLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QBox­Layout.html
  49. QHBoxLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QHBox­Layout.html
  50. QVBoxLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QVBox­Layout.html
  51. QGridLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QGrid­Layout.html
  52. QAction
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QAc­tion.html
  53. QDialog
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QDi­alog.html
  54. QMessageBox
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QMes­sageBox.html
  55. QErrorMessage
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QError­Message.html
  56. QInputDialog
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QIn­putDialog.html
  57. QColorDialog
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QCo­lorDialog.html
  58. QListWidget
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QLis­tWidget.html
  59. Signals & Slots
    http://doc.qt.io/qt-4.8/signalsandslots.html
  60. Signals and Slots in PySide
    http://wiki.qt.io/Signals_an­d_Slots_in_PySide
  61. Intro to PySide/PyQt: Basic Widgets and Hello, World!
    http://www.pythoncentral.io/intro-to-pysidepyqt-basic-widgets-and-hello-world/
  62. QLineEdit
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QLi­neEdit.html
  63. QTextEdit
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QTex­tEdit.html
  64. QValidator
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QVa­lidator.html
  65. QIntValidator
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QIn­tValidator.html
  66. QRegExpValidator
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QRe­gExpValidator.html
  67. QWidget
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QWid­get.html
  68. QMainWindow
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QMa­inWindow.html
  69. QLabel
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QLa­bel.html
  70. QAbstractButton
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QAb­stractButton.html
  71. QCheckBox
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QChec­kBox.html
  72. QRadioButton
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QRa­dioButton.html
  73. QButtonGroup
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QBut­tonGroup.html
  74. QFrame
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QFra­me.html#PySide.QtGui.PySi­de.QtGui.QFrame
  75. QFrame.frameStyle
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QFra­me.html#PySide.QtGui.PySi­de.QtGui.QFrame.frameStyle
  76. Leo editor
    http://leoeditor.com/
  77. IPython Qt Console aneb vylepšený pseudoterminál
    https://mojefedora.cz/integrovana-vyvojova-prostredi-ve-fedore-ipython-a-ipython-notebook/#k06
  78. Vývojová prostředí ve Fedoře (4. díl)
    https://mojefedora.cz/vyvojova-prostredi-ve-fedore-4-dil/
  79. Seriál Letní škola programovacího jazyka Logo
    http://www.root.cz/serialy/letni-skola-programovaciho-jazyka-logo/
  80. Educational programming language
    http://en.wikipedia.org/wi­ki/Educational_programmin­g_language
  81. Logo Tree Project:
    http://www.elica.net/downlo­ad/papers/LogoTreeProject­.pdf
  82. Hra Breakout napísaná v Tkinteri
    https://www.root.cz/clanky/hra-breakout-napisana-v-tkinteri/
  83. Hra Snake naprogramovaná v Pythone s pomocou Tkinter
    https://www.root.cz/clanky/hra-snake-naprogramovana-v-pythone-s-pomocou-tkinter/
  84. 24.1. turtle — Turtle graphics
    https://docs.python.org/3­.5/library/turtle.html#mo­dule-turtle
  85. TkDND
    http://freecode.com/projects/tkdnd
  86. Python Tkinter Fonts
    https://www.tutorialspoin­t.com/python/tk_fonts.htm
  87. The Tkinter Canvas Widget
    http://effbot.org/tkinter­book/canvas.htm
  88. Ovládací prvek (Wikipedia)
    https://cs.wikipedia.org/wi­ki/Ovl%C3%A1dac%C3%AD_prvek_­%28po%C4%8D%C3%ADta%C4%8D%29
  89. Rezervovaná klíčová slova v Pythonu
    https://docs.python.org/3/re­ference/lexical_analysis.html#ke­ywords
  90. TkDocs: Styles and Themes
    http://www.tkdocs.com/tuto­rial/styles.html
  91. Drawing in Tkinter
    http://zetcode.com/gui/tkin­ter/drawing/
  92. Changing ttk widget text color (StackOverflow)
    https://stackoverflow.com/qu­estions/16240477/changing-ttk-widget-text-color
  93. The Hitchhiker's Guide to Pyhton: GUI Applications
    http://docs.python-guide.org/en/latest/scenarios/gui/
  94. 7 Top Python GUI Frameworks for 2017
    http://insights.dice.com/2014/11/26/5-top-python-guis-for-2015/
  95. GUI Programming in Python
    https://wiki.python.org/mo­in/GuiProgramming
  96. Cameron Laird's personal notes on Python GUIs
    http://phaseit.net/claird/com­p.lang.python/python_GUI.html
  97. Python GUI development
    http://pythoncentral.io/introduction-python-gui-development/
  98. Graphic User Interface FAQ
    https://docs.python.org/2/faq/gu­i.html#graphic-user-interface-faq
  99. TkInter
    https://wiki.python.org/moin/TkInter
  100. Tkinter 8.5 reference: a GUI for Python
    http://infohost.nmt.edu/tcc/hel­p/pubs/tkinter/web/index.html
  101. TkInter (Wikipedia)
    https://en.wikipedia.org/wiki/Tkinter
  102. appJar
    http://appjar.info/
  103. appJar (Wikipedia)
    https://en.wikipedia.org/wiki/AppJar
  104. appJar na Pythonhosted
    http://pythonhosted.org/appJar/
  105. appJar widgets
    http://appjar.info/pythonWidgets/
  106. Stránky projektu PyGTK
    http://www.pygtk.org/
  107. PyGTK (Wikipedia)
    https://cs.wikipedia.org/wiki/PyGTK
  108. Stránky projektu PyGObject
    https://wiki.gnome.org/Pro­jects/PyGObject
  109. Stránky projektu Kivy
    https://kivy.org/#home
  110. Stránky projektu PyQt
    https://riverbankcomputin­g.com/software/pyqt/intro
  111. PyQt (Wikipedia)
    https://cs.wikipedia.org/wiki/PyGTK
  112. Stránky projektu PySide
    https://wiki.qt.io/PySide
  113. PySide (Wikipedia)
    https://en.wikipedia.org/wiki/PySide
  114. Stránky projektu Kivy
    https://kivy.org/#home
  115. Kivy (framework, Wikipedia)
    https://en.wikipedia.org/wi­ki/Kivy_(framework)
  116. QML Applications
    http://doc.qt.io/qt-5/qmlapplications.html
  117. KDE
    https://www.kde.org/
  118. Qt
    https://www.qt.io/
  119. GNOME
    https://en.wikipedia.org/wiki/GNOME
  120. Category:Software that uses PyGTK
    https://en.wikipedia.org/wi­ki/Category:Software_that_u­ses_PyGTK
  121. Category:Software that uses PyGObject
    https://en.wikipedia.org/wi­ki/Category:Software_that_u­ses_PyGObject
  122. Category:Software that uses wxWidgets
    https://en.wikipedia.org/wi­ki/Category:Software_that_u­ses_wxWidgets
  123. GIO
    https://developer.gnome.or­g/gio/stable/
  124. GStreamer
    https://gstreamer.freedesktop.org/
  125. GStreamer (Wikipedia)
    https://en.wikipedia.org/wi­ki/GStreamer
  126. Wax Gui Toolkit
    https://wiki.python.org/moin/Wax
  127. Python Imaging Library (PIL)
    http://infohost.nmt.edu/tcc/hel­p/pubs/pil/
  128. Why Pyjamas Isn’t a Good Framework for Web Apps (blogpost z roku 2012)
    http://blog.pyjeon.com/2012/07/29/why-pyjamas-isnt-a-good-framework-for-web-apps/
Našli jste v článku chybu?