Hlavní navigace

Tvorba GUI v Pythonu s využitím frameworku PySide: rastrová a vektorová grafika

Pavel Tišnovský

Dnes se budeme zabývat zpracováním a zobrazením grafických informací ve frameworku PySide. Jedná se o poměrně rozsáhlé téma, proto si zatím popíšeme třídy QImage, QBitmap, QPixmap a QTransform.

Doba čtení: 28 minut

Obsah

1. Tvorba GUI v Pythonu s využitím frameworku PySide: rastrová a vektorová grafika

2. Konverze rastrového obrázku reprezentovaného třídami QImage a QPixmap

3. Vytvoření obrázku s jeho vykreslením do hlavního okna aplikace

4. Zdrojový kód prvního demonstračního příkladu

5. Manipulace s obsahem rastrového obrázku reprezentovaného třídou QImage

6. Určení barvy kreslení

7. Druhý demonstrační příklad: použití metody QImage.fill

8. Změna barev jednotlivých pixelů

9. Třetí demonstrační příklad: použití metody QImage.setPixel

10. Třída QBitmap a její odlišnost od třídy QPixmap

11. Čtvrtý demonstrační příklad – vykreslení obrázku se dvěma barvami

12. Afinní transformace rastrových obrázků

13. Pátý demonstrační příklad: otočení obrázku

14. Šestý demonstrační příklad: skládání transformací

15. Sedmý demonstrační příklad: ruční vytvoření transformační matice

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

17. Odkazy na Internetu

1. Tvorba GUI v Pythonu s využitím frameworku PySide: rastrová a vektorová grafika

Jak jsme se již zmínili v perexu, budeme se v dnešním článku zabývat především tím, jakým způsobem je možné ve frameworku PySide pracovat s grafickými informacemi (2D rastrovými obrázky, vektorovými scénami v ploše i v 3D prostoru). Jedná se o poměrně rozsáhlé téma, protože možnosti nabízené framworkem PySide jsou v tomto ohledu skutečně široké – možné je pracovat s jednoduchými ikonami (QIcon), existuje rozhraní ke knihovně OpenGL (QtOpenGL), k vektorové grafice ukládané ve formátu SVG (QtSvg) a zapomenout nesmíme ani na možnost vysokoúrovňové práce s grafickými daty s využitím třídy QGraphicsScene a tříd odvozených od QGraphicsItem.

Začneme popisem práce s rastrovými obrázky, které mohou být reprezentovány hned několika třídami, které jsou vypsány v následující tabulce:

Třída Stručný popis
QImage umožňuje snadný přístup k hodnotám pixelů, nabízí možnost specifikace formátů pixelů
QPixmap lze je snadno vykreslit přes vybraný widget, operace pro načtení a uložení obrázku do jednoho z podporovaných formátů
QBitmap speciální případ QPixmap – bitmapa s dvěma barvami (typicky černobílá)
QPicture kontejner, na který je možné kreslit s využitím třídy QPainter a následně data serializovat do grafického metaformátu

Poznámka: třída QPicture ve skutečnosti vytváří rastrové obrázky až ve chvíli, kdy skutečně budeme chtít provést vykreslení (rendering). Před touto operací jsou grafické entity reprezentovány vektorově. Podrobnosti si ukážeme příště.

2. Konverze rastrového obrázku reprezentovaného třídami QImage a QPixmap

Pokud budeme chtít manipulovat s rastrovými obrázky na úrovni jednotlivých pixelů a následně výsledek zobrazit, lze postupovat například takto:

  1. Vytvoří se instance třídy QImage se zvoleným rozlišením a formátem pixelů (z něj se odvozuje bitová hloubka, existence barvové palety atd.)
  2. S využitím metody setPixel atd. se může s rastrovým obrázkem libovolně manipulovat.
  3. Následně se obrázek převede na instanci třídy QPixmap či QBitmap, protože tyto třídy již umožňují snadné vykreslení do zvoleného widgetu.
  4. Provede se vykreslení obrázku, například použitím metodysetPixmap, kterou nabízí widget QLabel, ale i některé další ovládací prvky.

Ve skutečnosti je možné provádět konverze mezi QImageQPixmap oběma směry. Použité metody jsou vypsány v následující tabulce:

Konverze Metoda
QImage → QPixmap QPixmap.fromImage()
QPixmap → QImage QPixmap.toImage()

Poznámka: první metoda je statická, protože ji můžeme zavolat i ve chvíli, kdy žádná instance třídy QPixmap prozatím neexistuje.

