Hlavní navigace

Tvorba GUI v Pythonu s PySide: použití cest při kreslení 2D scén

Pavel Tišnovský

V devátém článku o frameworku PySide se budeme zabývat popisem cest (paths), které se v PySide používají pro kreslení složitějších 2D scén mj. s využitím Bézierových křivek a množinových operací aplikovaných na cesty.

Doba čtení: 27 minut

Obsah

1. Tvorba GUI v Pythonu s využitím frameworku PySide: použití cest při kreslení 2D scén

2. Koncepty, na nichž je založen objekt typu QPainterPath

3. Základní příkazy určené pro vytvoření cesty

4. První demonstrační příklad: vytvoření a vykreslení jednoduché cesty složené z úsečkových segmentů

5. Kvadratické Bézierovy křivky

6. Kubické Bézierovy křivky

7. Druhý demonstrační příklad: vytvoření cest složených z kvadratických a kubických Bézierových křivek

8. Tvorba vyplněných cest s využitím konstantní barvy, gradientu nebo textury

9. Třetí demonstrační příklad: vykreslení vyplněných cest

10. Množinové operace aplikované na cesty

11. Sjednocení

12. Rozdíl

13. Průnik

14. Symetrická diference

15. Demonstrační příklad: množinové operace aplikované na cesty

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

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

18. Odkazy na Internetu

1. Tvorba GUI v Pythonu s využitím frameworku PySide: použití cest při kreslení 2D scén

V dnešním článku o frameworku PySide se budeme zabývat popisem konceptu takzvaných cest (paths), protože z cest jsou složeny prakticky všechny složitěji tvarované dvourozměrné objekty, ať již se jedná o objekty otevřené (úsečky, polyčáry/lomené čáry, oblouky či křivky) či naopak objekty uzavřené (kruh, elipsa, obdélník, vyplněné mnohoúhelníky apod.). Každá cesta se skládá z prakticky libovolného množství takzvaných segmentů (segments), přičemž je zajímavé, že jednotlivé segmenty na sebe mohou, ale také nemusí navazovat (cesta tedy může obsahovat „skoky“). Použití cest v 2D grafice samozřejmě není nic nového; spíš by se dalo říci, že se jedná o dlouhým časem prověřenou technologii použitou například v PostScriptu či v SVG (PostScript lze přitom chápat jako souborový formát, programovací jazyk a současně i vykreslovací knihovnu).

Obrázek 1: Příklad cesty vytvořené z několika úsečkových (lineárních) segmentů a s nastaveným režimem vyplňování.

2. Koncepty, na nichž je založen objekt typu QPainterPath

Cesty jsou ve frameworku PySide představovány instancemi třídy pojmenované QPainterPath. Jak jsme si již řekli v úvodní kapitole, může se každá cesta skládat z libovolného množství segmentů, přičemž segmentem může být úsečka, kvadratická Bézierova křivka, kubická Bézierova křivka, kruhový oblouk, eliptický oblouk, mnohoúhelník, text, elipsa či obdélník se zaoblenými hranami. Uzavřené segmenty cest tvoří takzvané podcesty (subpath); v případě potřeby je možné podcesty vytvořit programově pomocí metod moveTo a closeSubpath.

Pokud navíc máme vytvořeno několik cest, lze je pomocí množinových operací skládat a vytvořit tak novou cestu s využitím operace sjednocení, průniku, rozdílu a nepřímo také symetrické diference. Na vytvořenou cestu je také možné aplikovat vybranou lineární transformaci, což znamená, že celý 2D objekt lze snadno přesunout, otočit, zkosit nebo změnit jeho měřítko, a to bez nutnosti ručního přepočtu jednotlivých vrcholů. V dalších kapitolách se setkáme se všemi těmito koncepty.

Samotné vytvoření (prázdné) cesty a její následné vykreslení je snadné. Celý postup vypadá zhruba následovně:

# vytvoření objektu typu QPainter s předáním
# reference na "pokreslovaný" objekt
qp = QtGui.QPainter(self.image)
 
# vytvoření cesty
path = QtGui.QPainterPath()
...
...
...
 
# vytvoření pera a nastavení barvy kreslení
pen = QtGui.QPen(QtGui.QColor(255, 0, 0))
 
# kreslit se bude právě vytvořeným perem
qPainter.setPen(pen)
 
# vykreslení cesty
qPainter.drawPath(path)

Obrázek 2: Cesta složená ze dvou kvadratických Bézierových křivek.

3. Základní příkazy určené pro vytvoření cesty

Třída QPainterPath programátorům nabízí poměrně velké množství příkazů určených pro definici segmentů cest. Jednotlivé typy segmentů budou podrobněji popsány v navazujících kapitolách, proto si zde pouze uveďme jednotlivé metody a jejich význam:

# Metoda Stručný popis
1 closeSubpath uzavření podcesty nakreslením úsečky do jejího počátečního vrcholu
2 moveTo přesun aktivního bodu bez kreslení
3 lineTo lineární segment (úsečka)
4 quadTo kvadratická Bézierova křivka
5 cubicTo kubická Bézierova křivka
6 arcTo kruhový či eliptický oblouk
7 arcMoveTo přesun po kruhovém či eliptickém oblouku
8 addEllipse přidání podcesty ve tvaru elipsy
9 addRect přidání podcesty ve tvaru obdélníku
10 addRoundRect přidání podcesty ve tvaru obdélníku se zaoblenými hranami
11 addPolygon přidání podcesty ve tvaru polygonu
12 addText přidání podcesty složené z textu (křivek znaků)
13 addPath přidání další cesty ke stávající cestě (propojení pomocí úsečky)
14 addRegion přidání regionu (oblasti) do cesty. Viz druhá část článku

Doplňme si nyní úryvek kódu ze druhé kapitoly o příkazy, které vykreslí domek jedním tahem. Pro tento účel nám poslouží metody moveTo (přesun do počátečního bodu kreslení) a lineTo (kreslení úsečkového segmentu):

