GUI v Pythonu s PySide: rastrové operace, vykreslování textů

30. 1. 2018
Doba čtení: 34 minut

Sdílet

Dnes dokončíme popis vykreslovacích technik nabízených třídou QPainter. Nejdříve si ukážeme způsob určení oblastí vykreslování a použití bitových masek. Dále se budeme zabývat tzv. ROPs a závěr bude věnován popisu vykreslování textů.

Obsah

1. Tvorba GUI v×Pythonu s využitím frameworku PySide: rastrové operace, vykreslování textů

2. Určení oblasti (regionu), která se může překreslit

3. První demonstrační příklad: vykreslování do oblasti ohraničené obdélníkem

4. Maskování vykreslování s využitím bitmapy (stencil)

5. Druhý demonstrační příklad: vykreslování do pixelů specifikovaných bitovou maskou

6. Množinové operace prováděné nad regiony

7. Třetí demonstrační příklad: vytvoření nového regionu s využitím operace nonekvivalence

8. Rastrové operace vykonávané při vykreslování dvourozměrných entit

9. Čtvrtý demonstrační příklad: vzorník základních rastrových operací

10. Pokročilejší rastrové operace a role alfa kanálu při provádění těchto operací

11. Pátý demonstrační příklad: vzorník vybraných pokročilejších rastrových operací

12. Vykreslení textů

13. Osmý demonstrační příklad: vykreslení vycentrovaného textu

14. Devátý demonstrační příklad: nastavení stylu a velikosti fontu

15. Desátý demonstrační příklad: načtení fontu ze specifikované rodiny

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

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

18. Odkazy na Internetu

1. Tvorba GUI v Pythonu s využitím frameworku PySide: rastrové operace, vykreslování textů

Dnes dokončíme popis možností frameworku PySide v oblasti vykreslování dvourozměrné grafiky. Ukážeme si zejména způsob konfigurace vykreslování jednotlivých dvourozměrných entit, a to na úrovni pixelů tvořících hranici popř. i vnitřek entity. Při vykreslování se totiž postupuje zhruba následujícím způsobem:

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

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

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

2. Určení oblasti (regionu), která se může překreslit

První operací, kterou si dnes popíšeme, je určení oblasti (neboli regionu), která se může překreslit. Všechny pixely, které by se měly vykreslit mimo stanovenou oblast, budou ignorovány, tj. nijak neovlivní již nakreslenou scénu. Region je možné vytvořit například z obyčejného obdélníku, což je pravděpodobně nejčastěji používaná varianta. Nejprve se vytvoří instance objektu typu QRegion a posléze se tento objekt (resp. přesněji řečeno reference na něj) předá metodě QPainter.setClipRegion():

# vytvoření objektu typu QPainter s předáním
# reference na "pokreslovaný" objekt
qPainter = QtGui.QPainter(self.image)
 
# nastavení oblasti omezující vykreslování
region = QtGui.QRegion(QtCore.QRect(x, y, šířka, výška))
qPainter.setClipRegion(region)

Ve skutečnosti je samozřejmě možné, aby měl region i jiné tvary. V takovém případě namísto QRect použijte instanci třídy typu QPolygon či dále popsanou bitmapu (QBitmap).

3. První demonstrační příklad: vykreslování do oblasti ohraničené obdélníkem

Dnešní první demonstrační příklad je založen na příkladu, s nímž jsme se již seznámili v předchozích částech tohoto seriálu. Po jeho spuštění se vykreslí vzorník štětců, které jsou součástí frameworku PySide a které tedy můžeme použít bez nutnosti jejich explicitní deklarace:

# vykreslení obdélníků různým stylem
drawRectangleUsingBrush(qp, YELLOW, 10, 10, 50, 50, QtCore.Qt.SolidPattern)
drawRectangleUsingBrush(qp, YELLOW, 70, 10, 50, 50, QtCore.Qt.HorPattern)
...
...
...

Ovšem ve skutečnosti se vykreslí pouze vybraná oblast kreslicí plochy, protože před zahájením vlastního vykreslování nastavíme oblast, do které se má vykreslování provést. Okolní pixely, které do oblasti nezasahují, nebudou nijak modifikovány:

# nastavení oblasti omezující vykreslování
region = QtGui.QRegion(QtCore.QRect(20, 20, MainWindow.IMAGE_WIDTH-40, MainWindow.IMAGE_HEIGHT-50))
qp.setClipRegion(region)

Výsledek můžeme vidět na následujícím screenshotu:

