Obsah
2. Vytvoření štětce (brush) z externího rastrového obrázku (bitmapy i pixmapy)
3. První demonstrační příklad – vytvoření štětce z externí pixmapy
4. Druhý demonstrační příklad – vytvoření štětce z externí bitmapy
5. Gradientní barevné přechody využitelné při kreslení vyplněných obrazců
6. Třetí demonstrační příklad – použití lineárního gradientu ve funkci štětce
7. Čtvrtý demonstrační příklad – opakující se lineární přechod (pruhy)
8. Pátý demonstrační příklad – radiální barevný přechod
9. 2D entity, které je možné vykreslit s využitím třídy QPainter
11. Obdélníky, kružnice a elipsy
12. Obdélníky se zaoblenými rohy
13. Oblouky, kruhové výseče a kruhové úseče
14. Šestý demonstrační příklad – vykreslení „jednoduchých“ 2D entit
15. Polyčáry (lomené čáry) a polygony (mnohoúhelníky)
16. Objekt typu QPolygon, přetížený operátor << pro konstrukci polygonu
17. Sedmý demonstrační příklad – vykreslení domku jednou lomenou čarou (polyčarou)
18. Osmý demonstrační příklad – vykreslení hvězdy s využitím polygonu
19. Repositář s demonstračními příklady
1. Tvorba GUI v Pythonu s využitím frameworku PySide: pokročilejší grafické operace používající třídu QPainter
V předchozím článku jsme se seznámili se způsobem vykreslování 2D entit s využitím třídy QPainter, která programátorům nabízí relativně vysokoúrovňový přístup k tvorbě grafiky, na rozdíl od třídy QImage, kde jsme byli omezeni na změnu barev jednotlivých pixelů, popř. na vyplnění oblasti konstantní barvou. Připomeňme si jen krátce, že QPainter je třída, která pouze realizuje vykreslovací algoritmy, ovšem vykreslování je nutné provádět na nějaké „plátno“. To může být představováno například instancí třídy QPixmap apod. QPainter navíc umožňuje změnu stylu vykreslování obrysů entit a stylu výplně vnitřku uzavřených entit (u liniových entit, tj. například úseček či oblouků, samozřejmě vnitřek neexistuje a není vyplňován). Styl vykreslování je řízen stavem dvou objektů, které mohou být přiřazeny instanci třídy QPainter:
- Pero (pen) je nástroj použitý při vykreslování obrysu tvarů a samozřejmě i všech liniových tvarů (úsečky, oblouky, …).
- Štětec (brush) je nástroj použitý pro vyplnění uzavřených tvarů (obdélník, polygon, elipsa, cesta).
Obrázek 1: Různé vzorky čar (zvětšeno 2×).
Minule jsme se již s těmito nástroji setkali, takže již víme, jakým způsobem je možné změnit barvu, šířku i styl obrysů 2D entit (vlastnosti pera) a taktéž jsme se seznámili se základními styly výplně (vlastnosti štětce). Dnes si ukážeme, jak je možné styl výplně načíst z externí bitmapy (vzorek) či pixmapy (textura), popř. jak se používají gradientní (barevné) přechody pro vyplnění jednotlivých 2D entit. Následně se seznámíme se všemi 2D entitami nabízenými třídou QPainter, a to včetně entit s proměnným počtem vrcholů.
Obrázek 2: Vzorník standardních štětců, které již jsou ve frameworku PySide připraveny.
2. Vytvoření štětce (brush) z externího rastrového obrázku (bitmapy i pixmapy)
Kromě vzorků štětců, které jsou součástí frameworku PySide, je možné vytvořit nový vzorek s využitím bitmapy nebo pixmapy. Pokud se použije bitmapa, tj. rastrový obrázek, kde je každý pixel reprezentován jen jediným bitem, je nutné nastavit barvu vzorku, a to přímo při konstrukci štětce. U pixmap to samozřejmě není nutné, protože pixmapy jsou reprezentovány buď obrázky s barvovou paletou či obrázky plnobarevnými (truecolor).
Nový štětec se z bitmapy vytvoří takto:
brush = QtGui.QBrush(barva, bitmapa)
U štětce tvořeného z pixmapy se nemusí specifikovat barva:
brush = QtGui.QBrush(pixmapa)
Obrázek 3: Bitmapa, kterou v dalším příkladech použijeme jako podklad pro vzorek štětce. V praxi se sice setkáme spíše se skutečnými opakujícími se vzorky, my si však potřebujeme ukázat, jak se podklad posouvá či naopak neposouvá společně s vykreslovanými 2D entitami.
Bitmapu nebo pixmapu je možné načíst z externích souborů. To se provede jednoduše předáním cesty a jména souboru s rastrovým obrázkem do konstruktoru QBitmap(filename) popř. do konstruktoru QPixmap(filename). Podporovány jsou tyto formáty rastrových obrázků:
Koncovka souboru | Formát |
---|---|
.bmp | Windows Bitmap |
.gif | Graphic Interchange Format |
.jpg, .jpeg | Joint Photographic Experts Group |
.png | Portable Network Graphics |
.pbm | Portable Bitmap |
.pgm | Portable Graymap |
.ppm | Portable Pixmap |
.xbm | X11 Bitmap |
.xpm | X11 Pixmap |
Obrázek 4: Pixmapa, kterou v dalších příkladech taktéž použijeme jako podklad pro vzorek štětce.
3. První demonstrační příklad – vytvoření štětce z externí pixmapy
V dnešním prvním demonstračním příkladu je ukázán jeden ze způsobů vytvoření stylu štětce z externí pixmapy (barevného obrázku). Samotné načtení vzorku/textury, která bude použita při vyplňování 2D entit, je realizováno ve funkci createBrushFromPixmap:
# vytvoření štětce z pixmapy def createBrushFromPixmap(filename): pixmap = QtGui.QPixmap(filename) return QtGui.QBrush(pixmap)
Posléze je možné texturu štětce jednoduše načíst:
brush = createBrushFromPixmap("pixmaps/voronoi.png")
a následně použít při vykreslení uzavřeného obrazce:
# vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(pen_width) qPainter.setPen(pen) # změna tvaru štětce qPainter.setBrush(brush) # vykreslení obdélníku qPainter.drawRect(x, y, width, height)
Povšimněte si toho, jak na sebe navazují vnitřní plochy jednotlivých 2D obrazců vyplněných stejnou texturou – pokud bychom animovali posun jednoho čtverce pouhou změnou jeho souřadnic, vnitřní vzorek by se neposouval:
Obrázek 5: Několik čtverců, jejichž vnitřní plocha používá totožnou texturu.
Úplný zdrojový kód prvního příkladu je umístěn pod tento odstavec:
#!/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) # funkce pro vykreslení obdélníku zadanou barvou a se specifikovaným štětcem def drawRectangleUsingCustomBrush(qPainter, color, x, y, width, height, brush, pen_width=0): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(pen_width) qPainter.setPen(pen) # změna tvaru štětce qPainter.setBrush(brush) # vykreslení obdélníku qPainter.drawRect(x, y, width, height) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 250 IMAGE_HEIGHT = 170 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) brush = createBrushFromPixmap("pixmaps/voronoi.png") # Vykreslení obdélníků různým stylem drawRectangleUsingCustomBrush(qp, YELLOW, 10, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, RED, 90, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLUE, 170, 10, 70, 70, brush) # barva pozadi a povoleni vykreslení pozadi (vyplne) qp.setBackgroundMode(QtCore.Qt.BGMode.OpaqueMode) qp.setBackground(QtGui.QColor(*BLUE)) drawRectangleUsingCustomBrush(qp, WHITE, 10, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 90, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 170, 90, 70, 70, brush) # 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()
4. Druhý demonstrační příklad – vytvoření štětce z externí bitmapy
Ve druhém příkladu je ukázáno použití bitmapy pro vytvoření jednobarevného nebo dvoubarevného vzorku. Povšimněte si jedné změny oproti předchozímu příkladu – vzhledem k tomu, že některé formáty bitmap neobsahují informace o barvě, ale jen vlastní bitový vzorek, je nutné specifikovat barvu „zapnutých“ pixelů, a to ideálně přímo v konstruktoru třídy QBrush:
# vytvoření štětce z bitmapy def createBrushFromBitmap(color, filename): bitmap = QtGui.QBitmap(filename) c = QtGui.QColor(*color) return QtGui.QBrush(c, bitmap)
Takto připravenou funkci použijeme pro načtení bitmapy uložené ve formátu XBM (X BitMap), což je dnes již sice dosti zastaralý formát, ale mnohdy se ještě setkáme s ikonami i výplňovými vzorky uloženými právě v tomto formátu:
WHITE = (255, 255, 255) brush = createBrushFromBitmap(WHITE, "bitmaps/test.xbm")
Použití takto vytvořeného štětce se již nijak neliší od předchozího příkladu:
Obrázek 6: Několik čtverců, jejichž vnitřní plocha používá totožný bitový vzorek. Opět si povšimněte faktu, že se vzorek neposunuje společně s vlastní plochou čtverců.
Následuje výpis zdrojového kódu dnešního druhého demonstračního 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 # vytvoření štětce z bitmapy def createBrushFromBitmap(color, filename): bitmap = QtGui.QBitmap(filename) c = QtGui.QColor(*color) return QtGui.QBrush(c, bitmap) # funkce pro vykreslení obdélníku zadanou barvou a se specifikovaným štětcem def drawRectangleUsingCustomBrush(qPainter, color, x, y, width, height, brush, pen_width=0): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(pen_width) qPainter.setPen(pen) # změna tvaru štětce qPainter.setBrush(brush) # vykreslení obdélníku qPainter.drawRect(x, y, width, height) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 330 IMAGE_HEIGHT = 240 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) brush = createBrushFromBitmap(WHITE, "bitmaps/test.xbm") # Vykreslení obdélníků různým stylem drawRectangleUsingCustomBrush(qp, YELLOW, 10, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, RED, 90, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLUE, 170, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, MAGENTA, 250, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 10, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 90, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 170, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 250, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 10, 170, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 90, 170, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 170, 170, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 250, 170, 70, 70, brush) # 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. Gradientní barevné přechody využitelné při kreslení vyplněných obrazců
Další možnost vyplňování uzavřených tvarů spočívá v použití gradientích (barevných) přechodů. Existuje několik variant těchto přechodů, přičemž nejpoužívanější je lineární přechod (v libovolném směru) nebo radiální přechod. U lineárních přechodů je nutné specifikovat dva body a taktéž libovolné množství barev. Přechod je vypočten na úsečce ležící mezi těmito dvěma body – tím je určen jak směr přechodu, tak i nepřímo jeho šířka (čím jsou body blíže u sebe, tím je šířka přechodu menší). Barvy je možné specifikovat jak přímo v zadaných bodech (relativní souřadnice na úsečce budou v tomto případě rovny 0,0 a 1,0, nebo v libovolném místě zmíněné úsečky). Celkový počet zadaných barev není omezen na dvě, můžeme například vytvořit přechod mezi třemi barvami:
gradient = QtGui.QLinearGradient(x1, y1, x2, y2) # barva přesně v prvním bodu gradient.setColorAt(0.0, QtGui.QColor(barva1)) # barva v polovině přechodu gradient.setColorAt(0.5, QtGui.QColor(barva2)) # barva přesně v posledním bodu gradient.setColorAt(1.0, QtGui.QColor(barva3)) brush = QtGui.QBrush(gradient)
Obrázek 7: Lineární gradientní přechod se třemi barvami.
U gradientního přechodu je možné navíc nastavit režim opakování či zrcadlení, což je téma, které si ukážeme v demonstračním příkladu.
Obrázek 8: Lineární gradientní přechod se čtyřmi barvami.
U specifikace radiálního přechodu se zadává střed a poloměr kružnice, v níž k výpočtu změny barev dochází. Barvy se zadávají na úsečce ze středu směrem k zadané kružnici. Ani zde není počet barev omezen:
gradient = QtGui.QRadialGradient(cx, cy, radius) gradient.setColorAt(0.2, QtGui.QColor(barva1)) gradient.setColorAt(0.5, QtGui.QColor(barva1)) gradient.setColorAt(1.0, QtGui.QColor(barva3)) brush QtGui.QBrush(gradient)
Obrázek 9: Radiální gradientní přechod se čtyřmi barvami.
6. Třetí demonstrační příklad – použití lineárního gradientu ve funkci štětce
V dnešním třetím demonstračním příkladu je ukázána tvorba lineárního gradientního přechodu mezi dvěma barvami. Výsledek by měl vypadat následovně:
Obrázek 10: Lineární gradientní přechod se dvěma barvami vykreslený demonstračním příkladem.
Podívejme se nyní na to, jak vypadá zdrojový kód tohoto 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 # vytvoření štětce z gradientního přechodu def createBrushFromGradient(color1, color2): gradient = QtGui.QLinearGradient(100, 100, 200, 200) gradient.setColorAt(0.2, QtGui.QColor(*color1)) gradient.setColorAt(0.8, QtGui.QColor(*color2)) return QtGui.QBrush(gradient) # funkce pro vykreslení obdélníku zadanou barvou a se specifikovaným štětcem def drawRectangleUsingCustomBrush(qPainter, color, x, y, width, height, brush, pen_width=0): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(pen_width) qPainter.setPen(pen) # změna tvaru štětce qPainter.setBrush(brush) # vykreslení obdélníku qPainter.drawRect(x, y, width, height) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 330 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) brush = createBrushFromGradient(YELLOW, RED) # Vykreslení obdélníků různým stylem drawRectangleUsingCustomBrush(qp, YELLOW, 10, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, RED, 90, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLUE, 170, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, MAGENTA, 250, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 10, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 90, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 170, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 250, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 10, 170, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 90, 170, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 170, 170, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 250, 170, 70, 70, brush) # 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()
7. Čtvrtý demonstrační příklad – opakující se lineární přechod (pruhy)
V dalším demonstračním příkladu je ukázán vliv volby:
gradient.setSpread(QtGui.QGradient.Spread.ReflectSpread)
Tato volba způsobí, že se lineární přechod bude opakovat, ovšem každé sudé opakování bude navíc zrcadleno. Výsledek můžeme vidět na následujícím screenshotu:
Obrázek 11: Opakující se lineární gradientní přechod se dvěma barvami.
Opět se podívejme na zdrojový kód příkladu. Ten se odlišuje od příkladu předchozího prakticky jen odlišným nastavením přechodu, volbou jiných barev a takovou úpravou obou bodů přechodu, aby začátek přechodu korespondoval s velikostí vykreslovaných čtverců:
#!/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 gradientního přechodu def createBrushFromGradient(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) # funkce pro vykreslení obdélníku zadanou barvou a se specifikovaným štětcem def drawRectangleUsingCustomBrush(qPainter, color, x, y, width, height, brush, pen_width=0): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(pen_width) qPainter.setPen(pen) # změna tvaru štětce qPainter.setBrush(brush) # vykreslení obdélníku qPainter.drawRect(x, y, width, height) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 330 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) brush = createBrushFromGradient(WHITE, BLUE) # Vykreslení obdélníků různým stylem drawRectangleUsingCustomBrush(qp, YELLOW, 10, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, RED, 90, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLUE, 170, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, MAGENTA, 250, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 10, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 90, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 170, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 250, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 10, 170, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 90, 170, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 170, 170, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 250, 170, 70, 70, brush) # 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. Pátý demonstrační příklad – radiální barevný přechod
V pátém příkladu je ukázán způsob použití radiálního barevného přechodu. Střed přechodu je umístěn na souřadnice [85, 85] a jeho poloměr je nastaven na 100 délkových jednotek. Navíc je povoleno opakování přechodu se zrcadlením (schválně si zkuste zakomentovat volání metody setSpread()):
gradient = QtGui.QRadialGradient(85, 85, 100) gradient.setColorAt(0.2, QtGui.QColor(*color1)) gradient.setColorAt(1.0, QtGui.QColor(*color2)) gradient.setSpread(QtGui.QGradient.Spread.ReflectSpread) brush = QtGui.QBrush(gradient)
Obrázek 12: Opakující se radiální barevný přechod se dvěma barvami vykreslený 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 # vytvoření štětce z gradientního přechodu def createBrushFromGradient(color1, color2): gradient = QtGui.QRadialGradient(85, 85, 100) gradient.setColorAt(0.2, QtGui.QColor(*color1)) gradient.setColorAt(1.0, QtGui.QColor(*color2)) gradient.setSpread(QtGui.QGradient.Spread.ReflectSpread) return QtGui.QBrush(gradient) # funkce pro vykreslení obdélníku zadanou barvou a se specifikovaným štětcem def drawRectangleUsingCustomBrush(qPainter, color, x, y, width, height, brush, pen_width=0): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) # změna šířky pera pen.setWidth(pen_width) qPainter.setPen(pen) # změna tvaru štětce qPainter.setBrush(brush) # vykreslení obdélníku qPainter.drawRect(x, y, width, height) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 330 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) brush = createBrushFromGradient(YELLOW, BLACK) # Vykreslení obdélníků různým stylem drawRectangleUsingCustomBrush(qp, YELLOW, 10, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, RED, 90, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLUE, 170, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, MAGENTA, 250, 10, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 10, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 90, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 170, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, WHITE, 250, 90, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 10, 170, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 90, 170, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 170, 170, 70, 70, brush) drawRectangleUsingCustomBrush(qp, BLACK, 250, 170, 70, 70, brush) # 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()
9. 2D entity, které je možné vykreslit s využitím třídy QPainter
Třída QPainter programátorům nabízí možnost vykreslení následujících tvarů a objektů:
Tvar/objekt | Metoda |
---|---|
bod | drawPoint() |
sada bodů | drawPoints() |
úsečka | drawLine() |
sada úseček | drawLines() |
obdélník | drawRect(), fillRect() |
sada obdélníků | drawRects() |
obdélník se zakulacenými rohy | drawRoundRect(), drawRoundedRect() |
polyčára (lomená úsečka) | drawPolyline() |
polygon | drawPolygon(), drawConvexPolygon() |
oblouk | drawArc() |
kruhová úseč | drawChord() |
kruhová výseč | drawPie() |
kružnice nebo elipsa | drawEllipse() |
obecná cesta | drawPath(), fillPath() |
text | drawText(), drawTextItem() |
rastrový obrázek | drawImage(), drawPicture(), drawPixmap(), … |
V dalších kapitolách se postupně s jednotlivými entitami seznámíme (s výjimkou textu a cest; těm bude věnován samostatný článek).
10. Body a úsečky
Body se vykreslují buď pomocí metody drawPoint() nebo (pokud potřebujeme vykreslit větší množství bodů současně) s využitím metody drawPoints(). Na tomto místě je vhodné si uvědomit, že se body odlišují od pixelů, protože pixel je dále nedělitelná diskrétní jednotka, ale bod může být vykreslen s využitím stop různé velikosti a tvaru (stopa se nastavuje pomocí atributů pera). Následující kód vykreslí náhodné body v dané oblasti:
# funkce pro vykreslení bodu zadanou barvou def drawPoint(qPainter, color, x, y): setColor(qPainter, color) # vykreslení jediného bodu qPainter.drawPoint(x, y) for _ in range(250): x = random.uniform(90, 160) y = random.uniform(10, 80) drawPoint(qp, WHITE, x, y)
S úsečkami jsme se již setkali minule, takže si jen krátce uvedeme funkci pro vykreslení úsečky specifikovanou barvou:
# funkce pro vykreslení úsečky zadanou barvou def drawLine(qPainter, color, x1, y1, x2, y2): setColor(qPainter, color) # vykreslení úsečky qPainter.drawLine(x1, y1, x2, y2)
Obrázek 13: Úsečka a náhodně vykreslené body.
11. Obdélníky, kružnice a elipsy
Obdélníky i elipsy jsou specifikovány stejným způsobem, konkrétně souřadnicí levého horního rohu, šířkou a výškou. Pokud je šířka a výška shodná, vykreslí se čtverec popř. kružnice. Podívejme se na příklad:
# funkce pro vykreslení obdélníku zadanou barvou def drawRectangle(qPainter, color, x, y, width, height): setColor(qPainter, color) # vykreslení obdélníku qPainter.drawRect(x, y, width, height) # funkce pro vykreslení elipsy zadanou barvou def drawEllipse(qPainter, color, x, y, width, height): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) qPainter.setPen(pen) # vykreslení elipsy qPainter.drawEllipse(x, y, width, height) # funkce pro vykreslení kružnice zadanou barvou def drawCircle(qPainter, color, cx, cy, radius): setColor(qPainter, color) # vykreslení kružnice qPainter.drawEllipse(cx-radius, cy-radius, 2*radius, 2*radius)
Pro kreslení kružnic jsou zvoleny odlišné parametry, konkrétně souřadnice středu a poloměr. Podobně je samozřejmě možné upravit i funkci pro kreslení elipsy, pouze se musí specifikovat poloměr obou poloos.
Obrázek 14: Obdélník (resp. čtverec jako speciální typ obdélníku), kružnice a dvě elipsy.
12. Obdélníky se zaoblenými rohy
Obdélníky se zaoblenými rohy se vykreslují metodou drawRoundRect() a drawRoundedRect(). Kromě souřadnice jednoho vrcholu obdélníku a jeho rozměrů se těmto metodám předávají i poloměry rohů (v horizontálním i vertikálním směru). Z dalšího příkladu si povšimněte, že pokud je poloměr příliš velký, stává se ze čtverce kružnice:
# funkce pro vykreslení obdélníku zadanou barvou a se zaoblenými rohy def drawRoundedRectangle(qPainter, color, x, y, width, height, r): setColor(qPainter, color) # vykreslení obdélníku qPainter.drawRoundedRect(x, y, width, height, r, r) drawRoundedRectangle(qp, MAGENTA, 10, 170, 70, 70, 1) drawRoundedRectangle(qp, MAGENTA, 90, 170, 70, 70, 10) drawRoundedRectangle(qp, MAGENTA, 170, 170, 70, 70, 20) drawRoundedRectangle(qp, MAGENTA, 250, 170, 70, 70, 1000)
Obrázek 15: Čtverec se zaoblenými rohy; poloměr zaoblení se zleva doprava postupně zvětšuje.
13. Oblouky, kruhové výseče a kruhové úseče
Pro kreslení oblouků, kruhových výsečí a kruhových úsečí se používají velmi podobné metody, které se od sebe odlišují pouze jménem. U všech tří metod se opět specifikuje souřadnice jednoho z rohů obalového obdélníku následovaná šířkou a výškou tohoto obdélníku. Navíc však ještě musíme specifikovat počáteční úhel oblouku a jeho délku. Obě tyto hodnoty se zadávají ve stupních vynásobených konstantou 16 (tento způsob specifikace úhlů odpovídá konceptům, na nichž je postavena knihovna Qt):
# funkce pro vykreslení oblouku zadanou barvou def drawArc(qPainter, color, cx, cy, radius, angle, span): setColor(qPainter, color) # vykreslení kružnice qPainter.drawArc(cx-radius, cy-radius, 2*radius, 2*radius, 16*angle, 16*span) # funkce pro vykreslení kruhové výseče zadanou barvou def drawPie(qPainter, color, cx, cy, radius, angle, span): setColor(qPainter, color) # vykreslení kruhové výseče qPainter.drawPie(cx-radius, cy-radius, 2*radius, 2*radius, 16*angle, 16*span) # funkce pro vykreslení kruhové úseče zadanou barvou def drawChord(qPainter, color, cx, cy, radius, angle, span): setColor(qPainter, color) # vykreslení kruhové úseče qPainter.drawChord(cx-radius, cy-radius, 2*radius, 2*radius, 16*angle, 16*span)
Obrázek 16: Oblouky, kruhové výseče a kruhové úseče.
14. Šestý demonstrační příklad – vykreslení „jednoduchých“ 2D entit
Všechny typy entit popsaných v předchozích čtyřech kapitolách jsou vykresleny dnešním šestým příkladem, což je ostatně patrné z následujícího screenshotu:
Obrázek 17: Všechny „jednoduché“ 2D entity podporované třídou QPainter.
Opět se podívejme na úplný zdrojový kód tohoto příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys import math import random # 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) # funkce pro vykreslení bodu zadanou barvou def drawPoint(qPainter, color, x, y): setColor(qPainter, color) # vykreslení jediného bodu qPainter.drawPoint(x, y) # funkce pro vykreslení úsečky zadanou barvou def drawLine(qPainter, color, x1, y1, x2, y2): setColor(qPainter, color) # vykreslení úsečky qPainter.drawLine(x1, y1, x2, y2) # funkce pro vykreslení obdélníku zadanou barvou def drawRectangle(qPainter, color, x, y, width, height): setColor(qPainter, color) # vykreslení obdélníku qPainter.drawRect(x, y, width, height) # funkce pro vykreslení obdélníku zadanou barvou a se zaoblenými rohy def drawRoundedRectangle(qPainter, color, x, y, width, height, r): setColor(qPainter, color) # vykreslení obdélníku qPainter.drawRoundedRect(x, y, width, height, r, r) # funkce pro vykreslení elipsy zadanou barvou def drawEllipse(qPainter, color, x, y, width, height): # vytvoření pera a nastavení barvy kreslení pen = QtGui.QPen(QtGui.QColor(*color)) qPainter.setPen(pen) # vykreslení elipsy qPainter.drawEllipse(x, y, width, height) # funkce pro vykreslení kružnice zadanou barvou def drawCircle(qPainter, color, cx, cy, radius): setColor(qPainter, color) # vykreslení kružnice qPainter.drawEllipse(cx-radius, cy-radius, 2*radius, 2*radius) # funkce pro vykreslení oblouku zadanou barvou def drawArc(qPainter, color, cx, cy, radius, angle, span): setColor(qPainter, color) # vykreslení kružnice qPainter.drawArc(cx-radius, cy-radius, 2*radius, 2*radius, 16*angle, 16*span) # funkce pro vykreslení kruhové výseče zadanou barvou def drawPie(qPainter, color, cx, cy, radius, angle, span): setColor(qPainter, color) # vykreslení kruhové výseče qPainter.drawPie(cx-radius, cy-radius, 2*radius, 2*radius, 16*angle, 16*span) # funkce pro vykreslení kruhové úseče zadanou barvou def drawChord(qPainter, color, cx, cy, radius, angle, span): setColor(qPainter, color) # vykreslení kruhové úseče qPainter.drawChord(cx-radius, cy-radius, 2*radius, 2*radius, 16*angle, 16*span) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 330 IMAGE_HEIGHT = 520 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) # Vykreslení různých 2D entit drawLine(qp, GREEN, 10, 10, 80, 80) for _ in range(250): x = random.uniform(90, 160) y = random.uniform(10, 80) drawPoint(qp, WHITE, x, y) drawRectangle(qp, YELLOW, 10, 90, 70, 70) drawCircle(qp, RED, 125, 125, 35) drawEllipse(qp, CYAN, 170, 30+80, 70, 35) drawEllipse(qp, BLUE, 268, 10+80, 35, 70) drawRoundedRectangle(qp, MAGENTA, 10, 170, 70, 70, 1) drawRoundedRectangle(qp, MAGENTA, 90, 170, 70, 70, 10) drawRoundedRectangle(qp, MAGENTA, 170, 170, 70, 70, 20) drawRoundedRectangle(qp, MAGENTA, 250, 170, 70, 70, 1000) drawArc(qp, CYAN, 10+35, 260+35, 35, 0, 90) drawArc(qp, CYAN, 90+35, 260+35, 35, 45, 90) drawArc(qp, CYAN, 170+35, 260+35, 35, 45, 180) drawArc(qp, CYAN, 250+35, 260+35, 35, 45, 270) drawPie(qp, YELLOW, 10+35, 350+35, 35, 0, 90) drawPie(qp, YELLOW, 90+35, 350+35, 35, 45, 90) drawPie(qp, YELLOW, 170+35, 350+35, 35, 45, 180) drawPie(qp, YELLOW, 250+35, 350+35, 35, 45, 270) drawChord(qp, GREEN, 10+35, 440+35, 35, 0, 90) drawChord(qp, GREEN, 90+35, 440+35, 35, 45, 90) drawChord(qp, GREEN, 170+35, 440+35, 35, 45, 180) drawChord(qp, GREEN, 250+35, 440+35, 35, 45, 270) # 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()
15. Polyčáry (lomené čáry) a polygony (mnohoúhelníky)
Některé typy 2D entit, zejména lomené čáry a polygony (mnohoúhelníky), mohou být deklarovány pomocí takřka libovolného množství vrcholů. Z tohoto důvodu se při vykreslování těchto entit využívá datová struktura nazvaná QPolygon a vykreslení lomené čáry a mnohoúhelníku bude vypadat následovně:
# funkce pro vykreslení lomené čáry zadanou barvou def drawPolyline(qPainter, color, polygon): setColor(qPainter, color) # vykreslení lomené čáry qPainter.drawPolyline(polygon)
a:
# funkce pro vykreslení polygonu zadanou barvou def drawPolygon(qPainter, color, brush, polygon, fillrule): setColor(qPainter, color) # změna tvaru štětce qPainter.setBrush(brush) # vykreslení polygonu qPainter.drawPolygon(polygon, fillrule)
Povšimněte si, že u polygonu je možné nastavit režim vyplňování – buď se vyplní celá plocha polygonu nebo pouze ta část, která je označena jako vnitřní algoritmem sudý-lichý. Tento algoritmus pro libovolný bod X vytvoří paprsek z tohoto bodu do nekonečna a zjišťuje počet průsečíků paprsku s hranami objektu. Pokud je počet průsečíků lichý, leží bod X uvnitř objektu, jinak leží vně. Rozdíl uvidíme při kresbě hvězdy.
16. Objekt typu QPolygon, přetížený operátor << pro konstrukci polygonu
Objekt typu QPolygon obsahuje souřadnice vrcholů reprezentované datovou strukturou QPoint. To znamená, že pokud máme sekvenci dvojic [x,y], můžeme z této sekvence vytvořit objekt typu QPolygon relativně snadno, například takto:
# vytvoření polygonu ze sekvence koordinát [x,y] def createPolygon(coordinatesSequence): polygon = QtGui.QPolygon() for coordinates in coordinatesSequence: p = QtCore.QPoint(coordinates[0], coordinates[1]) polygon.append(p) return polygon
Existuje však ještě jednodušší způsob, protože pro objekt QPolygon byl přetížen operátor <<, který taktéž realizuje metodu append(). Funkci tedy můžeme přepsat i následujícím (pro někoho čitelnějším) způsobem:
# vytvoření polygonu ze sekvence koordinát [x,y] def createPolygon(coordinatesSequence): polygon = QtGui.QPolygon() for coordinates in coordinatesSequence: p = QtCore.QPoint(coordinates[0], coordinates[1]) polygon << p return polygon
17. Sedmý demonstrační příklad – vykreslení domku jednou lomenou čarou (polyčarou)
V dnešním sedmém demonstračním příkladu je ukázáno, jakým způsobem je možné vykreslit domek jednou lomenou čarou neboli polyčarou. Postup je následující:
Vytvoříme polygon představující jednotlivé body, které se budou spojovat tak, aby vznikl domek:
polygon = createPolygon([[100, 200], [200, 200], [100, 100], [100, 200], [200, 100], [100, 100], [150, 50], [200, 100], [200, 200]])
Vykreslíme polyčáru:
# 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) # vykreslení lomené čáry qPainter.drawPolyline(polygon)
Výsledek by měl vypadat takto:
Obrázek 18: Domek vykreslený jedním tahem.
Následuje výpis celého zdrojového kódu demonstračního 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í polygonu ze sekvence koordinát [x,y] def createPolygon(coordinatesSequence): polygon = QtGui.QPolygon() for coordinates in coordinatesSequence: p = QtCore.QPoint(coordinates[0], coordinates[1]) polygon.append(p) return polygon # funkce pro vykreslení lomené čáry zadanou barvou def drawPolyline(qPainter, color, polygon): setColor(qPainter, color) # vykreslení lomené čáry qPainter.drawPolyline(polygon) # 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) # Vykreslení různých 2D entit polygon = createPolygon([[100, 200], [200, 200], [100, 100], [100, 200], [200, 100], [100, 100], [150, 50], [200, 100], [200, 200]]) drawPolyline(qp, YELLOW, polygon) # 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()
18. Osmý demonstrační příklad – vykreslení hvězdy s využitím polygonu
V osmém a současně i dnešním posledním demonstračním příkladu je ukázán způsob vykreslení hvězdy jedním tahem, tentokrát s využitím 2D entity typu polygon (mnohoúhelník). Nejprve opět vytvoříme polygon představující vrcholy hvězdy. Vypomůžeme si funkcí, která vypočítá souřadnice n-tého vrcholu hvězdy. Ty jsou navzájem otočeny o 360°/5=72°, ovšem jednotlivé vrcholy se propojují ob jeden, tedy o 144°:
# 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)
Polygon s vrcholy hvězdy:
polygon = createPolygon([starVertex(120, 120, 100, 0), starVertex(120, 120, 100, 1), starVertex(120, 120, 100, 2), starVertex(120, 120, 100, 3), starVertex(120, 120, 100, 4)])
Následně hvězdu vykreslíme, a to s různým nastavením parametru fillrule:
qPainter.drawPolygon(polygon, QtCore.Qt.OddEvenFill) qPainter.drawPolygon(polygon, QtCore.Qt.WindingFill)
Výsledek by měl vypadat následovně:
Obrázek 19: Hvězdy vykreslené s využitím různých parametrů fillrule.
Následuje výpis zdrojového kódu tohoto 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í polygonu ze sekvence koordinát [x,y] def createPolygon(coordinatesSequence): polygon = QtGui.QPolygon() for coordinates in coordinatesSequence: p = QtCore.QPoint(coordinates[0], coordinates[1]) polygon << p return polygon # vytvoření štětce z pixmapy def createBrushFromPixmap(filename): pixmap = QtGui.QPixmap(filename) return QtGui.QBrush(pixmap) # funkce pro vykreslení polygonu zadanou barvou def drawPolygon(qPainter, color, brush, polygon, fillrule): setColor(qPainter, color) # změna tvaru štětce qPainter.setBrush(brush) # vykreslení polygonu qPainter.drawPolygon(polygon, fillrule) # 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) # nový widget bude odvozen od obecného hlavního okna class MainWindow(QtGui.QMainWindow): # rozměry rastrového obrázku IMAGE_WIDTH = 460 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) brush = createBrushFromPixmap("pixmaps/voronoi.png") # Vykreslení polygonu polygon = createPolygon([starVertex(120, 120, 100, 0), starVertex(120, 120, 100, 1), starVertex(120, 120, 100, 2), starVertex(120, 120, 100, 3), starVertex(120, 120, 100, 4)]) drawPolygon(qp, YELLOW, brush, polygon, QtCore.Qt.OddEvenFill) polygon = createPolygon([starVertex(330, 120, 100, 0), starVertex(330, 120, 100, 1), starVertex(330, 120, 100, 2), starVertex(330, 120, 100, 3), starVertex(330, 120, 100, 4)]) drawPolygon(qp, WHITE, brush, polygon, QtCore.Qt.WindingFill) # 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()
19. Repositář s demonstračními příklady
Zdrojové kódy všech osmi dnes popsaných demonstračních příkladů (plus jeden příklad zmíněný jen částečně) 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:
20. 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 - QPainterPath
https://pyside.github.io/docs/pyside/PySide/QtGui/QPainterPath.html - QGradient
https://pyside.github.io/docs/pyside/PySide/QtGui/QGradient.html - QLinearGradient
https://pyside.github.io/docs/pyside/PySide/QtGui/QLinearGradient.html - QRadialGradient
https://pyside.github.io/docs/pyside/PySide/QtGui/QRadialGradient.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/