# vytvoření objektu typu QPainter s předáním
# reference na "pokreslovaný" objekt
qp = QtGui.QPainter(self.image)
 
# vytvoření cesty
path = QtGui.QPainterPath()
# nakreslení domku jedním tahem
path.moveTo(100, 200)
path.lineTo(200, 200)
path.lineTo(100, 100)
path.lineTo(100, 200)
path.lineTo(200, 100)
path.lineTo(100, 100)
path.lineTo(150,  50)
path.lineTo(200, 100)
path.lineTo(200, 200)
 
# vytvoření pera a nastavení barvy kreslení
pen = QtGui.QPen(QtGui.QColor(255, 0, 0))
 
# kreslit se bude právě vytvořeným perem
qPainter.setPen(pen)
 
# vykreslení cesty
qPainter.drawPath(path)

Obrázek 3: Domek vykreslený předchozím úryvkem kódu.

4. První demonstrační příklad: vytvoření a vykreslení jednoduché cesty složené z úsečkových segmentů

V dnešním prvním demonstračním příkladu je ukázán způsob vykreslení jednoduché cesty složené pouze z úsečkových segmentů. Z úseček je vykreslen domek jedním tahem (viz předchozí kapitolu), takže výsledek je podobný příkladu z předchozí části tohoto seriálu, v níž jsme si ukázali podobný příklad, v němž však byla použita „pouze“ polyčára (lomená čára), jejíž možnosti jsou menší než možnosti objektů typu cesta:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
import math
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
 
# 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)
 
 
# vytvoření cesty
def createPath():
    path = QtGui.QPainterPath()
    path.moveTo(100, 200)
    path.lineTo(200, 200)
    path.lineTo(100, 100)
    path.lineTo(100, 200)
    path.lineTo(200, 100)
    path.lineTo(100, 100)
    path.lineTo(150,  50)
    path.lineTo(200, 100)
    path.lineTo(200, 200)
    return path
 
 
# funkce pro vykreslení cesty zadanou barvou
def drawPath(qPainter, color, path):
    setColor(qPainter, color)
 
    # vykreslení cesty
    qPainter.drawPath(path)
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 300
    IMAGE_HEIGHT = 250
 
    def __init__(self):
        # zavoláme konstruktor předka
        super(MainWindow, self).__init__()
 
        self.prepareImage()
        # konfigurace GUI + přidání widgetu do okna
        self.prepareGUI()
 
    def prepareImage(self):
        # vytvoření instance třídy QImage
        self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH,
                                  MainWindow.IMAGE_HEIGHT,
                                  QtGui.QImage.Format_RGB32)
 
        # vymazání obrázku
        self.image.fill(0)
 
        # vytvoření objektu typu QPainter s předáním
        # reference na "pokreslovaný" objekt
        qp = QtGui.QPainter(self.image)
 
        # 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)
 
        # vytvoření cesty
        path = createPath()
 
        # vykreslení cesty
        drawPath(qp, YELLOW, path)
 
        # vytvoření instance třídy QPixmap z objektu QImage
        self.pixmap = QtGui.QPixmap.fromImage(self.image)
 
    def prepareGUI(self):
        # velikost okna nezadávejte ručně - špatně se počítá kvůli toolbaru
        # self.resize(256, 300)
        self.setWindowTitle('QPainter')
 
        # tlačítko Quit
        quitAction = QtGui.QAction(QtGui.QIcon('icons/application-exit.png'),
                                   '&Quit', self)
        quitAction.triggered.connect(self.close)
        quitAction.setStatusTip('Quit the application')
        quitAction.setShortcut('Ctrl+Q')
 
        # nástrojový pruh
        self.toolbar = self.addToolBar('title')
        self.toolbar.setMovable(False)
 
        # přidání tlačítka na nástrojový pruh
        self.toolbar.addAction(quitAction)
 
        # doprostřed okna přidáme návěští s rastrovým obrázkem
        self.addLabelWithPixmap()
 
        # zobrazení hlavního okna
        self.show()
 
    def addLabelWithPixmap(self):
        # vytvoření návěští
        label = QtGui.QLabel("test")
        # přiřazení rastrového obrázku k návěští
        label.setPixmap(self.pixmap)
        # vložení návěští do hlavního okna
        self.setCentralWidget(label)
 
    def run(self, app):
        # zobrazení okna na obrazovce
        self.show()
        # vstup do smyčky událostí (event loop)
        app.exec_()
 
 
def main():
    app = QtGui.QApplication(sys.argv)
    MainWindow().run(app)
 
 
if __name__ == '__main__':
    main()

5. Kvadratické Bézierovy křivky

Při tvorbě cest jsou podporovány jak kvadratické, tak i kubické Bézierovy křivky, což je jen dobře, protože kvadratické křivky jsou často používány například při definici fontů zatímco křivky kubické najdeme například v mnoha vektorových grafických editorech a tím pádem i v souborech exportovaných z těchto nástrojů. Bézierovy kvadratické křivky jsou určeny pouze jedním řídicím bodem a dvojicí bodů kotvicích (koncových). Křivka prochází prvním a třetím bodem (kotvicí body), druhý bod (řídicí) určuje současně oba tečné vektory. Ukázka Bézierovy kvadratické křivky spolu s jejími určujícími body je zobrazena na čtvrtém obrázku:

404

Obrázek 4: Bézierova kvadratická křivka zadaná dvojicí kotvicích bodů a jedním bodem řídicím.

Následuje příklad cesty s jedinou Bézierovou kvadratickou křivkou. Povšimněte si nutnosti použít metody moveTo pro specifikaci počátečního bodu křivky:

# vytvoření cesty složené z jediné kvadratické Bézierovy křivky
def createPath1():
    path = QtGui.QPainterPath()
    path.moveTo(10, 100)
    path.quadTo(50, 10, 90, 100)
    return path