Obrázek 1: Screenshot okna prvního demonstračního příkladu.

Následuje výpis zdrojového kódu:

#!/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
 
 
# funkce pro vykreslení obdélníku zadanou barvou a se specifikovaným štětcem
def drawRectangleUsingBrush(qPainter, color, x, y, width, height, brush_style, pen_width=0):
    # vytvoření pera a nastavení barvy kreslení
    pen = QtGui.QPen(QtGui.QColor(*color))
 
    # změna šířky pera
    pen.setWidth(pen_width)
    qPainter.setPen(pen)
 
    # změna tvaru štětce
    brush = QtGui.QBrush(QtGui.QColor(*color))
    brush.setStyle(brush_style)
    qPainter.setBrush(brush)
 
    # vykreslení obdélníku
    qPainter.drawRect(x, y, width, height)
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 440
    IMAGE_HEIGHT = 140
 
    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í 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)
 
        # nastavení oblasti omezující vykreslování
        region = QtGui.QRegion(QtCore.QRect(20, 20, MainWindow.IMAGE_WIDTH-40, MainWindow.IMAGE_HEIGHT-50))
        qp.setClipRegion(region)
 
        # vykreslení obdélníků různým stylem
        drawRectangleUsingBrush(qp, YELLOW, 10, 10, 50, 50, QtCore.Qt.SolidPattern)
        drawRectangleUsingBrush(qp, YELLOW, 70, 10, 50, 50, QtCore.Qt.HorPattern)
        drawRectangleUsingBrush(qp, YELLOW, 130, 10, 50, 50, QtCore.Qt.VerPattern)
        drawRectangleUsingBrush(qp, YELLOW, 190, 10, 50, 50, QtCore.Qt.CrossPattern)
        drawRectangleUsingBrush(qp, YELLOW, 250, 10, 50, 50, QtCore.Qt.BDiagPattern)
        drawRectangleUsingBrush(qp, YELLOW, 310, 10, 50, 50, QtCore.Qt.FDiagPattern)
        drawRectangleUsingBrush(qp, YELLOW, 370, 10, 50, 50, QtCore.Qt.DiagCrossPattern)
 
        drawRectangleUsingBrush(qp, WHITE, 10, 70, 50, 50, QtCore.Qt.Dense1Pattern)
        drawRectangleUsingBrush(qp, WHITE, 70, 70, 50, 50, QtCore.Qt.Dense2Pattern)
        drawRectangleUsingBrush(qp, WHITE, 130, 70, 50, 50, QtCore.Qt.Dense3Pattern)
        drawRectangleUsingBrush(qp, WHITE, 190, 70, 50, 50, QtCore.Qt.Dense4Pattern)
        drawRectangleUsingBrush(qp, WHITE, 250, 70, 50, 50, QtCore.Qt.Dense5Pattern)
        drawRectangleUsingBrush(qp, WHITE, 310, 70, 50, 50, QtCore.Qt.Dense6Pattern)
        drawRectangleUsingBrush(qp, WHITE, 370, 70, 50, 50, QtCore.Qt.Dense7Pattern)
 
        # 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. Maskování vykreslování s využitím bitmapy (stencil)

Mnohem zajímavější je použití běžné bitmapy pro určení těch pixelů, které je možné vykreslit, a těch, které jsou naopak maskovány. Jen pro připomínku – termínem bitmapa se v PySide označují rastrové obrázky, v nichž je každý pixel reprezentován jediným bitem, takže vidíme, že se skutečně jedná o bitovou masku. Již v úvodní kapitole jsme si řekli, že tato bitmapa bude pracovat jako takzvaný stencil buffer známý například z knihovny OpenGL (ve skutečnosti má stencil buffer větší možnosti konfigurace, ovšem toto téma již nezapadá do kontextu tohoto článku).

Obrázek 2: Bitmapa, která bude sloužit jako maska ve druhém příkladu.

Nastavení bitmapy ve funkci masky je poměrně snadné, pouze stačí bitmapu načíst a použít ji jako argument do konstruktoru třídy QRegion:

# vytvoření objektu typu QPainter s předáním
# reference na "pokreslovaný" objekt
qPainter = QtGui.QPainter(self.image)
 
# bitmapa tvořící masku regionu
bitmap = QtGui.QBitmap(cesta_k_bitmapě)
region = QtGui.QRegion(bitmap)
 
# nastavení oblasti vykreslování
qPainter.setClipRegion(region)

Obrázek 3: Toto není text, ale obdélník vyplněný gradientním přechodem. Při vykreslování byla použita bitová maska ze druhého obrázku.