3. Vytvoření obrázku s jeho vykreslením do hlavního okna aplikace

Podívejme se nyní na příklad – vytvoříme instanci třídy QImage se zvoleným rozlišením a formátem pixelů RGB32 (tj. truecolor) a ihned poté provedeme konverzi na instanci třídy QPixmap. Ve skutečnosti se interně žádná konverze nemusí provádět, záleží to na konkrétních podmínkách a formátu pixelů:

def prepareImage(self):
    # vytvoření instance třídy QImage
    self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH,
                              MainWindow.IMAGE_HEIGHT,
                              QtGui.QImage.Format_RGB32)
 
    # vytvoření instance třídy QPixmap z objektu QImage
    self.pixmap = QtGui.QPixmap.fromImage(self.image)

Vykreslení se provede způsobem naznačeným v předchozí kapitole – použitím metody setPixmap u návěští, které je vloženo do hlavního okna aplikace. Velikost okna se přitom automaticky přizpůsobí rozměrům návěstí:

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)

Obrázek 1: Ze screenshotu je patrné, že rastrový obrázek je implicitně naplněn zcela černými pixely.

4. Zdrojový kód prvního demonstračního příkladu

Screenshot zobrazený na obrázku číslo 1 byl získán spuštěním dnešního prvního demonstračního příkladu, jehož úplný zdrojový kód vypadá následovně:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 256
    IMAGE_HEIGHT = 256
 
    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)
 
        # 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('QImage demo #1')
 
        # 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. Manipulace s obsahem rastrového obrázku reprezentovaného třídou QImage

Ve třídě QImage najdeme několik (opravdu jen několik) metod určených pro manipulaci s obsahem rastrového obrázku. Jedná se o zcela základní operace, protože pro vykreslování složitějších grafických entit jsou určeny jiné technologie nabízené například třídou QPainter. Mezi operace podporované třídou QImage patří:

Metoda Provedená operace
setPixel(x, y, index) nastavení barvy pixelu u obrázků s paletou
setPixel(x, y, rgb) nastavení barvy pixelu u plnobarevných obrázků (truecolor)
setPixel(point: QPoint, index) nastavení barvy pixelu u obrázků s paletou
setPixel(point: QPoint, rgb) nastavení barvy pixelu u plnobarevných obrázků (truecolor)
   
fill(color: QColor) vyplnění celého obrázku konstantní barvou
fill(color: GlobalColor) vyplnění celého obrázku konstantní barvou
fill(color: uint) vyplnění celého obrázku konstantní barvou

Vidíme, že obě metody setPixel() a fill() existují v několika variantách, které se od sebe liší počtem a typem parametrů. Obecně platí, že při specifikaci souřadnice pixelu, jehož hodnota se má přečíst či změnit, je rychlejší použít souřadnice x, y namísto objektu typu QPoint.

Poznámka: metoda setPixel() je pochopitelně pomalejší, než přímá manipulace s hodnotami pixelů rastrového obrázku. Pokud tedy budete potřebovat generovat větší množství různých textur atd., je výhodnější si nejprve připravit data pro obrázek a až posléze zkonstruovat instanci třídy QImage s využitím těchto dat.

6. Určení barvy kreslení

Zajímavý je způsob získání barvy pro obě výše zmíněné operace setPixel() a fill(). Existuje totiž hned několik variant, které se od sebe odlišují podle toho, zda se používají obrázky s paletou či plnobarevné (truecolor) obrázky. V případě plnobarevných obrázků se hodnota pixelu vypočítá přes statickou metodu QtGui.qRgb(r, g, b), u obrázků s paletou je možné použít přímo index barvy v paletě (těmito typy obrázků, které jsou velmi užitečné například pro reprezentaci ikon, se budeme podrobněji zabývat příště). V obou případech je barva reprezentována jediným celým číslem, které má u obrázků s paletou význam indexu a u plnobarevných obrázků je do onoho celého čísla zakódována hodnota všech tří barvových složek a popř. i alfa kanálu. Alternativně lze získat barvu libovolného pixelu v obrázku zavoláním metody pixel(), která také existuje ve dvou variantách:

Metoda Provedená operace
pixel(x, y) získání barvy pixelu
pixel(point: QPoint) získání barvy pixelu

7. Druhý demonstrační příklad: použití metody QImage.fill

Ve druhém demonstračním příkladu je ukázáno použití metody QImage.fill určené pro vyplnění celého obrázku konstantní barvou. Princip je jednoduchý – ihned po vytvoření obrázku (který bude vyplněn černou barvou) vypočítáme hodnotu barvy (RGB) a použijeme tuto hodnotu při volání metody QImage.fill:

# vytvoření instance třídy QImage
self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH,
                          MainWindow.IMAGE_HEIGHT,
                          QtGui.QImage.Format_RGB32)
 
# vyplnění celého obrázku konstantní barvou
self.image.fill(QtGui.qRgb(10, 80, 20))

Obrázek 2: Vyplnění celého obrázku s rozlišením 256×256 pixelů konstantní tmavě zelenou barvou.

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

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 256
    IMAGE_HEIGHT = 256
 
    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)
 
        # vyplnění celého obrázku konstantní barvou
        self.image.fill(QtGui.qRgb(10, 80, 20))
 
        # 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('QImage demo #2')
 
        # 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. Změna barev jednotlivých pixelů

Změna barev jednotlivých pixelů s využitím metody QImage.setPixel je velmi snadná, především u plnobarevných obrázků, v nichž můžeme barvu pixelu vypočítat pomocí QtGui.qRgb. Před vlastním kreslením obrázek vytvoříme:

# vytvoření instance třídy QImage
self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH,
                          MainWindow.IMAGE_HEIGHT,
                          QtGui.QImage.Format_RGB32)

Následně s využitím dvojice do sebe vnořených programových smyček vykreslíme gradientní přechod:

# vyplnění celého obrázku barvovým přechodem
for y in range(MainWindow.IMAGE_HEIGHT):
    for x in range(MainWindow.IMAGE_WIDTH):
        self.image.setPixel(x, y, QtGui.qRgb(x, x, y))

Obrázek 3: Vyplnění obrázku o rozměrech 256×256 pixelů barevným přechodem.

Poznámka: pokud bude šířka a/nebo výška obrázku přesahovat hodnotu 255, znamená to, že při výpočtu barvy:

QtGui.qRgb(x, x, y)

bude docházet k přetečení hodnoty jednotlivých barvových složek, protože funkce qRgb žádné kontroly neprovádí. Pokud tedy například zadáme zelenou barvovou složku nastavenou na hodnotu 400, ve skutečnosti se použije 400 % 256 = 144 a současně se ovlivní i hodnota složky modré či červené (podle použitého formátu pixelů). Toho můžeme využít pro různé efekty, například:

# rozměry rastrového obrázku vetší než 255
IMAGE_WIDTH = 512
IMAGE_HEIGHT = 512
 
# vyplnění celého obrázku barvovým přechodem
for y in range(MainWindow.IMAGE_HEIGHT):
    for x in range(MainWindow.IMAGE_WIDTH):
        # zde dochází k přetečení hodnoty barvových složek
        self.image.setPixel(x, y, QtGui.qRgb(x, x*10, y))

Obrázek 4: Efekt přetečení hodnot barvových složek.

9. Třetí demonstrační příklad: použití metody QImage.setPixel

Opět si ukažme, jak se výše uvedený fragment kódu použije v demonstračním příkladu:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 256
    IMAGE_HEIGHT = 256
 
    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)
 
        # vyplnění celého obrázku barvovým přechodem
        for y in range(MainWindow.IMAGE_HEIGHT):
            for x in range(MainWindow.IMAGE_WIDTH):
                self.image.setPixel(x, y, QtGui.qRgb(x, x, y))
 
        # 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('QImage demo #3')
 
        # 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()

Varianta s větším obrázkem a přetečením hodnoty barvových složek:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku vetší než 255
    IMAGE_WIDTH = 512
    IMAGE_HEIGHT = 512
 
    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)
 
        # vyplnění celého obrázku barvovým přechodem
        for y in range(MainWindow.IMAGE_HEIGHT):
            for x in range(MainWindow.IMAGE_WIDTH):
                # zde dochází k přetečení hodnoty barvových složek
                # Red, Green, Blue
                self.image.setPixel(x, y, QtGui.qRgb(x, x*10, y))
 
        # 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('QImage demo #3')
 
        # 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. Třída QBitmap a její odlišnost od třídy QPixmap

Prozatím jsme v příkladech nejprve vytvořili objekt typu QImage, změnili pixely obrázku a následně jsme obrázek převedli na objekt typu QPixmap, jenž je možné relativně snadno vykreslit do okna:

# vytvoření instance třídy QImage
self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH,
                          MainWindow.IMAGE_HEIGHT,
                          QtGui.QImage.Format_RGB32)
 
...
... manipulace s obsahem obrázku
...
 