Hladké napojení Bézierových kvadratických křivek lze zajistit zadáním identického koncového bodu první křivky a počátečního bodu křivky druhé. Současně musí být shodné tečné vektory v těchto bodech, tj. prostřední (řídicí) body musí být středově symetrické okolo společného bodu obou křivek.

6. Kubické Bézierovy křivky

Kubické Bézierovy křivky většina uživatelů používajících vektorové grafické editory již pravděpodobně velmi dobře zná (ale nalezneme je i v rastrových grafických editorech, například v GIMPu). Připomeňme si tedy, že tyto křivky jsou definovány počátečním bodem, koncovým bodem a dvojicí řídicích bodů (těmi křivka obecně neprochází, tyto body však ovlivňují její tvar). Větší množství řídicích bodů dává uživatelům i větší možnosti tvarování křivky, protože je možné vytvořit i esíčko, smyčku atd. Ve frameworku PySide se tyto křivky (resp. segmenty složené z kubických Bézierových křivek) přidávají do cesty pomocí metody cubicTo, přičemž tato metoda očekává tři body popř.ךestici souřadnic, protože počáteční bod již známe – je jím dočasný poslední bod aktuálně vytvářené cesty.

403

Obrázek 5: Bézierova kubická křivka zadaná dvojicí kotvicích (koncových) bodů a dvojicí bodů řídicích.

Následuje příklad cesty s jedinou Bézierovou kubickou křivkou. Opět si povšimněte nutnosti použít metody moveTo pro specifikaci počátečního bodu křivky:

# vytvoření cesty složené z jediné kubické Bézierovy křivky
def createPath():
    path = QtGui.QPainterPath()
    path.moveTo(10, 100)
    path.quadTo(50, 10, 90, 100)
    return path
405

Obrázek 6: Rozdíl mezi kvadratickými a kubickými Bézierovými křivkami.

Poznámka: Bézierovy kubické křivky jsou v počítačové grafice velmi rozšířeny. Mezi jejich hlavní východy patří intuitivní zadávání a snadné hladké navazování křivek na sebe. Také výpočet bodů, které leží na křivce, je velmi jednoduchý a rychlý. Pomocí Bézierových kubických křivek však nelze přesně modelovat kuželosečky, zejména kruh a elipsu, což omezuje použití těchto křivek v CAD systémech. Také nelze k obecné Bézierově kubice vytvořit offsetovou křivku, tj. křivku, která se od zadané křivky nachází v určité vzdálenosti. Toto omezení se teoreticky může projevit i v komerční grafice (doména programů typu „Illustrator“), ale je nutno říci, že chyba vzniklá použitím Bézierových křivek bývá velmi malá, mnohdy pod rozlišovací schopností lidského oka (opět však platí, že pro přesné CAD a CAM je nutné přijít s přesnějším řešením). Většinou postačí (automatické) rozdělení Bézierovy křivky, která má tvořit offsetovou cestu, na více částí, čímž se získá i větší množství řídicích bodů, se kterými je možné manipulovat. Tuto funkci však přímo v PySide nenajdeme.

7. Druhý demonstrační příklad: vytvoření cest složených z kvadratických a kubických Bézierových křivek

V dnešním druhém demonstračním příkladu je ukázán způsob použití výše zmíněných metod cubicTo a quadTo pro vytvoření několika cest složených z Bézierových kvadratických a kubických křivek. Po spuštění tohoto příkladu by se mělo zobrazit toto okno:

Obrázek 7: Okno s vykreslenými křivkami získanými druhým demonstračním příkladem.

Následuje výpis zdrojového kódu příkladu:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
import math
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
 
# 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)
 
 
# vytvoření cesty složené z kvadratických křivek
def createPath1():
    path = QtGui.QPainterPath()
    path.moveTo(10, 100)
    path.quadTo(50, 10, 90, 100)
    path.quadTo(130, 190, 170, 100)
    path.quadTo(210, 10, 250, 100)
    path.quadTo(290, 190, 330, 100)
    return path
 
 
# vytvoření cesty z kubických křivek
def createPath2():
    path = QtGui.QPainterPath()
    path.moveTo(10, 250)
    path.cubicTo(120, 100,
                 -20, 100,
                  90, 250)
    return path
 
 
# vytvoření cesty z kubických křivek
def createPath3():
    path = QtGui.QPainterPath()
    path.moveTo(150, 200)
    path.cubicTo(225,  50,
                 225, 350,
                 300, 200)
    return path
 
 
# funkce pro vykreslení cesty zadanou barvou
def drawPath(qPainter, color, path):
    # nastavení barvy
    setColor(qPainter, color)
 
    # vykreslení cesty
    qPainter.drawPath(path)
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 340
    IMAGE_HEIGHT = 300
 
    def __init__(self):
        # zavoláme konstruktor předka
        super(MainWindow, self).__init__()
 
        self.prepareImage()
        # konfigurace GUI + přidání widgetu do okna
        self.prepareGUI()
 
    def prepareImage(self):
        # vytvoření instance třídy QImage
        self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH,
                                  MainWindow.IMAGE_HEIGHT,
                                  QtGui.QImage.Format_RGB32)
 
        # vymazání obrázku
        self.image.fill(0)
 
        # vytvoření objektu typu QPainter s předáním
        # reference na "pokreslovaný" objekt
        qp = QtGui.QPainter(self.image)
 
        # 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)
 
        # vytvoření cesty
        path1 = createPath1()
 
        # vykreslení cesty
        drawPath(qp, GREEN, path1)
 
        # vytvoření cesty
        path2 = createPath2()
 
        # vykreslení cesty
        drawPath(qp, YELLOW, path2)
 
        # vytvoření cesty
        path3 = createPath3()
 
        # vykreslení cesty
        drawPath(qp, MAGENTA, path3)
 
        # vytvoření instance třídy QPixmap z objektu QImage
        self.pixmap = QtGui.QPixmap.fromImage(self.image)
 
    def prepareGUI(self):
        # velikost okna nezadávejte ručně - špatně se počítá kvůli toolbaru
        # self.resize(256, 300)
        self.setWindowTitle('QPainter')
 
        # tlačítko Quit
        quitAction = QtGui.QAction(QtGui.QIcon('icons/application-exit.png'),
                                   '&Quit', self)
        quitAction.triggered.connect(self.close)
        quitAction.setStatusTip('Quit the application')
        quitAction.setShortcut('Ctrl+Q')
 
        # nástrojový pruh
        self.toolbar = self.addToolBar('title')
        self.toolbar.setMovable(False)
 
        # přidání tlačítka na nástrojový pruh
        self.toolbar.addAction(quitAction)
 
        # doprostřed okna přidáme návěští s rastrovým obrázkem
        self.addLabelWithPixmap()
 
        # zobrazení hlavního okna
        self.show()
 
    def addLabelWithPixmap(self):
        # vytvoření návěští
        label = QtGui.QLabel("test")
        # přiřazení rastrového obrázku k návěští
        label.setPixmap(self.pixmap)
        # vložení návěští do hlavního okna
        self.setCentralWidget(label)
 
    def run(self, app):
        # zobrazení okna na obrazovce
        self.show()
        # vstup do smyčky událostí (event loop)
        app.exec_()
 
 