5. Druhý demonstrační příklad: vykreslování do pixelů specifikovaných bitovou maskou

Ve druhém demonstračním příkladu vykreslíme do okna (prakticky přes celou jeho plochu) obdélník vyplněný opakujícím se gradientním přechodem od žluté barvy do barvy černé:

# výplň obdélníku
brush = createBrushFromGradient(YELLOW, BLACK)
 
# vykreslení obdélníku
drawRectangleUsingCustomBrush(qp, BLACK, 0, 0, 340, 240, brush)

Ovšem před vlastním vykreslením vyplněného obdélníku nastavíme bitovou masku, která vykreslení omezí pouze na vybrané pixely:

# bitmapa tvořící masku regionu
bitmap = QtGui.QBitmap("bitmaps/clip.xbm")
region = QtGui.QRegion(bitmap)
 
# nastavení oblasti vykreslování
qp.setClipRegion(region)

Výsledek jste mohli vidět na obrázcích uvedených v předchozí kapitole. Opět následuje výpis zdrojového kódu tohoto 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 gradientního přechodu
def createBrushFromGradient(color1, color2):
    gradient = QtGui.QLinearGradient(100, 100, 100, 160)
    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 = 340
    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)
 
        # bitmapa tvořící masku regionu
        bitmap = QtGui.QBitmap("bitmaps/clip.xbm")
        region = QtGui.QRegion(bitmap)
 
        # nastavení oblasti vykreslování
        qp.setClipRegion(region)
 
        # výplň obdélníku
        brush = createBrushFromGradient(YELLOW, BLACK)
 
        # vykreslení obdélníku
        drawRectangleUsingCustomBrush(qp, BLACK, 0, 0, 340, 240, 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()

6. Množinové operace prováděné nad regiony

V případě, že máme dvě instance třídy QRegion, tj. dvě oblasti vykreslování, můžeme z nich vytvořit třetí oblast s využitím vybrané množinové operace. Tyto operace se podobají množinovým operacím, s nimiž jsme se seznámili při popisování cest:

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

Vidíme, že je k dispozici ještě čtvrtá operace, kterou si ukážeme hned v dalším příkladu popsaném v navazující kapitole.

Obrázek 4: Tento obrázek vznikl jednoduše – zkombinovali jsme dva regiony, první ve tvaru obdélníku a druhý reprezentovaný nám již známou bitovou maskou. Kombinace byla provedena operací XOR, která vedla k inverzi bitové masky v levé třetině obrázku.

7. Třetí demonstrační příklad: vytvoření nového regionu s využitím operace nonekvivalence

Ve třetím příkladu si ukážeme použití množinových operací provedených nad dvěma regiony. První region je tvořen pečlivě vybranou obdélníkovou oblastí, která zasahuje zhruba do levé třetiny plochy:

region1 = QtGui.QRegion(QtCore.QRect(0, 0, 110, MainWindow.IMAGE_HEIGHT))

Druhý region je tvořen bitmapou, resp. přesněji řečeno bitovou maskou:

# bitmapa tvořící masku regionu
bitmap = QtGui.QBitmap("bitmaps/clip.xbm")
 
region2 = QtGui.QRegion(bitmap)

Oblast vykreslování je určena třetím regionem, který vznikne z předchozích dvou regionů jejich zkombinováním operací nonekvivalence (XOR):

# nastavení oblasti vykreslování s využitím operace XOR
qp.setClipRegion(region1 ^ region2)

Opět 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
 
 
# vytvoření štětce z gradientního přechodu
def createBrushFromGradient(color1, color2):
    gradient = QtGui.QLinearGradient(100, 100, 100, 160)
    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 = 340
    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)
 
        # bitmapa tvořící masku regionu
        bitmap = QtGui.QBitmap("bitmaps/clip.xbm")
 
        # vytvoření dvou regionů
        region1 = QtGui.QRegion(QtCore.QRect(0, 0, 110, MainWindow.IMAGE_HEIGHT))
        region2 = QtGui.QRegion(bitmap)
 
        # nastavení oblasti vykreslování s využitím operace XOR
        qp.setClipRegion(region1 ^ region2)
 
        # výplň obdélníku
        brush = createBrushFromGradient(YELLOW, BLACK)
 
        # vykreslení obdélníku
        drawRectangleUsingCustomBrush(qp, BLACK, 0, 0, MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT, 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. Rastrové operace vykonávané při vykreslování dvourozměrných entit

Již v úvodní kapitole jsme si řekli, že při vykreslování dvourozměrných entit jsou v průběhu rasterizace aplikovány rastrové operace, které kombinují barvu již uloženého (vykresleného) pixelu a pixelu vykreslovaného. Podporovány jsou následující operace:

Konstanta pro metodu setCompositionMode Význam
QPainter.RasterOp_SourceOrDestination bitová operace OR
QPainter.RasterOp_SourceAndDestination bitová operace AND
QPainter.RasterOp_SourceXorDestination bitová operace XOR
QPainter.RasterOp_NotSource­AndNotDestination bitová operace NOR
QPainter.RasterOp_NotSource­OrNotDestination bitová operace NAND
QPainter.RasterOp_NotSource­XorDestination (NOT src) XOR dst
QPainter.RasterOp_NotSource negace hodnoty původního pixelu
QPainter.RasterOp_NotSource­AndDestination (NOT src) AND dst
QPainter.RasterOp_SourceAn­dNotDestination src AND (NOT dst)

9. Čtvrtý demonstrační příklad: vzorník základních rastrových operací

Dnešní čtvrtý demonstrační příklad je poněkud delší, než příklady předchozí. Po jeho spuštění se na obrazovku vykreslí vzorník základních rastrových operací. Samotné vykreslování je založeno na uživatelské funkci nazvané twoOverlappingSquares, která skutečně vykreslí dva vyplněné čtverce, ovšem navíc před vykreslením druhého čtverce nastaví vybranou ROPs:

def twoOverlappingSquares(qPainter, color1, color2, x, y, compositionMode):
    # nastavení výchozího režimu míchání barev
    qPainter.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver)
 
    # první čtverec
    drawRectangleUsingBrush(qPainter, color1, x, y, 100, 100,
                            QtCore.Qt.SolidPattern)
 
    # nastavení režimu míchání barev
    qPainter.setCompositionMode(compositionMode)
 
    # druhý čtverec
    drawRectangleUsingBrush(qPainter, color2, x+50, y+50, 100, 100,
                            QtCore.Qt.SolidPattern)

