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
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
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:
- 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.)
- S využitím metody setPixel atd. se může s rastrovým obrázkem libovolně manipulovat.
- 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.
- 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 QImage ↔ QPixmap 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/presentations. 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říklad | Adresa |
---|---|
55_qimage.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/55_qimage.py |
56_qimage_fill.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/56_qimage_fill.py |
57_qimage_setpixel.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/57_qimage_setpixel.py |
58_qimage_setpixel_overflow.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/58_qimage_setpixel_overflow.py |
59_bitmap.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/59_bitmap.py |
60_qimage_transform_rotate.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/60_qimage_transform_rotate.py |
61_qimage_transform_composition.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/61_qimage_transform_composition.py |
62_qimage_transformation_matrix.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/62_qimage_transformation_matrix.py |
Příklady prozatím připravené pro další část tohoto seriálu:
Příklad | Adresa |
---|---|
63_qpainter.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/63_qpainter.py |
64_qpainter_smart_constructor.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/64_qpainter_smart_constructor.py |
17. Odkazy na Internetu
- PySide 1.2.1 documentation
https://pyside.github.io/docs/pyside/index.html - QImage
https://pyside.github.io/docs/pyside/PySide/QtGui/QImage.html - QPixmap
https://pyside.github.io/docs/pyside/PySide/QtGui/QPixmap.html - QBitmap
https://pyside.github.io/docs/pyside/PySide/QtGui/QBitmap.html - QPaintDevice
https://pyside.github.io/docs/pyside/PySide/QtGui/QPaintDevice.html - QPicture
https://pyside.github.io/docs/pyside/PySide/QtGui/QPicture.html - QPainter
https://pyside.github.io/docs/pyside/PySide/QtGui/QPainter.html - Afinní zobrazení
https://cs.wikipedia.org/wiki/Afinn%C3%AD_zobrazen%C3%AD - Differences Between PySide and PyQt
https://wiki.qt.io/Differences_Between_PySide_and_PyQt - PySide 1.2.1 tutorials
https://pyside.github.io/docs/pyside/tutorials/index.html - PySide tutorial
http://zetcode.com/gui/pysidetutorial/ - Drawing in PySide
http://zetcode.com/gui/pysidetutorial/drawing/ - Qt Core
https://pyside.github.io/docs/pyside/PySide/QtCore/Qt.html - QLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QLayout.html - QStackedLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QStackedLayout.html - QFormLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QFormLayout.html - QBoxLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QBoxLayout.html - QHBoxLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QHBoxLayout.html - QVBoxLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QVBoxLayout.html - QGridLayout
https://pyside.github.io/docs/pyside/PySide/QtGui/QGridLayout.html - QAction
https://pyside.github.io/docs/pyside/PySide/QtGui/QAction.html - QMessageBox
https://pyside.github.io/docs/pyside/PySide/QtGui/QMessageBox.html - QListWidget
https://pyside.github.io/docs/pyside/PySide/QtGui/QListWidget.html - Signals & Slots
http://doc.qt.io/qt-4.8/signalsandslots.html - Signals and Slots in PySide
http://wiki.qt.io/Signals_and_Slots_in_PySide - Intro to PySide/PyQt: Basic Widgets and Hello, World!
http://www.pythoncentral.io/intro-to-pysidepyqt-basic-widgets-and-hello-world/ - QWidget
https://pyside.github.io/docs/pyside/PySide/QtGui/QWidget.html - QMainWindow
https://pyside.github.io/docs/pyside/PySide/QtGui/QMainWindow.html - QLabel
https://pyside.github.io/docs/pyside/PySide/QtGui/QLabel.html - QAbstractButton
https://pyside.github.io/docs/pyside/PySide/QtGui/QAbstractButton.html - QCheckBox
https://pyside.github.io/docs/pyside/PySide/QtGui/QCheckBox.html - QRadioButton
https://pyside.github.io/docs/pyside/PySide/QtGui/QRadioButton.html - QButtonGroup
https://pyside.github.io/docs/pyside/PySide/QtGui/QButtonGroup.html - QFrame
https://pyside.github.io/docs/pyside/PySide/QtGui/QFrame.html#PySide.QtGui.PySide.QtGui.QFrame - QFrame.frameStyle
https://pyside.github.io/docs/pyside/PySide/QtGui/QFrame.html#PySide.QtGui.PySide.QtGui.QFrame.frameStyle - Leo editor
http://leoeditor.com/ - IPython Qt Console aneb vylepšený pseudoterminál
https://mojefedora.cz/integrovana-vyvojova-prostredi-ve-fedore-ipython-a-ipython-notebook/#k06 - Vývojová prostředí ve Fedoře (4. díl)
https://mojefedora.cz/vyvojova-prostredi-ve-fedore-4-dil/ - Seriál Letní škola programovacího jazyka Logo
http://www.root.cz/serialy/letni-skola-programovaciho-jazyka-logo/ - Educational programming language
http://en.wikipedia.org/wiki/Educational_programming_language - Logo Tree Project:
http://www.elica.net/download/papers/LogoTreeProject.pdf - Hra Breakout napísaná v Tkinteri
https://www.root.cz/clanky/hra-breakout-napisana-v-tkinteri/ - Hra Snake naprogramovaná v Pythone s pomocou Tkinter
https://www.root.cz/clanky/hra-snake-naprogramovana-v-pythone-s-pomocou-tkinter/ - 24.1. turtle — Turtle graphics
https://docs.python.org/3.5/library/turtle.html#module-turtle - TkDND
http://freecode.com/projects/tkdnd - Python Tkinter Fonts
https://www.tutorialspoint.com/python/tk_fonts.htm - The Tkinter Canvas Widget
http://effbot.org/tkinterbook/canvas.htm - Ovládací prvek (Wikipedia)
https://cs.wikipedia.org/wiki/Ovl%C3%A1dac%C3%AD_prvek_%28po%C4%8D%C3%ADta%C4%8D%29 - Rezervovaná klíčová slova v Pythonu
https://docs.python.org/3/reference/lexical_analysis.html#keywords - TkDocs: Styles and Themes
http://www.tkdocs.com/tutorial/styles.html - Drawing in Tkinter
http://zetcode.com/gui/tkinter/drawing/ - Changing ttk widget text color (StackOverflow)
https://stackoverflow.com/questions/16240477/changing-ttk-widget-text-color - The Hitchhiker's Guide to Pyhton: GUI Applications
http://docs.python-guide.org/en/latest/scenarios/gui/ - 7 Top Python GUI Frameworks for 2017
http://insights.dice.com/2014/11/26/5-top-python-guis-for-2015/ - GUI Programming in Python
https://wiki.python.org/moin/GuiProgramming - Cameron Laird's personal notes on Python GUIs
http://phaseit.net/claird/comp.lang.python/python_GUI.html - Python GUI development
http://pythoncentral.io/introduction-python-gui-development/ - Graphic User Interface FAQ
https://docs.python.org/2/faq/gui.html#graphic-user-interface-faq - TkInter
https://wiki.python.org/moin/TkInter - Tkinter 8.5 reference: a GUI for Python
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/index.html - TkInter (Wikipedia)
https://en.wikipedia.org/wiki/Tkinter - appJar
http://appjar.info/ - appJar (Wikipedia)
https://en.wikipedia.org/wiki/AppJar - appJar na Pythonhosted
http://pythonhosted.org/appJar/ - appJar widgets
http://appjar.info/pythonWidgets/ - Stránky projektu PyGTK
http://www.pygtk.org/ - PyGTK (Wikipedia)
https://cs.wikipedia.org/wiki/PyGTK - Stránky projektu PyGObject
https://wiki.gnome.org/Projects/PyGObject - Stránky projektu Kivy
https://kivy.org/#home - Stránky projektu PyQt
https://riverbankcomputing.com/software/pyqt/intro - PyQt (Wikipedia)
https://cs.wikipedia.org/wiki/PyGTK - Stránky projektu PySide
https://wiki.qt.io/PySide - PySide (Wikipedia)
https://en.wikipedia.org/wiki/PySide - Stránky projektu Kivy
https://kivy.org/#home - Kivy (framework, Wikipedia)
https://en.wikipedia.org/wiki/Kivy_(framework) - QML Applications
http://doc.qt.io/qt-5/qmlapplications.html - KDE
https://www.kde.org/ - Qt
https://www.qt.io/ - GNOME
https://en.wikipedia.org/wiki/GNOME - Category:Software that uses PyGTK
https://en.wikipedia.org/wiki/Category:Software_that_uses_PyGTK - Category:Software that uses PyGObject
https://en.wikipedia.org/wiki/Category:Software_that_uses_PyGObject - Category:Software that uses wxWidgets
https://en.wikipedia.org/wiki/Category:Software_that_uses_wxWidgets - GIO
https://developer.gnome.org/gio/stable/ - GStreamer
https://gstreamer.freedesktop.org/ - GStreamer (Wikipedia)
https://en.wikipedia.org/wiki/GStreamer - Wax Gui Toolkit
https://wiki.python.org/moin/Wax - Python Imaging Library (PIL)
http://infohost.nmt.edu/tcc/help/pubs/pil/ - 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/