def main():
    app = QtGui.QApplication(sys.argv)
    MainWindow().run(app)
 
 
if __name__ == '__main__':
    main()

8. Tvorba vyplněných cest s využitím konstantní barvy, gradientu nebo textury

Již z předchozích kapitol víme, že cesty mohou tvořit buď uzavřené tvary, nebo se jedná o cesty složené z liniových segmentů. Pokud při kresbě cesty vybereme neprůhledný (viditelný) štětec, budou uzavřené tvary tímto štětcem jednoduše vyplněny. U cesty tvořených liniovými segmenty také dojde k vyplnění, a to takovým způsobem, že se cesta (či jednotlivé izolované segmenty) automaticky uzavřou úsečkou spojující konec cesty s jejím začátkem. V takové situaci může dojít k tomu, že se některé hrany tvořící hranici cesty mohou protínat. Podívejme se na tři příklady ukazující, jakým způsobem k vyplnění cest dojde.

V prvním příkladu je cesta tvořena jen dvěma úsečkovými segmenty zobrazenými širší zelenou stopou:

path = QtGui.QPainterPath()
path.moveTo(50, 50)
path.lineTo(50, 150)
path.lineTo(150, 150)

Výsledek vykreslení cesty vyplněné lineárním gradientem:

Obrázek 8: Cesta tvořená jen dvěma úsečkovými segmenty, která byla vyplněna lineárním gradientem.

Ve druhém příkladu je cesta opět otevřená a tvoří ji protínající se úsečky tvořící hvězdu (ovšem povšimněte si chybějící hrany):

# výpočet souřadnic n-tého vrcholu hvězdy
def starVertex(cx, cy, radius, n):
    angle = math.radians(n*144)
    return cx + radius * math.sin(angle), cy - radius * math.cos(angle)
 
path = QtGui.QPainterPath()
path.moveTo(*starVertex(140, 140, 120, 0))
path.lineTo(*starVertex(140, 140, 120, 1))
path.lineTo(*starVertex(140, 140, 120, 2))
path.lineTo(*starVertex(140, 140, 120, 3))
path.lineTo(*starVertex(140, 140, 120, 4))

Výsledek vykreslení cesty vyplněné radiálním gradientem:

Obrázek 9: Cesta tvořená hranami hvězdy.

V posledním příkladu můžeme vidět použití segmentů tvořených Bézierovými křivkami. Jedna cesta je vyplněna texturou, další lineárním barevným přechodem a třetí přechodem radiálním:

# vytvoření cesty složené z kvadratických křivek
def createPath1():
    path = QtGui.QPainterPath()
    path.moveTo(10, 100)
    path.quadTo(50, 10, 90, 100)
    path.quadTo(130, 190, 170, 100)
    path.quadTo(210, 10, 250, 100)
    path.quadTo(290, 190, 330, 100)
    return path
 
 
# vytvoření cesty z kubických křivek
def createPath2():
    path = QtGui.QPainterPath()
    path.moveTo(10, 250)
    path.cubicTo(120, 100,
                 -20, 100,
                  90, 250)
    return path
 
 
# vytvoření cesty z kubických křivek
def createPath3():
    path = QtGui.QPainterPath()
    path.moveTo(150, 200)
    path.cubicTo(225,  50,
                 225, 350,
                 300, 200)
    return path

Obrázek 10: Cesty tvořené Bézierovými křivkami.

9. Třetí demonstrační příklad: vykreslení vyplněných cest

Metody pro tvorbu vyplněných cest, které byly popsané v předchozí kapitole, použijeme ve třetím demonstračním příkladu, jehož zdrojový kód je vypsán níže:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
import math
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
 
# vytvoření štětce z pixmapy
def createBrushFromPixmap(filename):
    pixmap = QtGui.QPixmap(filename)
    return QtGui.QBrush(pixmap)
 
 
# vytvoření štětce z gradientního přechodu
def createBrushFromLinearGradient(color1, color2):
    gradient = QtGui.QLinearGradient(100, 100, 100, 140)
    gradient.setColorAt(0.2, QtGui.QColor(*color1))
    gradient.setColorAt(1.0, QtGui.QColor(*color2))
    gradient.setSpread(QtGui.QGradient.Spread.ReflectSpread)
    return QtGui.QBrush(gradient)
 
 
# vytvoření štětce z gradientního přechodu
def createBrushFromRadialGradient(color1, color2):
    gradient = QtGui.QRadialGradient(225, 200, 10)
    gradient.setColorAt(0.2, QtGui.QColor(*color1))
    gradient.setColorAt(1.0, QtGui.QColor(*color2))
    gradient.setSpread(QtGui.QGradient.Spread.ReflectSpread)
    return QtGui.QBrush(gradient)
 
 