Tato funkce je volána pro různé rastrové operace, takže na obrazovce skutečně uvidíme různé varianty kombinací barev pixelů dvou obdélníků:

# vykreslení sady překrývajících se čtverců
twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_1, ROW_1, QtGui.QPainter.CompositionMode_SourceOver)
twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_1, ROW_2, QtGui.QPainter.RasterOp_SourceOrDestination)
twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_2, ROW_2, QtGui.QPainter.RasterOp_SourceAndDestination)
twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_3, ROW_2, QtGui.QPainter.RasterOp_SourceXorDestination)
twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_1, ROW_3, QtGui.QPainter.RasterOp_NotSourceAndNotDestination)
twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_2, ROW_3, QtGui.QPainter.RasterOp_NotSourceOrNotDestination)
twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_3, ROW_3, QtGui.QPainter.RasterOp_NotSourceXorDestination)
twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_1, ROW_4, QtGui.QPainter.RasterOp_NotSource)
twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_2, ROW_4, QtGui.QPainter.RasterOp_NotSourceAndDestination)
twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_3, ROW_4, QtGui.QPainter.RasterOp_SourceAndNotDestination)

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

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 "jádra" frameworku Qt i modulu pro GUI
from PySide import QtCore
from PySide import QtGui
 
 
# funkce pro vykreslení obdélníku zadanou barvou a se specifikovaným štětcem
def drawRectangleUsingBrush(qPainter, color, x, y, width, height, brush_style,
                            pen_width=0):
    # vytvoření pera a nastavení barvy kreslení
    pen = QtGui.QPen(QtGui.QColor(*color))
 
    # změna šířky pera
    pen.setWidth(pen_width)
    qPainter.setPen(pen)
 
    # změna tvaru štětce
    brush = QtGui.QBrush(QtGui.QColor(*color))
    brush.setStyle(brush_style)
    qPainter.setBrush(brush)
 
    # vykreslení obdélníku
    qPainter.drawRect(x, y, width, height)
 
 