# vytvoření instance třídy QPixmap z objektu QImage
self.pixmap = QtGui.QPixmap.fromImage(self.image)

Takto převedený obrázek je stále plnobarevný, takže nedošlo k žádné ztrátě informace. Můžeme však provést malou úpravu v posledním kroku a vytvořit skutečnou bitmapu v původním slova smyslu, tj. obrázek, v němž je každý pixel reprezentován jediným bitem:

# vytvoření instance třídy QBitmap z objektu QImage
self.bitmap = QtGui.QBitmap.fromImage(self.image)

Při této konverzi už dochází ke ztrátě obrazové informace (v našem případě dost podstatné), čemuž se framework PySide snaží alespoň trošku zabránit použitím ditheringu:

Obrázek 5: Použití ditheringu při práci s bitmapami.

K čemu se vlastně bitmapy dnes hodí, když současné zobrazovací systémy prakticky ve všech případech podporují plnobarevné zobrazení? Ve skutečnosti se bitmapy již (většinou) nevyužívají pro přímé kreslení, ale plní další funkce, například:

  • Definice tvaru kurzoru (třída QCursor)
  • Definice štětce při kreslení (třída QBrush)
  • Definice složitějších regionů (třída QRegion), které omezují prostor, který se musí překreslit
  • Specifikace masky u pixmap

11. Čtvrtý demonstrační příklad – vykreslení obrázku se dvěma barvami

V dnešním třetím příkladu je ukázáno, jak lze z plnobarevného obrázku reprezentovaného objektem typu QImage vytvořit černobílý obrázek se dvěma barvami. Při převodu se framework PySide snaží o použití jednoduchého ditheringu:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 256
    IMAGE_HEIGHT = 256
 
    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)
 
        # vyplnění celého obrázku barvovým přechodem
        for y in range(MainWindow.IMAGE_HEIGHT):
            for x in range(MainWindow.IMAGE_WIDTH):
                self.image.setPixel(x, y, QtGui.qRgb(y, y, y))
 
        # vytvoření instance třídy QBitmap z objektu QImage
        self.bitmap = QtGui.QBitmap.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('QImage+QBitmap')
 
        # 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 monochromatickým rastrovým obrázkem
        self.addLabelWithBitmap()
 
        # zobrazení hlavního okna
        self.show()
 
    def addLabelWithBitmap(self):
        # vytvoření návěští
        label = QtGui.QLabel("test")
        # přiřazení monochromatického rastrového obrázku k návěští
        label.setPixmap(self.bitmap)
        # 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()

12. Afinní transformace rastrových obrázků

Obrázky je možné před jejich vykreslením nebo dalším zpracováním otočit, změnit jejich měřítko, posunout, zkosit atd. Pro specifikaci operace prováděné s celým obrázkem slouží takzvané afinní transformace, které jsou v počítačové grafice velmi často používány, a to jak ve 2D, tak i v 3D (zde se samozřejmě pracuje s další souřadnicí navíc). Afinní transformace jsou představovány objekty typu QTransform, přičemž samotná transformace je interně představována maticí o rozměrech 3×3 prvky. Většina transformací prováděných v rovině navíc nepotřebuje specifikovat poslední sloupec transformační matice, protože tento sloupec většinou obsahuje sloupcový vektor [0, 0, 1]T, takže se počet skutečně používaných prvků matice sníží na 2×3 prvky.

Velmi důležitou vlastností afinních transformací je možnost jejich skládání, přičemž složením vznikne další afinní transformace. Vzhledem k tomu, že se interně skládání transformací provádí maticovým součinem, je jasné, že se NEjedná o komutativní operaci (to je ovšem logické, když si uvědomíme rozdíl mezi otočením objektu okolo počátku souřadnic a jeho posunem a posunem objektu následovaným otočením okolo počátku souřadnic).

Poznámka: po aplikaci transformace vznikne nový obrázek, ovšem pokud na tento nový obrázek budeme aplikovat opačnou transformaci, nemusíme vždy dostat obrázek původní, protože některé transformace jsou ztrátové (typicky zmenšení, otočení).

Podívejme se, jak lze otočit obrázek o 30° s využitím afinní transformace. Není to nic těžkého, pouze vytvoříme instanci typu QTransform a následně tuto transformaci použijeme pro vytvoření nového obrázku (starý je zahozen správcem paměti):

# vytvoření instance třídy QImage
self.image = QtGui.QImage(MainWindow.IMAGE_WIDTH,
                          MainWindow.IMAGE_HEIGHT,
                          QtGui.QImage.Format_RGB32)
 