# 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)
 
 
# vytvoření cesty složené z kvadratických křivek
def createPath1():
    path = QtGui.QPainterPath()
    path.moveTo(10, 100)
    path.quadTo(50, 10, 90, 100)
    path.quadTo(130, 190, 170, 100)
    path.quadTo(210, 10, 250, 100)
    path.quadTo(290, 190, 330, 100)
    return path
 
 
# vytvoření cesty z kubických křivek
def createPath2():
    path = QtGui.QPainterPath()
    path.moveTo(10, 250)
    path.cubicTo(120, 100,
                 -20, 100,
                  90, 250)
    return path
 
 
# vytvoření cesty z kubických křivek
def createPath3():
    path = QtGui.QPainterPath()
    path.moveTo(150, 200)
    path.cubicTo(225,  50,
                 225, 350,
                 300, 200)
    return path
 
 
# funkce pro vykreslení cesty zadanou barvou a stylem štětce
def drawPath(qPainter, color, brush, path):
    # nastavení barvy
    setColor(qPainter, color)
 
    # nastavení stylu štětce
    qPainter.setBrush(brush)
 
    # vykreslení cesty
    qPainter.drawPath(path)
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 340
    IMAGE_HEIGHT = 300
 
    def __init__(self):
        # zavoláme konstruktor předka
        super(MainWindow, self).__init__()
 
        self.prepareImage()
        # konfigurace GUI + přidání widgetu do okna
        self.prepareGUI()
 
    def prepareImage(self):
        # vytvoření instance třídy QImage
        self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH,
                                  MainWindow.IMAGE_HEIGHT,
                                  QtGui.QImage.Format_RGB32)
 
        # vymazání obrázku
        self.image.fill(0)
 
        # vytvoření objektu typu QPainter s předáním
        # reference na "pokreslovaný" objekt
        qp = QtGui.QPainter(self.image)
 
        # 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)
 
        # vytvoření štětců
        brush1 = createBrushFromLinearGradient(WHITE, BLUE)
        brush2 = createBrushFromPixmap("pixmaps/voronoi.png")
        brush3 = createBrushFromRadialGradient(YELLOW, BLACK)
 
        # vytvoření cesty
        path1 = createPath1()
 
        # vykreslení cesty
        drawPath(qp, GREEN, brush1, path1)
 
        # vytvoření cesty
        path2 = createPath2()
 
        # vykreslení cesty
        drawPath(qp, YELLOW, brush2, path2)
 
        # vytvoření cesty
        path3 = createPath3()
 
        # vykreslení cesty
        drawPath(qp, MAGENTA, brush3, path3)
 
        # vytvoření instance třídy QPixmap z objektu QImage
        self.pixmap = QtGui.QPixmap.fromImage(self.image)
 
    def prepareGUI(self):
        # velikost okna nezadávejte ručně - špatně se počítá kvůli toolbaru
        # self.resize(256, 300)
        self.setWindowTitle('QPainter')
 
        # tlačítko Quit
        quitAction = QtGui.QAction(QtGui.QIcon('icons/application-exit.png'),
                                   '&Quit', self)
        quitAction.triggered.connect(self.close)
        quitAction.setStatusTip('Quit the application')
        quitAction.setShortcut('Ctrl+Q')
 
        # nástrojový pruh
        self.toolbar = self.addToolBar('title')
        self.toolbar.setMovable(False)
 
        # přidání tlačítka na nástrojový pruh
        self.toolbar.addAction(quitAction)
 
        # doprostřed okna přidáme návěští s rastrovým obrázkem
        self.addLabelWithPixmap()
 
        # zobrazení hlavního okna
        self.show()
 
    def addLabelWithPixmap(self):
        # vytvoření návěští
        label = QtGui.QLabel("test")
        # přiřazení rastrového obrázku k návěští
        label.setPixmap(self.pixmap)
        # vložení návěští do hlavního okna
        self.setCentralWidget(label)
 
    def run(self, app):
        # zobrazení okna na obrazovce
        self.show()
        # vstup do smyčky událostí (event loop)
        app.exec_()
 
 
def main():
    app = QtGui.QApplication(sys.argv)
    MainWindow().run(app)
 
 
if __name__ == '__main__':
    main()

10. Množinové operace aplikované na cesty

V případě, že máme vytvořeny dvě cesty, lze nad nimi aplikovat základní množinové operace sjednocení, průniku a rozdílu. Výsledkem těchto operací je nová cesta. Pro množinové operace se používají buď metody united, intersected a subtracted, nebo lze alternativně použít i přetížené operátory aplikovatelné na dvojici libovolných cest:

Operace Metoda Přetížený operátor Alternativní operátor
sjednocení united |, |= +, +=
průnik intersected &, &=  
rozdíl subtracted -, -=  

Vzhledem k tomu, že výsledkem těchto operací je nová cesta, je možné operace snadno „zřetězit“, například takto:

path = path1 + path2 - path3 & path4

V těchto případech se aplikují běžná pravidla priorit operátorů!

Poznámka: pokud jsou použity operátory ve tvaru +=, -=, &= a |=, je nově vytvořená cesta uložena do prvního (tj. levého) operandu, což je u těchto operátorů očekávané chování.

Obrázek 11: Dvě překrývající se uzavřené cesty, na které budeme aplikovat množinové operace.

11. Sjednocení

Operaci sjednocení dvou cest je možné provést buď metodou united, nebo alternativně s využitím přetížených operátorů + a | (ty samozřejmě existují i ve variantách += a |=). Ukažme si použití operátoru | pro sjednocení dvou cest do cesty jediné:

# vytvoření první cesty
path1 = createPath1()
 
# vytvoření druhé cesty
path2 = createPath2()
 
# SJEDNOCENÍ #
 
# vykreslení kombinace obou cest
drawPath(qp, RED, 4, brush, path1 | path2)

Zcela stejný výsledek bude mít použití přetíženého operátoru +:

# vytvoření první cesty
path1 = createPath1()
 