def twoOverlappingSquares(qPainter, color1, color2, x, y, compositionMode):
    # nastavení výchozího režimu míchání barev
    qPainter.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver)
 
    # první čtverec
    drawRectangleUsingBrush(qPainter, color1, x, y, 100, 100,
                            QtCore.Qt.SolidPattern)
 
    # nastavení režimu míchání barev
    qPainter.setCompositionMode(compositionMode)
 
    # druhý čtverec
    drawRectangleUsingBrush(qPainter, color2, x+50, y+50, 100, 100,
                            QtCore.Qt.SolidPattern)
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 600
    IMAGE_HEIGHT = 800
 
    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)
 
        # vykreslení sady překrývajících se čtverců
        self.drawOverlappingSquares(qp)
 
        # vytvoření instance třídy QPixmap z objektu QImage
        self.pixmap = QtGui.QPixmap.fromImage(self.image)
 
    def drawOverlappingSquares(self, qPainter):
        # 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)
 
        # umístění čtverců na kreslicí ploše
        HORIZONTAL_DISTANCE = 200
        VERTICAL_DISTANCE = 200
 
        COLUMN_1 = 10
        COLUMN_2 = COLUMN_1 + HORIZONTAL_DISTANCE
        COLUMN_3 = COLUMN_2 + HORIZONTAL_DISTANCE
 
        ROW_1 = 10
        ROW_2 = ROW_1 + HORIZONTAL_DISTANCE
        ROW_3 = ROW_2 + HORIZONTAL_DISTANCE
        ROW_4 = ROW_3 + HORIZONTAL_DISTANCE
 
        # vykreslení sady překrývajících se čtverců
        twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_1, ROW_1,
                              QtGui.QPainter.CompositionMode_SourceOver)
 
        twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_1, ROW_2,
                              QtGui.QPainter.RasterOp_SourceOrDestination)
 
        twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_2, ROW_2,
                              QtGui.QPainter.RasterOp_SourceAndDestination)
 
        twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_3, ROW_2,
                              QtGui.QPainter.RasterOp_SourceXorDestination)
 
        twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_1, ROW_3,
                              QtGui.QPainter.RasterOp_NotSourceAndNotDestination)
 
        twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_2, ROW_3,
                              QtGui.QPainter.RasterOp_NotSourceOrNotDestination)
 
        twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_3, ROW_3,
                              QtGui.QPainter.RasterOp_NotSourceXorDestination)
 
        twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_1, ROW_4,
                              QtGui.QPainter.RasterOp_NotSource)
 
        twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_2, ROW_4,
                              QtGui.QPainter.RasterOp_NotSourceAndDestination)
 
        twoOverlappingSquares(qPainter, GREEN, BLUE, COLUMN_3, ROW_4,
                              QtGui.QPainter.RasterOp_SourceAndNotDestination)
 
    def prepareGUI(self):
        # velikost okna nezadávejte ručně - špatně se počítá kvůli toolbaru
        # self.resize(256, 300)
        self.setWindowTitle('QPainter')
 
        # tlačítko Quit
        quitAction = QtGui.QAction(QtGui.QIcon('icons/application-exit.png'),
                                   '&Quit', self)
        quitAction.triggered.connect(self.close)
        quitAction.setStatusTip('Quit the application')
        quitAction.setShortcut('Ctrl+Q')
 
        # nástrojový pruh
        self.toolbar = self.addToolBar('title')
        self.toolbar.setMovable(False)
 
        # přidání tlačítka na nástrojový pruh
        self.toolbar.addAction(quitAction)
 
        # doprostřed okna přidáme návěští s rastrovým obrázkem
        self.addLabelWithPixmap()
 
        # zobrazení hlavního okna
        self.show()
 
    def addLabelWithPixmap(self):
        # vytvoření návěští
        label = QtGui.QLabel("test")
        # přiřazení rastrového obrázku k návěští
        label.setPixmap(self.pixmap)
        # vložení návěští do hlavního okna
        self.setCentralWidget(label)
 
    def run(self, app):
        # zobrazení okna na obrazovce
        self.show()
        # vstup do smyčky událostí (event loop)
        app.exec_()
 
 
def main():
    app = QtGui.QApplication(sys.argv)
    MainWindow().run(app)
 
 
if __name__ == '__main__':
    main()

10. Pokročilejší rastrové operace a role alfa kanálu při provádění těchto operací

Při rasterizaci se kromě bitových operací zmíněných v osmé kapitole mohou použít i pokročilejí operace podporované frameworkem PySide. Jedná se o operace, které většinou používají alfa kanál zdrojových a/nebo cílových pixelů. Tento kanál typicky slouží pro určení míry smíchání barev zdrojového pixelu (tj. pixelu, který se bude vykreslovat) a pixelu cílového. Kódy těchto operací jsou reprezentovány konstantami QPainter.CompositionMode_* a význam některých z nich uvidíme v dnešním pátém demonstračním příkladu popsaném v navazující kapitole.

