Obsah
1. Práce s vektorovým formátem SVG ve frameworku PySide (dokončení)
2. „Ruční“ tvorba SVG a nevýhody tohoto postupu
3. Použití tříd QSvgGenerator a QPainter při tvorbě SVG
4. První demonstrační příklad – pokus o vytvoření výkresu ve formátu SVG
5. Druhý demonstrační příklad – vytvoření prázdného SVG bez entit a role značky <g>
6. Třetí demonstrační příklad – nastavení titulku a popisku SVG výkresu
7. Velikost výkresu a pohledový box
8. Čtvrtý demonstrační příklad – nastavení velikosti a pohledového boxu výkresu
9. Výsledky vygenerované čtvrtým příkladem
10. Pátý příklad – vykreslení základních 2D entit do výkresu
11. Výsledek vygenerovaný pátým příkladem
12. Podpora rastrových operací prováděných při vykreslování 2D entit do SVG
13. Šestý demonstrační příklad – klasické rastrové operace
14. Sedmý demonstrační příklad – rastrové operace využívající alfa kanál
15. Výsledky vytvořené šestým a sedmým příkladem
16. Osmý demonstrační příklad – použití rastrového obrázku v SVG
17. Výsledek vygenerovaný osmým demonstračním příkladem
18. Repositář s demonstračními příklady
19. Články o možnostech a vlastnostech formátu SVG
1. Práce s vektorovým formátem SVG ve frameworku PySide (dokončení)
V předchozí části seriálu o tvorbě aplikací s GUI v Pythonu jsme se seznámili s tím, jak je možné v knihovně PySide využívat výkresy či kresby uložené ve formátu SVG (Scalable Vector Graphics), a to jak při zobrazování jednotlivých ovládacích prvků (widgetů), tak i při rasterizaci SVG do bitmapového obrázku (QBitmap, QPixmap). Dnes na toto téma částečně navážeme, protože si ukážeme způsob vytváření nových výkresů s využitím tříd QSvgGenerator a QPainter (s touto třídou jsme se již v tomto seriálu několikrát setkali, protože se jedná o základní třídu použitou pro práci s 2D grafikou v knihovně PySide a používá se i při tvorbě GUI).
Nejprve si však řekněme, jak vlastně vypadá základní struktura prakticky každého SVG souboru. Jako v jakémkoli jiném XML dokumentu i v SVG musí být na prvním řádku uveden takzvaný prolog, ve kterém je uvedena použitá verze XML (prozatím typicky 1.0, méně často pak 1.1) a většinou také použité kódování znaků (implicitně je předpokládáno UTF-8) či informace o tom, zda dokument obsahuje reference na externí entity. Na následujícím řádku bývá dobrým zvykem uvedení odkazu na externí DTD (Document Type Declaration). Jmenný prostor SVG má identifikaci http://www.w3.org/2000/svg (ta bude použita u značky <svg>), veřejný identifikátor PUBLIC „-W3CDTD SVG 1.0EN“ a systémový identifikátor http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd pro SVG verze 1.0 popř. http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd pro verzi 1.1.
Samotný dokument s kresbou je celý obsažen ve značce <svg>, která může mít uvedeno několik atributů. Typicky zde bývá umístěna minimálně informace o velikosti obrázku, umístění obdélníku s pohledem na obrázek (takzvaný view box) a jmenném prostoru pro značky SVG a popř. i jmenném prostoru pro značky Xlink (použité pro vytváření jednosměrných i oboustranných vazeb). Uvedením atributu xmlns se jmenným prostorem odpovídajícím SVG je umožněno, aby se všechny značky SVG mohly uvádět bez prefixu, který by celý zápis dokumentu prodloužil a také znepřehlednil. Dále se většinou specifikuje verze SVG (1.0, 1.1, 1.2) a v případě verze < 1 i profil (tiny, basic, …). Velmi často se setkáme se SVG Tiny verze 1.2. Pokud budeme brát v úvahu všechny výše uvedené informace o prologu, DTD a značce <svg>, můžeme zkonstruovat kostru použitelnou u prakticky každého SVG dokumentu:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> version="1.2" baseProfile="tiny" </svg>
2. „Ruční“ tvorba SVG a nevýhody tohoto postupu
Vzhledem k tomu, že výkresy a kresby uložené ve formátu SVG jsou založeny na XML, může být vygenerování takového souboru založeno na primitivních I/O funkcích, například na přímém zápisu do textového souboru. Tímto tématem jsme se již zabývali minule, takže si dnes pouze ukažme jednoduchý skript (napsaný samozřejmě v Pythonu), který ukázkovou vektorovou kresbu vytvoří. Tento skript nepoužívá žádnou specializovanou knihovnu, ale generuje SVG řádek po řádku jen s využitím základních nástrojů pro formátování řetězců. Následuje výpis zdrojového kódu tohoto krátkého skriptu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from math import sin, cos def main(): size = 480 with open("logo.svg", "w") as fout: fout.write("<?xml version="1.0" encoding="UTF-8"?>") fout.write("<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='{w}' height='{h}'>\n".format(w=size, h=size)) green = 255 for i, r, red, blue in zip(range(0, 128), range(128, 0, -1), range(255, 0, -2), range(0, 256, 2)): a = i / 12.0 b = i + 80.0 x = size / 2 + b * cos(a) y = size / 2 + b * sin(a) p = "<circle cx='{x}' cy='{y}' r='{r}' ".format(x=x, y=y, r=r) q = "fill='rgb({r}, {g}, {b})' style='fill-opacity:.06'/>\n".format(r=red, g=green, b=blue) r = "fill='none' stroke='black'/>\n" fout.write(p+q) fout.write(p+r) fout.write("</svg>\n") if __name__ == '__main__': main()
Obrázek 1: Výsledná kresba vytvořená předchozím skriptem a uložená do formátu SVG.
Soubor typu SVG vygenerovaný tímto skriptem by měl vypadat přibližně takto (je zobrazen jen jeho začátek a konec):
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='480' height='480'> <circle cx='320.0' cy='240.0' r='128' fill='rgb(255, 255, 0)' style='fill-opacity:.06'/> <circle cx='320.0' cy='240.0' r='128' fill='none' stroke='black'/> ... ... ... </svg>
3. Použití tříd QSvgGenerator a QPainter při tvorbě SVG
Při tvorbě souborů typu SVG se v knihovně PySide používají dvě spolupracující třídy – QSvgGenerator a QPainter. Zatímco se třída QPainter (resp. přesněji řečeno instance této třídy) stará o vykreslování 2D entit, textu i rastrových obrázků, slouží instance třídy QSvgGenerator jako „plátno“ (canvas), na něž se vykreslování provádí. Nyní již tedy známe minimálně dva typy plátna:
- Rastrový obrázek typu QImage, QPixmap nebo QBitmap
- Generátor výkresů ve formátu SVG.
Ve skutečnosti existují i další plátna, například driver tiskárny atd.
S třídou QPainter jsme se již v tomto seriálu setkali. Připomeňme si, že samotný objekt QPainter (tedy instance třídy tohoto jména) provádí vykreslení 2D entit a rastrových obrázků na nějaké „plátno“, což může být buď přímo hardwarové zařízení (grafický subsystém), rastrový obrázek nebo právě soubor SVG. Podívejme se nyní na způsob vykreslení velmi jednoduché grafiky do rastrového obrázku QImage s využitím možností nabízených třídou QPainter. Jeden z klasických postupů je následující:
- Vytvoření instance třídy QImage, která bude tvořit kreslicí „plátno“ pro QPainter. Konstruktoru QImage se předává rozlišení (počet sloupců a řádků) rastrového obrázku i formát pixelů. Nejjednodušší (i když ne vždy nejrychlejší) je použít formát QtGui.QImage.Format_RGB32 pro plnobarevné obrázky.
- Vytvoření objektu typu QPainter konstruktorem QPainter() (tento konstruktor nemá v nejjednodušším případě žádné parametry).
- Informace instance třídy QPainter o začátku vykreslování do instance třídy QImage. To se provede zavoláním metody QPainter.begin().
- Provedení vlastního vykreslení (například barevné úsečky). Mezi „příkazovými závorkami“ begin a end můžete zavolat libovolné množství vykreslovacích operací.
- Informace QPainteru o ukončení vykreslování. To se provede zavoláním metodyQPainter.end().
- Konverze objektu typu QImage na QPixmap (nebo QBitmap).
- QPixmap či QBitmap lze již přímo vykreslit na GUI, například umístěním na návěští.
V případě, že se má vykreslování provést do souboru s formátem SVG, změní se celý postup vlastně jen nepatrně:
- Vytvoření instance třídy QSvgGenerator, která bude tvořit kreslicí „plátno“ pro QPainter.
- Specifikace jména výstupního souboru metodouQSvgGenerator.setFileName()
- Vytvoření objektu typu QPainter konstruktorem QPainter() (tento konstruktor nemá v nejjednodušším případě žádné parametry).
- Informace instance třídy QPainter o začátku vykreslování do instance třídy QSvgGenerator („plátno“). To se provede zavoláním metodyQPainter.begin(), přičemž této metodě předáme referenci na instanci třídy QSvgGenerator.
- Provedení vlastního vykreslení (například barevné úsečky). Mezi „příkazovými závorkami“ begin a end můžete zavolat libovolné množství vykreslovacích operací.
- Informace QPainteru o ukončení vykreslování. To se provede zavoláním metodyQPainter.end().
4. První demonstrační příklad – pokus o vytvoření výkresu ve formátu SVG
Pokusme se nyní použít třídu QSvgGenerator pro vytvoření výkresu či kresby ve formátu SVG. Teoreticky by pro vytvoření souboru s kostrou výkresu měl stačit tento program napsaný v Pythonu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from PySide import QtSvg # vytvoření instance třídy QSvgGenerator generator = QtSvg.QSvgGenerator() # určení typu výstupu a nastavení # jména výsledného souboru generator.setFileName("test1.svg")
Ve skutečnosti se po spuštění tohoto skriptu žádný soubor „test1.svg“ nevytvoří, a to z toho důvodu, že jsme vůbec nezačali vykreslování.
5. Druhý demonstrační příklad – vytvoření prázdného SVG bez entit a role značky <g>
Aby skutečně došlo k vytvoření souboru s kostrou výkresu, je nutné zahájit vykreslování. Ve skutečnosti nemusí dojít k vykreslení žádné 2D entity, protože zcela postačuje, když se vytvoří instance třídy QPainter (tu již známe) a použijí se „příkazové závorky“ QPainter.begin() a QPainter.end() společně s určením, že výsledek vykreslování bude uložen do SVG:
# inicializace instance třídy QPainter painter = QtGui.QPainter() # začátek kreslení painter.begin(generator) # ihned poté ukončíme kreslení painter.end()
Úplný zdrojový kód tohoto příkladu vypadá následovně:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from PySide import QtGui, QtSvg # vytvoření instance třídy QSvgGenerator generator = QtSvg.QSvgGenerator() # určení typu výstupu a nastavení # jména výsledného souboru generator.setFileName("test2.svg") # inicializace instance třídy QPainter painter = QtGui.QPainter() # začátek kreslení painter.begin(generator) # ihned poté ukončíme kreslení painter.end()
Pokud příklad spustíte, měl by se vytvořit soubor „test2.svg“ s následujícím obsahem:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny"> <title>Qt Svg Document</title> <desc>Generated with Qt</desc> <defs> </defs> <g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" > </g> </svg>
Povšimněte si použití párové značky <g>. Tu nalezneme v prakticky každém SVG výkresu. Již v první kapitole jsme se dozvěděli, že formát SVG je založený na obecném značkovacím jazyku XML. To mimo jiného znamená, že všechna data jsou uložena ve stromové struktuře. To je ostatně patrné i z demonstračních příkladů – značka <svg> odpovídá kořenu stromu, ostatní značky představují listy. Je však možné přidávat do tohoto stromu i další uzly, které se mohou dále větvit? Ano, SVG to umožňuje a důvod je jednoduchý: každému uzlu je možné přiřadit styl (například barvu obrysu), který bude zděděn všemi poduzly i listy této části stromu. Uzel se vytvoří právě s využitím párové značky nazvané <g> (od slova group).
Všechny grafické objekty umístěné mezi počáteční a koncovou část značky mohou mít nastavené společné vlastnosti, které jsou zapsány přímo jako atributy značky <g>. A právě z tohoto důvodu vytváří QSvgGenerator při kreslení jednu „globální“ skupinu s implicitním nastavením pera, štětce a režimu výplně.
6. Třetí demonstrační příklad – nastavení titulku a popisku SVG výkresu
Dnešní třetí demonstrační příklad je již nepatrně složitější, protože si v něm ukážeme nastavení metadat, která jsou přidružena k vlastní kresbě. V případě SVG patří mezi metadata především titulek (title) a popis. Tato metadata se nastavují metodami QSvgGenerator.setTitle() a QSvgGenerator.setDescription(). Kromě toho ještě nastavujeme fyzické rozměry výkresu metodou QSvgGenerator.setSize() a pohledový box s využitím metody QSvgGenerator.setViewBox(). Význam těchto dvou metod bude vysvětlen později. Úplný zdrojový kód příkladu vypadá následovně:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from PySide import QtCore, QtGui, QtSvg # vytvoření instance třídy QSvgGenerator generator = QtSvg.QSvgGenerator() # určení typu výstupu a nastavení # jména výsledného souboru generator.setFileName("test3.svg") # specifikace rozměrů SVG obrázku generator.setSize(QtCore.QSize(320, 240)) # viditelný výřez generator.setViewBox(QtCore.QRect(0, 0, 320, 240)) # nastavení titulku SVG obrázku generator.setTitle("SVG: test 3") # popis SVG obrázku generator.setDescription("third SVG example") # inicializace instance třídy QPainter painter = QtGui.QPainter() # začátek kreslení painter.begin(generator) # ihned poté ukončíme kreslení painter.end()
Výsledkem činnosti tohoto skriptu bude soubor „test3.svg“ s následujícím obsahem. Povšimněte si, jaký je obsah značek <title> a <desc>:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg width="112.889mm" height="84.6667mm" viewBox="0 0 320 240" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny"> <title>SVG: test 3</title> <desc>third SVG example</desc> <defs> </defs> <g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" > </g> </svg>
7. Velikost výkresu a pohledový box
V předchozí kapitole jsme se zmínili o rozměrech výkresu a o pohledovém boxu. Jedná se o dva důležité údaje, které ovlivňují jak způsob zobrazení SVG, tak i například rozměry SVG při tisku atd. Rozměry (nebo též viewport) jsou určeny hodnotami atributů width a height, přičemž za samotnými numerickými hodnotami můžeme uvést i délkovou jednotku. V případě, že jednotka není určena, předpokládá se, že jsou rozměry zapsány v pixelech. Podporované jednotky vychází z HTML a CSS:
Zkratka | Použitá jednotka |
---|---|
mm | milimetry |
cm | centimetry |
in | palce |
pt | typografický „bod“ neboli 1/72 palce |
pc | typografická jednotka „pica“ neboli 1/6 palce |
px | pixely |
ex | výška písmena „x“ |
em | výška textového řádku (nepřesně šířka písmena „M“) |
Rozměry výkresu se uplatní při jeho umístění na HTML stránku, při tisku, vkládání do vytvářených publikací apod. Další důležitou informací související s použitými délkovými jednotkami je takzvaný pohledový box neboli viewbox (neplést s viewportem). Pohledový box určuje výřez z celé kreslicí plochy a současně i způsob přepočtu bezrozměrných jednotek použitých v jednotlivých 2D entitách na fyzické délkové jednotky popř. na relativní jednotky ve chvíli, kdy je šířka a výška specifikována v % obsazené plochy stránky. Specifikován je čtyřmi hodnotami minx, miny, width a height.
Pokud například zadáme:
<svg width="10cm" height="10cm" viewBox="0 0 100 100">
znamená to, že fyzické rozměry výkresu budou nastaveny na 10×10 cm a na této ploše bude rozprostřena souřadná síť s rozsahem x od 0 do 100 (bezrozměrných jednotek) a s rozsahem y také od 0 do 100. Interně se při specifikaci pohledového boxu vytvoří transformační matice, která je aplikována na všechny souřadnice.
8. Čtvrtý demonstrační příklad – nastavení velikosti a pohledového boxu výkresu
Ve čtvrtém demonstračním příkladu vytvoříme tři výkresy, které sice budou mít stejný obsah (bílý čtverec), ovšem rozdílné fyzické rozměry. Vykreslení čtverce je snadné:
# začátek kreslení painter.begin(generator) drawRectangle(painter, WHITE, 1, 1, VBOX_WIDTH-1, VBOX_HEIGHT-1) # konec kreslení painter.end()
Funkce nazvaná drawRectangle byla beze změn převzata z předchozích demonstračních příkladů, v nichž jsme prováděli vykreslování do GUI (a nikoli do SVG):
def drawRectangle(qPainter, color, x, y, width, height): setColor(qPainter, color) # vykreslení obdélníku qPainter.drawRect(x, y, width, height)
Úplný zdrojový kód tohoto příkladu vypadá následovně:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from PySide import QtCore, QtGui, QtSvg # 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í 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í 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) def create_svg(name, width, height): VBOX_WIDTH = 100 VBOX_HEIGHT = 100 # vytvoření instance třídy QSvgGenerator generator = QtSvg.QSvgGenerator() # určení typu výstupu a nastavení # jména výsledného souboru generator.setFileName(name) # specifikace rozměrů SVG obrázku generator.setSize(QtCore.QSize(width, height)) # viditelný výřez generator.setViewBox(QtCore.QRect(0, 0, VBOX_WIDTH, VBOX_HEIGHT)) # nastavení titulku SVG obrázku generator.setTitle("SVG: test 4") # popis SVG obrázku generator.setDescription("fourth SVG example") # inicializace instance třídy QPainter painter = QtGui.QPainter() # začátek kreslení painter.begin(generator) drawRectangle(painter, WHITE, 1, 1, VBOX_WIDTH-1, VBOX_HEIGHT-1) # konec kreslení painter.end() def main(): create_svg("test4.svg", 10, 10) create_svg("test5.svg", 100, 100) create_svg("test6.svg", 1000, 1000) if __name__ == '__main__': main()
9. Výsledky vygenerované čtvrtým příkladem
Podívejme se nyní, jak vypadají SVG soubory vygenerované čtvrtým demonstračním příkladem. Samotné vykreslení čtverce vypadá vždy stejně:
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M1,1 L100,1 L100,100 L1,100 L1,1"/>
Jedná se o deklaraci takzvané cesty („tahy štětcem“). Každá cesta je představována značkou <path>, která může mít nastaveno velké množství atributů ovlivňujících tvar, styl či animaci cesty. Nejdůležitější atributy této značky jsou vypsány v následující tabulce:
Atribut | Význam |
---|---|
d | vlastní geometrie cesty, bude podrobněji vysvětleno dále |
class | třída, do které cesta spadá, může být použito například jako selektor pro styl |
style | styl, kterým má být cesta vykreslena |
id | identifikace cesty, může být použito například při programově řízené animaci či při změně vlastností cesty pomocí DOM |
transform | lineární transformace aplikovaná na všechny specifikované vrcholy |
Hodnota atributu d (od data či definition), která je představována řetězcem obsahujícím znaky se speciálním významem a numerické údaje, má velký význam, protože je pomocí ní zapsána celá geometrie cesty, tj. koncové body úsečkových segmentů, parametry Bézierových kvadratických i kubických křivek, parametry eliptických oblouků atd. Všechny údaje, které se vztahují k souřadnicím, jsou zapisovány pomocí absolutního či relativního pohybu grafického kurzoru.
Jednotlivé výkresy sice používají shodné cesty pro vytvoření čtverce, ovšem liší se v nastavení fyzických rozměrů (šířky a výšky).
První varianta:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg width="3.52778mm" height="3.52778mm" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny"> <title>SVG: test 4</title> <desc>fourth SVG example</desc> <defs> </defs> <g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" > <g fill="none" stroke="#ffffff" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)" font-family="Helvetica" font-size="12" font-weight="400" font-style="normal" > <path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M1,1 L100,1 L100,100 L1,100 L1,1"/> </g> </g> </svg>
Druhá varianta:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg width="35.2778mm" height="35.2778mm" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny"> <title>SVG: test 4</title> <desc>fourth SVG example</desc> <defs> </defs> <g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" > <g fill="none" stroke="#ffffff" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)" font-family="Helvetica" font-size="12" font-weight="400" font-style="normal" > <path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M1,1 L100,1 L100,100 L1,100 L1,1"/> </g> </g> </svg>
Třetí varianta:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg width="352.778mm" height="352.778mm" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny"> <title>SVG: test 4</title> <desc>fourth SVG example</desc> <defs> </defs> <g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" > <g fill="none" stroke="#ffffff" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)" font-family="Helvetica" font-size="12" font-weight="400" font-style="normal" > <path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M1,1 L100,1 L100,100 L1,100 L1,1"/> </g> </g> </svg>
10. Pátý příklad – vykreslení základních 2D entit do výkresu
Do souborů typu SVG je možné ukládat značky/tagy reprezentující šest typů základních geometrických tvarů. Mezi tyto tvary patří úsečka (line), obdélník (rectangle), kružnice (circle), elipsa (ellipse), polyčára (polyline, lomená čára) a polygon (polygon). Všechny tyto tvary je možné nahradit vhodně zapsanou cestou, ale z různých důvodů (dodatečné informace pro grafické editory či další aplikace, jednodušší zápis i čtení pomocí SAX i DOM) se můžeme často setkat i s přímým zápisem základních geometrických tvarů. Definice tvarů pomocí cesty však většinou bývá kompaktnější, což vede k menšímu objemu výsledného souboru (ovšem tento rozdíl se viditelněji projeví až při velkých objemech dat, tedy při složitějších výkresech).
Zkusme nyní zjistit, jestli dvojice tříd QSvgGenerator + QPainter používá cesty nebo základní geometrické tvary. Způsob generování SVG vyzkoušíme na příkladu, v němž vykreslíme různé typy 2D entit. Výsledek by měl vypadat zhruba následovně (záleží na použité prohlížečce):
Obrázek 2: Výkres vytvořený dalším demonstračním příkladem.
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně. Zdrojový kód je sice poněkud delší, ale není příliš složitý (pouze obsahuje pomocné funkce pro vykreslení různých typů 2D entit):
#!/usr/bin/env python # vim: set fileencoding=utf-8 from PySide import QtCore, QtGui, QtSvg # 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í ú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) def drawScene(painter): # 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) # okraje drawRectangle(painter, WHITE, 0, 0, 330, 520) # Vykreslení různých 2D entit drawLine(painter, GREEN, 10, 10, 80, 80) drawRectangle(painter, YELLOW, 10, 90, 70, 70) drawCircle(painter, RED, 125, 125, 35) drawEllipse(painter, CYAN, 170, 30+80, 70, 35) drawEllipse(painter, BLUE, 268, 10+80, 35, 70) drawRoundedRectangle(painter, MAGENTA, 10, 170, 70, 70, 1) drawRoundedRectangle(painter, MAGENTA, 90, 170, 70, 70, 10) drawRoundedRectangle(painter, MAGENTA, 170, 170, 70, 70, 20) drawRoundedRectangle(painter, MAGENTA, 250, 170, 70, 70, 1000) drawArc(painter, CYAN, 10+35, 260+35, 35, 0, 90) drawArc(painter, CYAN, 90+35, 260+35, 35, 45, 90) drawArc(painter, CYAN, 170+35, 260+35, 35, 45, 180) drawArc(painter, CYAN, 250+35, 260+35, 35, 45, 270) drawPie(painter, YELLOW, 10+35, 350+35, 35, 0, 90) drawPie(painter, YELLOW, 90+35, 350+35, 35, 45, 90) drawPie(painter, YELLOW, 170+35, 350+35, 35, 45, 180) drawPie(painter, YELLOW, 250+35, 350+35, 35, 45, 270) drawChord(painter, GREEN, 10+35, 440+35, 35, 0, 90) drawChord(painter, GREEN, 90+35, 440+35, 35, 45, 90) drawChord(painter, GREEN, 170+35, 440+35, 35, 45, 180) drawChord(painter, GREEN, 250+35, 440+35, 35, 45, 270) def create_svg(name, width, height): # vytvoření instance třídy QSvgGenerator generator = QtSvg.QSvgGenerator() # určení typu výstupu a nastavení # jména výsledného souboru generator.setFileName(name) # specifikace rozměrů SVG obrázku generator.setSize(QtCore.QSize(width, height)) # viditelný výřez generator.setViewBox(QtCore.QRect(0, 0, 330, 520)) # nastavení titulku SVG obrázku generator.setTitle("SVG: test 7") # popis SVG obrázku generator.setDescription("fifth SVG example") # inicializace instance třídy QPainter painter = QtGui.QPainter() # začátek kreslení painter.begin(generator) drawScene(painter) # konec kreslení painter.end() def main(): create_svg("test7.svg", 320, 320) if __name__ == '__main__': main()
11. Výsledek vygenerovaný pátým příkladem
SVG soubor vytvořený pátým příkladem je již poněkud rozsáhlý, ovšem nás bude zajímat jen způsob zápisu 2D entit, takže si uvedeme pouze vybrané části.
Úsečka je kupodivu zapsána jako polyčára se dvěma vrcholy:
<polyline fill="none" vector-effect="non-scaling-stroke" points="10,10 80,80 " />
Obdélník/čtverec je reprezentován cestou (bez příkazu pro její uzavření):
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M10,90 L80,90 L80,160 L10,160 L10,90"/>
Kružnice je zapsána formou čtyř oblouků:
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M160,125 C160,144.33 144.33,160 125,160 C105.67,160 90,144.33 90,125 C90,105.67 105.67,90 125,90 C144.33,90 160,105.67 160,125 "/>
Elipsa taktéž používá čtyři oblouky:
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M240,127.5 C240,137.165 224.33,145 205,145 C185.67,145 170,137.165 170,127.5 C170,117.835 185.67,110 205,110 C224.33,110 240,117.835 240,127.5 "/>
Obdélník/čtverec se zaoblenými rohy je opět reprezentován cestou:
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M10,171 C10,170.448 10.4477,170 11,170 L79,170 C79.5523,170 80,170.448 80,171 L80,239 C80,239.552 79.5523,240 79,240 L11,240 C10.4477,240 10,239.552 10,239 L10,171"/>
Kruhový oblouk je nutné zapsat jako cestu:
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M80,295 C80,275.67 64.33,260 45,260 "/>
Kruhová výseč a kruhová úseč:
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M45,385 L80,385 C80,365.67 64.33,350 45,350 L45,385"/> <path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M80,475 C80,455.67 64.33,440 45,440 L80,475"/>
Vysvětlení jednotlivých jednoznakových příkazů v definicích cesty:
Příkaz | Popis |
---|---|
M | absolutní pohyb bez kreslení |
m | relativní pohyb bez kreslení |
L | absolutní pohyb na souřadnice s kreslením úsečkového segmentu |
l | relativní pohyb o souřadnice s kreslením úsečkového segmentu |
H | horizontální posun (vykreslení vodorovné úsečky) |
h | relativní posun v horizontálním směru (vykreslení vodorovné úsečky) |
V | vertikální posun (vykreslení svislé úsečky) |
v | relativní posun (vykreslení svislé úsečky) |
Z | uzavření cesty úsečkovým segmentem |
z | má stejný význam jako příkaz Z |
Q | kvadratická Bézierova křivka zadaná trojicí řídicích bodů |
q | stejný význam jako Q s tím rozdílem, že souřadnice řídicích bodů jsou zadány relativně |
T | hladce navazující kvadratická Bézierova křivka |
t | stejný význam jako T s tím rozdílem, že souřadnice koncového bodu kvadratické křivky jsou zadány relativně |
C | kubická Bézierova křivka zadaná čtyřmi řídicími body |
c | stejný význam jako C s tím rozdílem, že souřadnice řídicích bodů jsou zadány relativně |
S | podobný příkazu T, ovšem s tím rozdílem, že se vytvoří kubická Bézierova křivka |
s | podobné příkazu S, ale všechny souřadnice řídicích bodů jsou zadány relativně |
A | eliptický oblouk zadaný absolutními souřadnicemi |
a | eliptický oblouk zadaný relativními souřadnicemi |
12. Podpora rastrových operací prováděných při vykreslování 2D entit do SVG
Z předchozích částí tohoto seriálu již víme, že při vykreslování (přesněji řečeno rasterizaci) 2D entit na plátno typu QImage můžeme aplikovat různé operace prováděné nad jednotlivými pixely. Při vykreslování se totiž postupuje zhruba následujícím způsobem:
- Vrcholy popř. řídicí body dvourozměrných entit jsou podrobeny vybrané lineární transformaci (zvětšení, zmenšení, otočení, zkosení, posun atd.).
- Zjistí se, zda je entita vůbec viditelná, tj. zda transformované vrcholy leží ve viditelné oblasti.
- 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).
- 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ří:
- 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.
- 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.
Problém spočívá v tom, že rasterizaci vektorového výkresu SVG neřídí framework PySide, ale obecně libovolná SVG prohlížečka, která může být zabudovaná například do webového browseru apod. Sice je možné při vykreslování aplikovat několik filtrů, ale tyto filtry neodpovídají klasickým rastrovým operacím. Proto bude zajímavé zjistit, co se vlastně stane, pokud vezmeme skripty použité pro vykreslení následujících obrázků a použijeme ho pro vygenerování SVG:
Obrázek 3: Vzorník základních rastrových operací.
Obrázek 4: Vzorník dalších rastrových operací.
Obrázek 5: Modré čtverce mají nastavenou poloviční průhlednost (vykresleno variantou čtvrtého příkladu).
Obrázek 6: Modré i zelené čtverce mají nastavenou poloviční průhlednost (vykresleno variantou čtvrtého příkladu).
13. Šestý demonstrační příklad – klasické rastrové operace
V šestém příkladu jsou použity základní rastrové operace podobné těm, které jsou zobrazeny na obrázku 3 a 4. Ovšem nyní provedeme export do SVG, nikoli vykreslení do rastrového obrázku (nad čímž má framework PySide mnohem větší kontrolu):
#!/usr/bin/env python # vim: set fileencoding=utf-8 from PySide import QtCore, QtGui, QtSvg # 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í 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 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) def drawScene(painter, width, height): # 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) # okraje drawRectangle(painter, WHITE, 0, 0, width, height) 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(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_1, QtGui.QPainter.CompositionMode_SourceOver) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_2, QtGui.QPainter.CompositionMode_Source) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_2, QtGui.QPainter.CompositionMode_DestinationIn) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_3, ROW_2, QtGui.QPainter.CompositionMode_SourceOut) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_3, QtGui.QPainter.CompositionMode_Xor) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_3, QtGui.QPainter.CompositionMode_Plus) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_3, ROW_3, QtGui.QPainter.CompositionMode_Screen) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_4, QtGui.QPainter.CompositionMode_HardLight) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_4, QtGui.QPainter.CompositionMode_SoftLight) twoOverlappingSquares(painter, WHITE, BLUE_50_ALPHA, COLUMN_3, ROW_4, QtGui.QPainter.CompositionMode_ColorBurn) def create_svg(name, width, height): VBOX_WIDTH = 600 VBOX_HEIGHT = 800 # vytvoření instance třídy QSvgGenerator generator = QtSvg.QSvgGenerator() # určení typu výstupu a nastavení # jména výsledného souboru generator.setFileName(name) # specifikace rozměrů SVG obrázku generator.setSize(QtCore.QSize(width, height)) # viditelný výřez generator.setViewBox(QtCore.QRect(0, 0, VBOX_WIDTH, VBOX_HEIGHT)) # nastavení titulku SVG obrázku generator.setTitle("SVG: test 8") # popis SVG obrázku generator.setDescription("sixth SVG example") # inicializace instance třídy QPainter painter = QtGui.QPainter() # začátek kreslení painter.begin(generator) drawScene(painter, VBOX_WIDTH, VBOX_HEIGHT) # konec kreslení painter.end() def main(): create_svg("test8.svg", 320, 320) if __name__ == '__main__': main()
14. Sedmý demonstrační příklad – rastrové operace využívající alfa kanál
Příklad sedmý by měl vytvořit obrázky podobné těm ze screenshotu 5 či 6, ovšem opět s omezením na možnosti SVG:
#!/usr/bin/env python # vim: set fileencoding=utf-8 from PySide import QtCore, QtGui, QtSvg # 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í 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 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) def drawScene(painter, width, height): # 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) # okraje drawRectangle(painter, WHITE, 0, 0, width, height) 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(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_1, QtGui.QPainter.CompositionMode_SourceOver) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_2, QtGui.QPainter.RasterOp_SourceOrDestination) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_2, QtGui.QPainter.RasterOp_SourceAndDestination) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_3, ROW_2, QtGui.QPainter.RasterOp_SourceXorDestination) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_3, QtGui.QPainter.RasterOp_NotSourceAndNotDestination) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_3, QtGui.QPainter.RasterOp_NotSourceOrNotDestination) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_3, ROW_3, QtGui.QPainter.RasterOp_NotSourceXorDestination) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_1, ROW_4, QtGui.QPainter.RasterOp_NotSource) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_2, ROW_4, QtGui.QPainter.RasterOp_NotSourceAndDestination) twoOverlappingSquares(painter, GREEN_50_ALPHA, BLUE_50_ALPHA, COLUMN_3, ROW_4, QtGui.QPainter.RasterOp_SourceAndNotDestination) def create_svg(name, width, height): VBOX_WIDTH = 600 VBOX_HEIGHT = 800 # vytvoření instance třídy QSvgGenerator generator = QtSvg.QSvgGenerator() # určení typu výstupu a nastavení # jména výsledného souboru generator.setFileName(name) # specifikace rozměrů SVG obrázku generator.setSize(QtCore.QSize(width, height)) # viditelný výřez generator.setViewBox(QtCore.QRect(0, 0, VBOX_WIDTH, VBOX_HEIGHT)) # nastavení titulku SVG obrázku generator.setTitle("SVG: test 9") # popis SVG obrázku generator.setDescription("seventh SVG example") # inicializace instance třídy QPainter painter = QtGui.QPainter() # začátek kreslení painter.begin(generator) drawScene(painter, VBOX_WIDTH, VBOX_HEIGHT) # konec kreslení painter.end() def main(): create_svg("test9.svg", 320, 320) if __name__ == '__main__': main()
15. Výsledky vytvořené šestým a sedmým příkladem
Podívejme se nyní na SVG obrázky vytvořené šestým a sedmým příkladem po jejich zobrazení v prohlížečce (konkrétně je použita prohlížečka gThumb):
Obrázek 7: Výkres vytvořený šestým demonstračním příkladem.
Obrázek 8: Výkres vytvořený sedmým demonstračním příkladem.
Můžeme vidět, že většina rastrových operací byla ignorována. Můžeme se o tom přesvědčit i pohledem do SVG (zde zkráceno), kde je patrné pouze nastavení průhlednosti, ovšem per-pixel operace se neprovádí:
<g fill="#00ff00" fill-opacity="0.501961" stroke="#00ff00" stroke-opacity="0.501961" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)" font-family="Helvetica" font-size="12" font-weight="400" font-style="normal" > <path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M10,10 L110,10 L110,110 L10,110 L10,10"/> </g> <g fill="#0000ff" fill-opacity="0.501961" stroke="#0000ff" stroke-opacity="0.501961" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)" font-family="Helvetica" font-size="12" font-weight="400" font-style="normal" > <path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M460,660 L560,660 L560,760 L460,760 L460,660"/> </g>
Sedmý příklad vygenerovat tento SVG (opět zkrácený):
<g fill="none" stroke="#ffffff" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)" font-family="Helvetica" font-size="12" font-weight="400" font-style="normal" > <path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M0,0 L600,0 L600,800 L0,800 L0,0"/> </g> <g fill="#00ff00" fill-opacity="0.501961" stroke="#00ff00" stroke-opacity="0.501961" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)" font-family="Helvetica" font-size="12" font-weight="400" font-style="normal" > <path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M10,10 L110,10 L110,110 L10,110 L10,10"/> </g>
16. Osmý demonstrační příklad – použití rastrového obrázku v SVG
V osmém a současně i dnešním posledním demonstračním příkladu je ukázáno, jak se do vektorového výkresu může vložit rastrový obrázek (se všemi důsledky, které to s sebou nese). Především je nutné provést inicializaci třídy QApplication. Pokud inicializaci neprovedeme, dojde k pádu aplikace(!):
app = QtGui.QApplication(sys.argv)
Dále již můžeme rastrový obrázek načíst (bude reprezentován objektem typu QImage) a vykreslit metodou QPainter.drawImage():
def drawScene(painter, width, height): # konstanty s n-ticemi představujícími základní barvy WHITE = (255, 255, 255) # okraje drawRectangle(painter, WHITE, 0, 0, width, height) image = QtGui.QImage("pixmaps/pysidelogo.png") rect1 = QtCore.QRectF(10, 10, 199, 102) painter.drawImage(rect1, image) rect2 = QtCore.QRectF(10, 120, 199*3, 102*3) painter.drawImage(rect2, image)
Obrázek 9: Výkres vytvořený osmým demonstračním příkladem.
Jak je již zvykem, opět následuje výpis úplného zdrojového kódu tohoto příkladu:
#!/usr/bin/env python # vim: set fileencoding=utf-8 import sys from PySide import QtCore, QtGui, QtSvg # 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í 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) def drawScene(painter, width, height): # konstanty s n-ticemi představujícími základní barvy WHITE = (255, 255, 255) # okraje drawRectangle(painter, WHITE, 0, 0, width, height) image = QtGui.QImage("pixmaps/pysidelogo.png") rect1 = QtCore.QRectF(10, 10, 199, 102) painter.drawImage(rect1, image) rect2 = QtCore.QRectF(10, 120, 199*3, 102*3) painter.drawImage(rect2, image) def create_svg(name, width, height): VBOX_WIDTH = 600 VBOX_HEIGHT = 440 # vytvoření instance třídy QSvgGenerator generator = QtSvg.QSvgGenerator() # určení typu výstupu a nastavení # jména výsledného souboru generator.setFileName(name) # specifikace rozměrů SVG obrázku generator.setSize(QtCore.QSize(width, height)) # viditelný výřez generator.setViewBox(QtCore.QRect(0, 0, VBOX_WIDTH, VBOX_HEIGHT)) # nastavení titulku SVG obrázku generator.setTitle("SVG: test 10") # popis SVG obrázku generator.setDescription("eight SVG example") # inicializace instance třídy QPainter painter = QtGui.QPainter() # začátek kreslení painter.begin(generator) drawScene(painter, VBOX_WIDTH, VBOX_HEIGHT) # konec kreslení painter.end() def main(): create_svg("test10.svg", 320, 320) if __name__ == '__main__': app = QtGui.QApplication(sys.argv) main()
17. Výsledek vygenerovaný osmým demonstračním příkladem
Zajímavé bude zjistit, jak se vlastně rastrový obrázek uloží do souboru typu SVG, který je založen na XML a tudíž i na běžném (čitelném) textovém formátu:
<image x="10" y="10" width="199" height="102" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUg AAAMcAAABmCAYAAAB/RYWNAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAA ... ... ... AOxAAADsQBlSsOGABJRU5ErkJggg==" />
Můžeme vidět, že data rastrového obrázku jsou uložena přímo do SVG (XML), ovšem aby byla zachována jeho textová podstata, bylo nutné pro zakódování binárních dat použít kódování Base64. Nevýhodou je, že oproti binárním datům se velikost zvýší o více než 33%, protože vždy tři původní bajty jsou zakódovány do čtyř znaků a na konci je většinou nutné použít výplň tvořenou znaky =. Dále stojí za povšimnutí, že ve výsledném SVG je celý řetězec zapsán na jediném řádku, i když se běžně v Base64 používají relativně krátké řádky s 64 či 76 znaky (znak pro konec řádku nemá žádný význam a je čtečkami ignorován).
18. Repositář s demonstračními příklady
Zdrojové kódy všech osmi dnes popsaných demonstračních příkladů společně s vygenerovanými výkresy SVG byly, podobně jako tomu bylo i v předchozích článcích, uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/presentations. Pokud nechcete klonovat celý repositář, můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
# | Příklad | Adresa |
---|---|---|
1 | 152_try_to_create_svg.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/152_try_to_create_svg.py |
2 | 153_create_empty_svg.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/153_create_empty_svg.py |
3 | 154_svg_title_and_description.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/154_svg_title_and_description.py |
4 | 155_size_viewbox.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/155_size_viewbox.py |
5 | 156_draw_into_svg.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/156_draw_into_svg.py |
6 | 157_compositing_modes.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/157_compositing_modes.py |
7 | 158_raster_ops.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/158_raster_ops.py |
8 | 159_draw_image.py | https://github.com/tisnik/presentations/blob/master/Python_GUI/PySide/159_draw_image.py |
Výkresy ve formátu SVG, které byly vytvořeny dnešními demonstračními příklady:
19. Články o možnostech a vlastnostech formátu SVG
- Vektorový grafický formát SVG
http://www.root.cz/clanky/vektorovy-graficky-format-svg/ - Cesty v souborech typu Scalable Vector Graphics
http://www.root.cz/clanky/cesty-v-souborech-typu-scalable-vector-graphics/ - Scalable Vector Graphics a základní geometrické tvary
http://www.root.cz/clanky/scalable-vector-graphics-a-zakladni-geometricke-tvary/ - Vlastnosti cest a základních geometrických tvarů v SVG
http://www.root.cz/clanky/vlastnosti-cest-a-zakladnich-geometrickych-tvaru-v-svg/ - SVG – styly výplní a značky připojované ke křivkám
http://www.root.cz/clanky/svg-styly-vyplni-a-znacky-pripojovane-ke-krivkam/ - Gradientní výplně a textové objekty v SVG
http://www.root.cz/clanky/gradientni-vyplne-a-textove-objekty-v-svg/ - Grafický formát SVG a animace
http://www.root.cz/clanky/graficky-format-svg-a-animace/ - Pokročilejší animace ve formátu SVG
http://www.root.cz/clanky/pokrocilejsi-animace-ve-formatu-svg/ - Podpora skriptování v grafickém formátu SVG
http://www.root.cz/clanky/podpora-skriptovani-v-grafickem-formatu-svg/ - Zpracování událostí při skriptování výkresů SVG
http://www.root.cz/clanky/zpracovani-udalosti-pri-skriptovani-vykresu-svg/
20. Odkazy na Internetu
- QSvgWidget
https://pyside.github.io/docs/pyside/PySide/QtSvg/QSvgWidget.html - QByteArray
https://pyside.github.io/docs/pyside/PySide/QtCore/QByteArray.html - Python Bytes, Bytearray
https://www.w3resource.com/python/python-bytes.php - psep-0101.txt (mj. popis mapování typů Pythonu na třídy v PySide)
https://github.com/techtonik/pseps/blob/master/psep-0101.txt - QSvgRenderer
https://pyside.github.io/docs/pyside/PySide/QtSvg/QSvgRenderer.html - QSvgGenerator
https://pyside.github.io/docs/pyside/PySide/QtSvg/QSvgGenerator.html - QIcon
https://pyside.github.io/docs/pyside/PySide/QtGui/QIcon.html - PySide 1.2.1 documentation
https://pyside.github.io/docs/pyside/index.html - QStyle
https://pyside.github.io/docs/pyside/PySide/QtGui/QStyle.html - QCommonStyle
https://pyside.github.io/docs/pyside/PySide/QtGui/QCommonStyle.html - QPlastiqueStyle
https://pyside.github.io/docs/pyside/PySide/QtGui/QPlastiqueStyle.html - QMacStyle
https://pyside.github.io/docs/pyside/PySide/QtGui/QMacStyle.html - QCleanlooksStyle
https://pyside.github.io/docs/pyside/PySide/QtGui/QCleanlooksStyle.html - QGtkStyle
https://pyside.github.io/docs/pyside/PySide/QtGui/QGtkStyle.html - QCDEStyle
https://pyside.github.io/docs/pyside/PySide/QtGui/QCDEStyle.html - QMotifStyle
https://pyside.github.io/docs/pyside/PySide/QtGui/QMotifStyle.html - QWindowsStyle
https://pyside.github.io/docs/pyside/PySide/QtGui/QWindowsStyle.html - QStyleFactory
https://pyside.github.io/docs/pyside/PySide/QtGui/QStyleFactory.html - QStyleOptionHeader
https://pyside.github.io/docs/pyside/PySide/QtGui/QStyleOptionHeader.html - QAbstractSlider
https://pyside.github.io/docs/pyside/PySide/QtGui/AbstractSlider.html - QScrollBar
https://pyside.github.io/docs/pyside/PySide/QtGui/ScrollBar.html - QSlider
https://pyside.github.io/docs/pyside/PySide/QtGui/Slider.html - QDial
https://pyside.github.io/docs/pyside/PySide/QtGui/Dial.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 - QTableWidget
https://pyside.github.io/docs/pyside/PySide/QtGui/QTableWidget.html - QTableWidgetItem
https://pyside.github.io/docs/pyside/PySide/QtGui/QTableWidgetItem.html - QTreeWidget
https://pyside.github.io/docs/pyside/PySide/QtGui/QTreeWidget.html - QTreeWidgetItem
https://pyside.github.io/docs/pyside/PySide/QtGui/QTreeWidgetItem.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 - QValidator
https://pyside.github.io/docs/pyside/PySide/QtGui/QValidator.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 - QDialog
https://pyside.github.io/docs/pyside/PySide/QtGui/QDialog.html - QMessageBox
https://pyside.github.io/docs/pyside/PySide/QtGui/QMessageBox.html - QErrorMessage
https://pyside.github.io/docs/pyside/PySide/QtGui/QErrorMessage.html - QInputDialog
https://pyside.github.io/docs/pyside/PySide/QtGui/QInputDialog.html - QColorDialog
https://pyside.github.io/docs/pyside/PySide/QtGui/QColorDialog.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/ - QLineEdit
https://pyside.github.io/docs/pyside/PySide/QtGui/QLineEdit.html - QTextEdit
https://pyside.github.io/docs/pyside/PySide/QtGui/QTextEdit.html - QValidator
https://pyside.github.io/docs/pyside/PySide/QtGui/QValidator.html - QIntValidator
https://pyside.github.io/docs/pyside/PySide/QtGui/QIntValidator.html - QRegExpValidator
https://pyside.github.io/docs/pyside/PySide/QtGui/QRegExpValidator.html - 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/