# vytvoření druhé cesty
path2 = createPath2()
 
# SJEDNOCENÍ #
 
# vykreslení kombinace obou cest
drawPath(qp, RED, 4, brush, path1 + path2)

Použití metody united:

# vytvoření první cesty
path1 = createPath1()
 
# vytvoření druhé cesty
path2 = createPath2()
 
# SJEDNOCENÍ #
 
# vykreslení kombinace obou cest
drawPath(qp, RED, 4, brush, path1.united(path2))

Obrázek 12: Výsledek operace sjednocení dvou cest.

Poznámka: operace sjednocení realizovaná pomocí + či | je komutativní, takže výsledek nezáleží na pořadí operandů.

12. Rozdíl

Operace rozdílu, tj. „odečtení“ tvaru jedné cesty od cesty druhé, je realizována buď metodou subtracted, nebo přetíženým operátorem -. Tato operace pochopitelně není komutativní, neboť výsledkem path1 – path2 bude cesta odlišná od výsledku operace path2 – path1:

# vytvoření první cesty
path1 = createPath1()
 
# vytvoření druhé cesty
path2 = createPath2()
 
# ROZDÍL
 
# vykreslení kombinace obou cest
drawPath(qp, YELLOW, 4, brush, path1 - path2)

Použití metody subtracted:

# vytvoření první cesty
path1 = createPath1()
 
# vytvoření druhé cesty
path2 = createPath2()
 
# ROZDÍL
 
# vykreslení kombinace obou cest
drawPath(qp, YELLOW, 4, brush, path1.subtracted(path2))

Obrázek 13: Výsledek operace rozdílu dvou cest.

Poznámka: výsledkem této operace může být prázdná cesta, což si ostatně můžete sami otestovat: postačí, aby první cesta byla celá překrytá cestou druhou.

13. Průnik

Třetí přímo podporovanou množinovou operací je průnik, který lze realizovat metodou intersected nebo alternativně pomocí přetíženého operátoru & (ale už ne operátoru *, ten má odlišný význam!). Použití této komutativní operace je ukázáno na dalším úryvku kódu:

# vytvoření první cesty
path1 = createPath1()
 
# vytvoření druhé cesty
path2 = createPath2()
 
# PRŮNIK
 
# vykreslení kombinace obou cest
drawPath(qp, CYAN, 4, brush, path1 & path2)

Použití metody intersected:

# vytvoření první cesty
path1 = createPath1()
 
# vytvoření druhé cesty
path2 = createPath2()
 
# PRŮNIK
 
# vykreslení kombinace obou cest
drawPath(qp, CYAN, 4, brush, path1.intersected(path2))

Obrázek 14: Výsledek operace průniku dvou cest.

Poznámka: i výsledkem této operace může být prázdná cesta, podobně jako tomu bylo i u operace předchozí.

14. Symetrická diference

Zajímavé je, že poslední často používaná množinová operace – symetrická diference – není přímo podporována. Ve skutečnosti ji ale můžeme snadno nahradit již výše popsanými operacemi, a to například takto:

# vytvoření první cesty
path1 = createPath1()
 
# vytvoření druhé cesty
path2 = createPath2()
 
# SYMETRICKÁ DIFERENCE
 
# vykreslení kombinace obou cest
drawPath(qp, MAGENTA, 4, brush, (path1 | path2) - (path1 & path2))

Použití metod namísto přetížených operátorů vede k relativně špatně čitelnému kódu:

# vytvoření první cesty
path1 = createPath1()
 
# vytvoření druhé cesty
path2 = createPath2()
 
# SYMETRICKÁ DIFERENCE
 
# vykreslení kombinace obou cest
drawPath(qp, MAGENTA, 4, brush, path1.united(path2).subtracted(path1.intersected(path2)))

Obrázek 15: Výsledek operace symetrické diference dvou cest.

15. Demonstrační příklad: množinové operace aplikované na cesty

V dalším, dnes již posledním demonstračním příkladu, jsou ukázány všechny výše popsané množinové operace aplikované na cesty:

Obrázek 16: Screenshot demonstračního příkladu s výsledky všech výše popsaných množinových operací nad cestami.

Zdrojový kód celého příkladu vypadá následovně:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
import math
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
 
# vytvoření štětce z pixmapy
def createBrushFromPixmap(filename):
    pixmap = QtGui.QPixmap(filename)
    return QtGui.QBrush(pixmap)
 
 
# nastavení barvy kreslení (pera) na zadanou barvu a šířku stopy
def setColorAndWidth(qPainter, color, width):
    # vytvoření pera a nastavení barvy kreslení
    pen = QtGui.QPen(QtGui.QColor(*color))
    pen.setWidth(width)
 
    # kreslit se bude právě vytvořeným perem
    qPainter.setPen(pen)
 
 
# vytvoření cesty složené z obdélníku
def createPath1():
    path = QtGui.QPainterPath()
    path.addRect(10, 10, 200, 200)
    return path
 
 
# vytvoření cesty složené z obdélníku
def createPath2():
    path = QtGui.QPainterPath()
    path.addRect(100, 100, 200, 200)
    return path
 
 