Obrázek 6: Vzorník rastrových operací vykreslený pátým demonstračním příkladem.

Ve skutečnosti je však možné alfa kanál použít i u klasických bitových ROPs, což je ukázáno na následujících dvou screenshotech, které vznikly nepatrnou úpravou čtvrtého příkladu:

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

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

11. Pátý demonstrační příklad: vzorník vybraných pokročilejších rastrových operací

V tomto příkladu jsou ukázány některé pokročilejší rastrové operace. Ze všech nabízených operací jsem vybral ty, které dávají zajímavé výsledky:

#!/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
 
 
# funkce pro vykreslení obdélníku zadanou barvou a se specifikovaným štětcem
def drawRectangleUsingBrush(qPainter, color, x, y, width, height, brush_style,
                            pen_width=0):
    # vytvoření pera a nastavení barvy kreslení
    pen = QtGui.QPen(QtGui.QColor(*color))
 
    # změna šířky pera
    pen.setWidth(pen_width)
    qPainter.setPen(pen)
 
    # změna tvaru štětce
    brush = QtGui.QBrush(QtGui.QColor(*color))
    brush.setStyle(brush_style)
    qPainter.setBrush(brush)
 
    # vykreslení obdélníku
    qPainter.drawRect(x, y, width, height)
 
 
def twoOverlappingSquares(qPainter, color1, color2, x, y, compositionMode):
    # nastavení výchozího režimu míchání barev
    qPainter.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver)
 
    # první čtverec
    drawRectangleUsingBrush(qPainter, color1, x, y, 100, 100,
                            QtCore.Qt.SolidPattern)
 
    # nastavení režimu míchání barev
    qPainter.setCompositionMode(compositionMode)
 
    # druhý čtverec
    drawRectangleUsingBrush(qPainter, color2, x+50, y+50, 100, 100,
                            QtCore.Qt.SolidPattern)
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 600
    IMAGE_HEIGHT = 800
 
    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)
 
        # vykreslení sady překrývajících se čtverců
        self.drawOverlappingSquares(qp)
 
        # vytvoření instance třídy QPixmap z objektu QImage
        self.pixmap = QtGui.QPixmap.fromImage(self.image)
 
    def drawOverlappingSquares(self, qPainter):
        # 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)
 
        BLUE_50_ALPHA = (0, 0, 255, 128)
        GREEN_50_ALPHA = (0, 255, 0, 128)
 
        # umístění čtverců na kreslicí ploše
        HORIZONTAL_DISTANCE = 200
        VERTICAL_DISTANCE = 200
 
        COLUMN_1 = 10
        COLUMN_2 = COLUMN_1 + HORIZONTAL_DISTANCE
        COLUMN_3 = COLUMN_2 + HORIZONTAL_DISTANCE
 
        ROW_1 = 10
        ROW_2 = ROW_1 + HORIZONTAL_DISTANCE
        ROW_3 = ROW_2 + HORIZONTAL_DISTANCE
        ROW_4 = ROW_3 + HORIZONTAL_DISTANCE
 
        # vykreslení sady překrývajících se čtverců
        twoOverlappingSquares(qPainter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_1,
                              QtGui.QPainter.CompositionMode_SourceOver)
 
        twoOverlappingSquares(qPainter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_2,
                              QtGui.QPainter.CompositionMode_Source)
 
        twoOverlappingSquares(qPainter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_2,
                              QtGui.QPainter.CompositionMode_DestinationIn)
 
        twoOverlappingSquares(qPainter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_3, ROW_2,
                              QtGui.QPainter.CompositionMode_SourceOut)
 
        twoOverlappingSquares(qPainter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_3,
                              QtGui.QPainter.CompositionMode_Xor)
 
        twoOverlappingSquares(qPainter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_3,
                              QtGui.QPainter.CompositionMode_Plus)
 
        twoOverlappingSquares(qPainter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_3, ROW_3,
                              QtGui.QPainter.CompositionMode_Screen)
 
        twoOverlappingSquares(qPainter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_4,
                              QtGui.QPainter.CompositionMode_HardLight)
 
        twoOverlappingSquares(qPainter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_4,
                              QtGui.QPainter.CompositionMode_SoftLight)
 
        twoOverlappingSquares(qPainter, WHITE, BLUE_50_ALPHA, COLUMN_3, ROW_4,
                              QtGui.QPainter.CompositionMode_ColorBurn)
 
    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()

12. Vykreslení textů