# vyplnění celého obrázku barvovým přechodem
...
...
...
 
# vytvoření transformace
transform = QtGui.QTransform()
transform.rotate(30)
 
# získání transformovaného obrázku
self.image = self.image.transformed(transform)

Obrázek 6: Otočení obrázku o 30° vede k jeho zvětšení.

Transformace můžeme skládat dohromady, takže například můžeme specifikovat rotaci následovanou změnou měřítka a další rotací (výsledkem je stále jediný objekt s jedinou transformační maticí):

# skládání transformací
transform = QtGui.QTransform()
transform.rotate(30)
transform.scale(2.0, 1.0)
transform.rotate(-30)

Obrázek 7: Zkosení pomocí složené transformace rotace+změna měřítka+rotace.

V některých případech můžeme chtít specifikovat prvky transformační matice ručně. To je samozřejmě možné, protože existuje přetížený konstruktor třídy QTransform, který akceptuje buď prvky celé matice nebo jen submatice o velikosti 2×3 prvky. Transformaci provádějící rotaci obrázku o 45° lze zapsat takto:

# vytvoření transformační matice
angle = math.radians(45)
transform = QtGui.QTransform(math.cos(angle), math.sin(angle),
                             -math.sin(angle), math.cos(angle),
                             0, 0)

Poznámka: v tomto případě je samozřejmě mnohem kratší použít QTransform.rotate(45).

Obrázek 8: Otočení obrázku o 45° ručně vytvořenou transformační maticí.

13. Pátý demonstrační příklad: otočení obrázku

V dnešním pátém příkladu je ukázán způsob otočení obrázku o 30° s využitím afinních transformací. Význam objektu QTransform jsme si vysvětlili v předchozí kapitole:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 256
    IMAGE_HEIGHT = 256
 
    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)
 
        # vyplnění celého obrázku barvovým přechodem
        for y in range(MainWindow.IMAGE_HEIGHT):
            for x in range(MainWindow.IMAGE_WIDTH):
                self.image.setPixel(x, y, QtGui.qRgb(y, y, 0))
 
        # vytvoření transformace
        transform = QtGui.QTransform()
        transform.rotate(30)
 
        # získání transformovaného obrázku
        self.image = self.image.transformed(transform)
 
        # 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('QImage transform')
 
        # 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()

14. Šestý demonstrační příklad: skládání transformací

V šestém demonstračním příkladu je ukázán způsob skládání několika afinních transformací. Výsledkem je samozřejmě opět afinní transformace reprezentovaná jedinou transformační maticí. S teorií jsme se opět seznámili ve dvanácté kapitole:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 256
    IMAGE_HEIGHT = 256
 
    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)
 
        # vyplnění celého obrázku barvovým přechodem
        for y in range(MainWindow.IMAGE_HEIGHT):
            for x in range(MainWindow.IMAGE_WIDTH):
                self.image.setPixel(x, y, QtGui.qRgb(y, y, 0))
 
        # skládání transformací
        transform = QtGui.QTransform()
        transform.rotate(30)
        transform.scale(2.0, 1.0)
        transform.rotate(-30)
 
        # získání transformovaného obrázku
        self.image = self.image.transformed(transform)
 
        # 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('QImage transform')
 
        # 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()

15. Sedmý demonstrační příklad: ruční vytvoření transformační matice

Jak jsme se již dozvěděli ve dvanácté kapitole, je možné vytvořit transformační matici „ručně“, tj. specifikací jejích jednotlivých prvků. Jak se to v praxi provádí je patrné ze zdrojového kódu dalšího příkladu:

#!/usr/bin/env python
# vim: set fileencoding=utf-8
 
import sys
 
# import "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
import math
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 256
    IMAGE_HEIGHT = 256
 
    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)
 
        # vyplnění celého obrázku barvovým přechodem
        for y in range(MainWindow.IMAGE_HEIGHT):
            for x in range(MainWindow.IMAGE_WIDTH):
                self.image.setPixel(x, y, QtGui.qRgb(y, y, 0))
 
        # vytvoření transformační matice
        angle = math.radians(45)
        transform = QtGui.QTransform(math.cos(angle), math.sin(angle),
                                     -math.sin(angle), math.cos(angle),
                                     0, 0)
 
        # aplikace transformace
        self.image = self.image.transformed(transform)
 
        # 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('QImage transform')
 
        # 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 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:

Příklady prozatím připravené pro další část tohoto seriálu:

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