# funkce pro vykreslení cesty zadanou barvou a stylem štětce
def drawPath(qPainter, color, width, brush, path):
    # nastavení barvy
    setColorAndWidth(qPainter, color, width)
 
    # nastavení stylu štětce
    qPainter.setBrush(brush)
 
    # vykreslení cesty
    qPainter.drawPath(path)
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 640
    IMAGE_HEIGHT = 640
 
    def __init__(self):
        # zavoláme konstruktor předka
        super(MainWindow, self).__init__()
 
        self.prepareImage()
        # konfigurace GUI + přidání widgetu do okna
        self.prepareGUI()
 
    def prepareImage(self):
        # vytvoření instance třídy QImage
        self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH,
                                  MainWindow.IMAGE_HEIGHT,
                                  QtGui.QImage.Format_RGB32)
 
        # vymazání obrázku
        self.image.fill(0)
 
        # vytvoření objektu typu QPainter s předáním
        # reference na "pokreslovaný" objekt
        qp = QtGui.QPainter(self.image)
 
        # 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)
 
        # vytvoření štětce
        brush = createBrushFromPixmap("pixmaps/voronoi.png")
 
        # vytvoření první cesty
        path1 = createPath1()
 
        # vytvoření druhé cesty
        path2 = createPath2()
 
        # SJEDNOCENÍ #
 
        # vykreslení kombinace obou cest
        drawPath(qp, RED, 4, brush, path1 | path2)
 
        # posun
        path1.translate(320, 0)
        path2.translate(320, 0)
 
        # ROZDÍL
 
        # vykreslení kombinace obou cest
        drawPath(qp, YELLOW, 4, brush, path1 - path2)
 
        # posun
        path1.translate(-320, 320)
        path2.translate(-320, 320)
 
        # PRŮNIK
 
        # vykreslení kombinace obou cest
        drawPath(qp, CYAN, 4, brush, path1 & path2)
 
        # posun
        path1.translate(320, 0)
        path2.translate(320, 0)
 
        # SYMETRICKÁ DIFERENCE
 
        # vykreslení kombinace obou cest
        drawPath(qp, MAGENTA, 4, brush, (path1 | path2) - (path1 & path2))
 
        # vytvoření instance třídy QPixmap z objektu QImage
        self.pixmap = QtGui.QPixmap.fromImage(self.image)
 
    def prepareGUI(self):
        # velikost okna nezadávejte ručně - špatně se počítá kvůli toolbaru
        # self.resize(256, 300)
        self.setWindowTitle('QPainter')
 
        # tlačítko Quit
        quitAction = QtGui.QAction(QtGui.QIcon('icons/application-exit.png'),
                                   '&Quit', self)
        quitAction.triggered.connect(self.close)
        quitAction.setStatusTip('Quit the application')
        quitAction.setShortcut('Ctrl+Q')
 
        # nástrojový pruh
        self.toolbar = self.addToolBar('title')
        self.toolbar.setMovable(False)
 
        # přidání tlačítka na nástrojový pruh
        self.toolbar.addAction(quitAction)
 
        # doprostřed okna přidáme návěští s rastrovým obrázkem
        self.addLabelWithPixmap()
 
        # zobrazení hlavního okna
        self.show()
 
    def addLabelWithPixmap(self):
        # vytvoření návěští
        label = QtGui.QLabel("test")
        # přiřazení rastrového obrázku k návěští
        label.setPixmap(self.pixmap)
        # vložení návěští do hlavního okna
        self.setCentralWidget(label)
 
    def run(self, app):
        # zobrazení okna na obrazovce
        self.show()
        # vstup do smyčky událostí (event loop)
        app.exec_()
 
 
def main():
    app = QtGui.QApplication(sys.argv)
    MainWindow().run(app)
 
 
if __name__ == '__main__':
    main()

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

Zdrojové kódy všech pěti dnes popsaných demonstračních příkladů byly opět, 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:

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

V následující části seriálu o tvorbě grafického uživatelského rozhraní s využitím frameworku PySide dokončíme téma tvorby 2D grafiky. Budeme se zabývat dvěma užitečnými technikami – použitím takzvaných oblastí (region) sloužících pro určení té části plochy, do níž má být provedeno vykreslování. Ve druhé části si pak ukážeme způsoby vzájemné kompozice dvourozměrných entit v z-ové rovině.