Vykreslení textu, přesněji řečeno textového řetězce, na plochu spravovanou třídou QPainter, je v tom nejjednodušším případě snadné. Stačí totiž nastavit barvu vykreslování (vlastnost kreslicího pera), získat instanci třídy QFont a následně text vykreslit metodou QPainter.drawText(x, y, text).

Většinou ale potřebujeme přesněji určit oblast, do které se text vypíše. V tomto případě použijeme odlišnou variantu metody QPainter.drawText(). Tentokrát budou při volání této metody použity tři parametry:

  1. obdélník (QtCore.QRect), do něhož se má text vykreslit.
  2. celočíselný parametr se specifikací horizontálního i vertikálního zarovnání. Jedná se o kombinaci celočíselných konstantQtCore.Qt.Align*.
  3. posledním parametrem je pochopitelně vlastní text.

Podívejme se na příklad:

# vytvoření pera a nastavení barvy kreslení
pen = QtGui.QPen(QtGui.QColor(255, 128, 128))
 
# kreslit se bude právě vytvořeným perem
qp.setPen(pen)
 
# obdélník, do kterého se text vypíše
rect = QtCore.QRect(0, 0, MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT)
 
# zarovnání textu
# - horizontální i vertikální vycentrování v rámci obdélníku
align = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignCenter
 
# vykreslení textu
qp.drawText(rect, align, "PySide")

Nejdůležitější vlastností textu je použitý font. Ten je možné získat různými způsoby, například přečtením reference na výchozí font přiřazený QPainteru:

# nastavení fontu
font = qPainter.font()
font.setBold(True)
font.setPointSize(54)
qPainter.setFont(font)

Užitečnější bývá explicitní určení rodiny fontů, velikosti textu (v typografických bodech), váhy (0 až 100%, určují se jí šířky tahů, tedy laicky „tučnost“) a příznaku, zda chceme použít kurzívu nebo skloněný text (pokud daná rodina kurzívu nemá):

# nastavení fontu
font = QtGui.QFont("monospace", pointSize=54, weight=90, italic=True)
qp.setFont(font)

13. Osmý demonstrační příklad: vykreslení vycentrovaného textu

V šestém příkladu je ukázán postup použitý pro vykreslení vycentrovaného textu, resp. přesněji řečeno textu vycentrovaného v zadaném obdélníku jak vertikálně, tak i horizontálně. Postup již známe, takže jen velmi stručně (povšimněte si, že nenastavujeme styl ani velikost fontu; budou použity výchozí hodnoty):

# obdélník, do kterého se text vypíše
rect = QtCore.QRect(0, 0, MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT)
 
# zarovnání textu
# - horizontální i vertikální vycentrování v rámci obdélníku
align = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignCenter
 
# vykreslení textu
qp.drawText(rect, align, "PySide")

Obrázek 9: Vycentrovaný text vykreslený do okna osmého příkladu.

Opět podle očekávání 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
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 320
    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)
 
        # vykreslení textu
 
        # vytvoření pera a nastavení barvy kreslení
        pen = QtGui.QPen(QtGui.QColor(255, 128, 128))
 
        # změna šířky pera
        pen.setWidth(1)
 
        # kreslit se bude právě vytvořeným perem
        qp.setPen(pen)
 
        # obdélník, do kterého se text vypíše
        rect = QtCore.QRect(0, 0,
                            MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT)
 
        # zarovnání textu
        # - horizontální i vertikální vycentrování v rámci obdélníku
        align = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignCenter
 
        # vykreslení textu
        qp.drawText(rect, align, "PySide")
 
        # 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()

14. Devátý demonstrační příklad: nastavení stylu a velikosti fontu

V předposledním příkladu je ukázán způsob nastavení stylu a velikosti fontu. Není to nic těžkého, pouze potřebujeme získat výchozí font a následně změnit jeho vlastnosti. Konkrétně budeme požadovat tučný text a font o velikosti 54 bodů (nikoli pixelů):

# nastavení fontu
font = qp.font()
font.setBold(True)
font.setPointSize(54)
qp.setFont(font)

Obrázek 10: Vycentrovaný tučný text o velikosti 54 bodů vykreslený do okna devátého příkladu.