18. Odkazy na Internetu

  1. PySide 1.2.1 documentation
    https://pyside.github.io/doc­s/pyside/index.html
  2. QImage
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QIma­ge.html
  3. QPixmap
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QPix­map.html
  4. QBitmap
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QBit­map.html
  5. QPaintDevice
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QPa­intDevice.html
  6. QPicture
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QPic­ture.html
  7. QPainter
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QPa­inter.html
  8. QPainterPath
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QPa­interPath.html
  9. QGradient
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QGra­dient.html
  10. QLinearGradient
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QLi­nearGradient.html
  11. QRadialGradient
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QRa­dialGradient.html
  12. Afinní zobrazení
    https://cs.wikipedia.org/wi­ki/Afinn%C3%AD_zobrazen%C3%AD
  13. Differences Between PySide and PyQt
    https://wiki.qt.io/Differen­ces_Between_PySide_and_PyQt
  14. PySide 1.2.1 tutorials
    https://pyside.github.io/doc­s/pyside/tutorials/index.html
  15. PySide tutorial
    http://zetcode.com/gui/py­sidetutorial/
  16. Drawing in PySide
    http://zetcode.com/gui/py­sidetutorial/drawing/
  17. Qt Core
    https://pyside.github.io/doc­s/pyside/PySide/QtCore/Qt­.html
  18. QLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QLa­yout.html
  19. QStackedLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QStac­kedLayout.html
  20. QFormLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QFor­mLayout.html
  21. QBoxLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QBox­Layout.html
  22. QHBoxLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QHBox­Layout.html
  23. QVBoxLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QVBox­Layout.html
  24. QGridLayout
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QGrid­Layout.html
  25. QAction
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QAc­tion.html
  26. QMessageBox
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QMes­sageBox.html
  27. QListWidget
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QLis­tWidget.html
  28. Signals & Slots
    http://doc.qt.io/qt-4.8/signalsandslots.html
  29. Signals and Slots in PySide
    http://wiki.qt.io/Signals_an­d_Slots_in_PySide
  30. Intro to PySide/PyQt: Basic Widgets and Hello, World!
    http://www.pythoncentral.io/intro-to-pysidepyqt-basic-widgets-and-hello-world/
  31. QWidget
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QWid­get.html
  32. QMainWindow
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QMa­inWindow.html
  33. QLabel
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QLa­bel.html
  34. QAbstractButton
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QAb­stractButton.html
  35. QCheckBox
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QChec­kBox.html
  36. QRadioButton
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QRa­dioButton.html
  37. QButtonGroup
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QBut­tonGroup.html
  38. QFrame
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QFra­me.html#PySide.QtGui.PySi­de.QtGui.QFrame
  39. QFrame.frameStyle
    https://pyside.github.io/doc­s/pyside/PySide/QtGui/QFra­me.html#PySide.QtGui.PySi­de.QtGui.QFrame.frameStyle
  40. Leo editor
    http://leoeditor.com/
  41. IPython Qt Console aneb vylepšený pseudoterminál
    https://mojefedora.cz/integrovana-vyvojova-prostredi-ve-fedore-ipython-a-ipython-notebook/#k06
  42. Vývojová prostředí ve Fedoře (4. díl)
    https://mojefedora.cz/vyvojova-prostredi-ve-fedore-4-dil/
  43. Seriál Letní škola programovacího jazyka Logo
    http://www.root.cz/serialy/letni-skola-programovaciho-jazyka-logo/
  44. Educational programming language
    http://en.wikipedia.org/wi­ki/Educational_programmin­g_language
  45. Logo Tree Project:
    http://www.elica.net/downlo­ad/papers/LogoTreeProject­.pdf
  46. Hra Breakout napísaná v Tkinteri
    https://www.root.cz/clanky/hra-breakout-napisana-v-tkinteri/
  47. Hra Snake naprogramovaná v Pythone s pomocou Tkinter
    https://www.root.cz/clanky/hra-snake-naprogramovana-v-pythone-s-pomocou-tkinter/
  48. 24.1. turtle — Turtle graphics
    https://docs.python.org/3­.5/library/turtle.html#mo­dule-turtle
  49. TkDND
    http://freecode.com/projects/tkdnd
  50. Python Tkinter Fonts
    https://www.tutorialspoin­t.com/python/tk_fonts.htm
  51. The Tkinter Canvas Widget
    http://effbot.org/tkinter­book/canvas.htm
  52. Ovládací prvek (Wikipedia)
    https://cs.wikipedia.org/wi­ki/Ovl%C3%A1dac%C3%AD_prvek_­%28po%C4%8D%C3%ADta%C4%8D%29
  53. Rezervovaná klíčová slova v Pythonu
    https://docs.python.org/3/re­ference/lexical_analysis.html#ke­ywords
  54. TkDocs: Styles and Themes
    http://www.tkdocs.com/tuto­rial/styles.html
  55. Drawing in Tkinter
    http://zetcode.com/gui/tkin­ter/drawing/
  56. Changing ttk widget text color (StackOverflow)
    https://stackoverflow.com/qu­estions/16240477/changing-ttk-widget-text-color
  57. The Hitchhiker's Guide to Pyhton: GUI Applications
    http://docs.python-guide.org/en/latest/scenarios/gui/
  58. 7 Top Python GUI Frameworks for 2017
    http://insights.dice.com/2014/11/26/5-top-python-guis-for-2015/
  59. GUI Programming in Python
    https://wiki.python.org/mo­in/GuiProgramming
  60. Cameron Laird's personal notes on Python GUIs
    http://phaseit.net/claird/com­p.lang.python/python_GUI.html
  61. Python GUI development
    http://pythoncentral.io/introduction-python-gui-development/
  62. Graphic User Interface FAQ
    https://docs.python.org/2/faq/gu­i.html#graphic-user-interface-faq
  63. TkInter
    https://wiki.python.org/moin/TkInter
  64. Tkinter 8.5 reference: a GUI for Python
    http://infohost.nmt.edu/tcc/hel­p/pubs/tkinter/web/index.html
  65. TkInter (Wikipedia)
    https://en.wikipedia.org/wiki/Tkinter
  66. appJar
    http://appjar.info/
  67. appJar (Wikipedia)
    https://en.wikipedia.org/wiki/AppJar
  68. appJar na Pythonhosted
    http://pythonhosted.org/appJar/
  69. appJar widgets
    http://appjar.info/pythonWidgets/
  70. Stránky projektu PyGTK
    http://www.pygtk.org/
  71. PyGTK (Wikipedia)
    https://cs.wikipedia.org/wiki/PyGTK
  72. Stránky projektu PyGObject
    https://wiki.gnome.org/Pro­jects/PyGObject
  73. Stránky projektu Kivy
    https://kivy.org/#home
  74. Stránky projektu PyQt
    https://riverbankcomputin­g.com/software/pyqt/intro
  75. PyQt (Wikipedia)
    https://cs.wikipedia.org/wiki/PyGTK
  76. Stránky projektu PySide
    https://wiki.qt.io/PySide
  77. PySide (Wikipedia)
    https://en.wikipedia.org/wiki/PySide
  78. Stránky projektu Kivy
    https://kivy.org/#home
  79. Kivy (framework, Wikipedia)
    https://en.wikipedia.org/wi­ki/Kivy_(framework)
  80. QML Applications
    http://doc.qt.io/qt-5/qmlapplications.html
  81. KDE
    https://www.kde.org/
  82. Qt
    https://www.qt.io/
  83. GNOME
    https://en.wikipedia.org/wiki/GNOME
  84. Category:Software that uses PyGTK
    https://en.wikipedia.org/wi­ki/Category:Software_that_u­ses_PyGTK
  85. Category:Software that uses PyGObject
    https://en.wikipedia.org/wi­ki/Category:Software_that_u­ses_PyGObject
  86. Category:Software that uses wxWidgets
    https://en.wikipedia.org/wi­ki/Category:Software_that_u­ses_wxWidgets
  87. GIO
    https://developer.gnome.or­g/gio/stable/
  88. GStreamer
    https://gstreamer.freedesktop.org/
  89. GStreamer (Wikipedia)
    https://en.wikipedia.org/wi­ki/GStreamer
  90. Wax Gui Toolkit
    https://wiki.python.org/moin/Wax
  91. Python Imaging Library (PIL)
    http://infohost.nmt.edu/tcc/hel­p/pubs/pil/
  92. 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?