Podívejme se na 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
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 320
    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)
 
        # vykreslení textu
 
        # vytvoření pera a nastavení barvy kreslení
        pen = QtGui.QPen(QtGui.QColor(255, 128, 128))
 
        # změna šířky pera
        pen.setWidth(1)
 
        # kreslit se bude právě vytvořeným perem
        qp.setPen(pen)
 
        # nastavení fontu
        font = qp.font()
        font.setBold(True)
        font.setPointSize(54)
        qp.setFont(font)
 
        # obdélník, do kterého se text vypíše
        rect = QtCore.QRect(0, 0,
                            MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT)
 
        # zarovnání textu
        # - horizontální i vertikální vycentrování v rámci obdélníku
        align = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignCenter
 
        # vykreslení textu
        qp.drawText(rect, align, "PySide")
 
        # 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. Desátý demonstrační příklad: načtení fontu ze specifikované rodiny

V posledním příkladu, který se dnes ukážeme, vytvoříme novou instanci třídy QFont, přičemž v konstruktoru specifikujeme jak rodinu fontu (zde „monospace“), tak i požadovanou velikost, váhu (90%) a taktéž to, že budeme chtít použít kurzívu (pokud ji rodina fontů nabízí) nebo skloněný text:

# nastavení fontu
font = QtGui.QFont("monospace", pointSize=54, weight=90, italic=True)
qp.setFont(font)

Obrázek 11: Příklad použití fontu vybraného výše zmíněným konstruktorem.

Opět se podívejme na zdrojový kód příkladu:

bitcoin školení listopad 24

#!/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
 
 
# nový widget bude odvozen od obecného hlavního okna
class MainWindow(QtGui.QMainWindow):
 
    # rozměry rastrového obrázku
    IMAGE_WIDTH = 320
    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)
 
        # vykreslení textu
 
        # vytvoření pera a nastavení barvy kreslení
        pen = QtGui.QPen(QtGui.QColor(255, 128, 128))
 
        # změna šířky pera
        pen.setWidth(1)
 
        # kreslit se bude právě vytvořeným perem
        qp.setPen(pen)
 
        # nastavení fontu
        font = QtGui.QFont("monospace", pointSize=54, weight=90, italic=True)
        qp.setFont(font)
 
        # obdélník, do kterého se text vypíše
        rect = QtCore.QRect(0, 0,
                            MainWindow.IMAGE_WIDTH, MainWindow.IMAGE_HEIGHT)
 
        # zarovnání textu
        # - horizontální i vertikální vycentrování v rámci obdélníku
        align = QtCore.Qt.AlignHCenter | QtCore.Qt.AlignCenter
 
        # vykreslení textu
        qp.drawText(rect, align, "PySide")
 
        # vytvoření instance třídy QPixmap z objektu QImage
        self.pixmap = QtGui.QPixmap.fromImage(self.image)
 
    def prepareGUI(self):
        # velikost okna nezadávejte ručně - špatně se počítá kvůli toolbaru
        # self.resize(256, 300)
        self.setWindowTitle('QPainter')
 
        # tlačítko Quit
        quitAction = QtGui.QAction(QtGui.QIcon('icons/application-exit.png'),
                                   '&Quit', self)
        quitAction.triggered.connect(self.close)
        quitAction.setStatusTip('Quit the application')
        quitAction.setShortcut('Ctrl+Q')
 
        # nástrojový pruh
        self.toolbar = self.addToolBar('title')
        self.toolbar.setMovable(False)
 
        # přidání tlačítka na nástrojový pruh
        self.toolbar.addAction(quitAction)
 
        # doprostřed okna přidáme návěští s rastrovým obrázkem
        self.addLabelWithPixmap()
 
        # zobrazení hlavního okna
        self.show()
 
    def addLabelWithPixmap(self):
        # vytvoření návěští
        label = QtGui.QLabel("test")
        # přiřazení rastrového obrázku k návěští
        label.setPixmap(self.pixmap)
        # vložení návěští do hlavního okna
        self.setCentralWidget(label)
 
    def run(self, app):
        # zobrazení okna na obrazovce
        self.show()
        # vstup do smyčky událostí (event loop)
        app.exec_()
 
 
def main():
    app = QtGui.QApplication(sys.argv)
    MainWindow().run(app)
 
 
if __name__ == '__main__':
    main()

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

V navazující části seriálu o tvorbě grafického uživatelského rozhraní v Pythonu již opustíme téma 2D grafiky. Popíšeme si totiž další widgety, jejichž chování je složitější, než chování základní sady widgetů (tlačítko, seznam, textové pole, položka menu atd.). Framework PySide totiž programátorům nabízí celou sadu komplikovanějších ovládacích prvků využitelných především v klasických desktopových aplikacích.

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

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

18. Odkazy na Internetu

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

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.