Obsah
1. Ovládací prvky a další prostředky určené pro zobrazení grafických informací
3. Malá odbočka – programové vytvoření procedurální textury s obrázkem
4. První demonstrační příklad – moaré jako obrázek ve stupních šedi
5. Druhý demonstrační příklad – aplikace barvové palety
6. Použití ovládacího prvku typu Image
7. Rastrový obrázek uložený přímo ve zdrojovém kódu aplikace
8. Postup při uložení obrázku do zdrojového kódu
9. Použití ovládacího prvku typu Image a obrázku ze zdrojového kódu
10. Ovládací prvek zobrazující koláčový diagram
11. Zobrazení mřížky LED (emulace počítače Micro Bit)
12. Programová změna světlosti jednotlivých LED
13. Použití prvku Canvas z Tkinteru
15. Repositář s demonstračními příklady
1. Ovládací prvky a další prostředky určené pro zobrazení grafických informací
Knihovna appJar vývojářům nabízí pouze velmi omezené množství ovládacích prvků, které je možné použít pro zobrazení grafických informací, ať již se to týká rastrové grafiky či grafiky vektorové. Existují vlastně pouze tři widgety, které práci s grafikou do jisté míry podporují. Prvním z těchto ovládacích prvků je widget nazvaný Image, jenž dokáže na ploše okna či dialogu zobrazit rastrový obrázek, který je načtený z externího souboru nebo ho je alternativně možné načíst z pole bajtů či znaků, které obsahují zakódovaná data obrázku. Dále tento widget dokáže reagovat na stisk tlačítka myši. Druhý widget je již specializovaný, protože slouží pro zobrazení koláčového grafu, ovšem jen s omezenými možnostmi nastavení parametrů zobrazení.
Třetí ovládací prvek, který do jisté míry podporuje zobrazení grafických informací, je widget simulující matici LED na jednodeskovém mikropočítači Micro Bit. Tato matice sice má „rozlišení“ pouze 5×5 bodů (pixelů), ovšem kupodivu právě tento widget je mezi dětmi oblíbený (což je vlastně na první pohled dost paradoxní, zvláště když vezmeme v úvahu možnosti dnešních počítačů a jejich GPU).
Pokud tyto možnosti nabízené přímo knihovnou appJar nebudou dostačující (a to pro složitější aplikace zcela jistě nebudou), je možné využít prostředky Tkinteru, tj. především prvek Canvas. I s touto možností se seznámíme, protože díky Tkinteru a Canvasu je možné skloubit hned dvě užitečné výukové pomůcky – appJar pro GUI a modul turtle pro želví grafiku.
2. Ovládací prvek typu Image
Prvním ovládacím prvkem, který si v dnešním článku popíšeme, je widget nazvaný jednoduše Image. Víme již, že tento widget dokáže na plochu okna nebo dialogu umístit rastrový obrázek, který může být načtený z externích souborů typu PPM (Portable PixelMap), GIF (Graphics Interchange Format), PNG (Portable Network Graphics) popř. JPEG.
Ovšem musíme si dát pozor na to, že pouze první dva formáty jsou přímo podporovány knihovnou TkInter, nad níž je appJar postavena. To mj. znamená, že se při načítání obrázků uložených ve formátech PNG a JPEG musí obrázek dekódovat, a to knihovnami naprogramovanými přímo v Pythonu (tyto knihovny jsou součástí instalace appJar). To s sebou přináší výhodu snadné přenositelnosti (není nutné přistupovat k nativním knihovnám, řešit nekompatibility mezi knihovnami apod.), na druhou stranu je však dekódování rastrových obrázků dosti pomalé, což se negativně projeví u rozměrnějších obrázků a/nebo u aplikací spouštěných například na jednodeskových mikropočítačích Raspberry Pi a podobně „výkonných“ strojích.
Poznámka: alternativně je možné pro převod obrázků z prakticky libovolného formátu do GIFu použít knihovny PIL neboli (Python Imaging Library) či Pillow (fork dnes již nevyvíjené knihovny PIL), to však již vyžaduje zásahy do konfigurace systému, na němž má být vyvíjená aplikace provozována.
3. Malá odbočka – programové vytvoření procedurální textury s obrázkem
Ještě předtím, než si ukážeme, jakým způsobem je možné v knihovně appJar použít rastrové obrázky, si jeden takový obrázek programově vytvoříme. Bude se jednat o procedurální texturu založenou na efektu takzvaného moaré. Tuto procedurální texturu (či možná lépe řečeno rastrový vzorek) vytvořil John Connett z Minnesotské univerzity. O tomto vzorku, který v podstatě názorně ukazuje vliv aliasu při tvorbě rastrových obrázků, později pojednal i A. K. Dewdney v časopise Scientific American. Popisovaný vzorek je generovaný velmi jednoduchým a taktéž dostatečně rychlým způsobem: každému pixelu ve vytvářeném rastrovém obrázku (bitmapě) je přiřazena dvojice souřadnic [x, y]. Tyto souřadnice obecně neodpovídají celočíselným indexům pixelu, které můžeme například označit [i, j] (záleží totiž na zvoleném faktoru zvětšení popř. zmenšení vzorku). Posléze je pro každý pixel vypočtena hodnota z na základě jednoduchého vztahu z=x2+y2.
Obrázek 1: Moaré s kružnicovým vzorkem.
A to je vlastně celý algoritmus, ke kterému ještě přidáme část, která na základě vypočtené hodnoty z vybere vhodnou barvu z barvové palety a pixel následně touto barvou vyplní. Tímto přímočarým, rychlým a současně i jednoduchým způsobem je možné vytvářet mnohdy fantastické vzorky; pouze stačí měnit barvovou paletu (ideální jsou plynulé přechody mezi barvami – gradient) a měřítko, pomocí kterého se převádí celočíselné pozice pixelů v rastru [i, j] na souřadnice [x, y].
Obrázek 2: Mez zvětšení, při kterém již kružnicový vzorek začíná mizet.
4. První demonstrační příklad – moaré jako obrázek ve stupních šedi
Způsob tvorby moaré s kružnicovým vzorkem je ukázán v dnešním prvním demonstračním příkladu, který je založen na funkci nazvané recalc_circle_pattern(). Tato funkce provádí výpočet popsaný v předchozí kapitole, na konci pak převádí vypočtenou hodnotu do celočíselného rozsahu 0..255:
# Funkce provadejici vypocet moare s kruznicovym vzorkem def recalc_circle_pattern(image, xmin, ymin, xmax, ymax): width, height = image.size # rozmery obrazku stepx = (xmax - xmin)/width stepy = (ymax - ymin)/height print(xmin, xmax, ymin, ymax, width, height, stepx, stepy) y1 = ymin for y in range(0, height): x1 = xmin for x in range(0, width): x1 += stepx x2 = x1 * x1 y2 = y1 * y1 i = (int)(x2 + y2) & 255 color = (i, i, i) image.putpixel((x, y), color) y1 += stepy
Obrázek 3: Při určitém měřítku narazíme na limit, pod kterým již nevidíme další detaily (viz střed obrázku, při jehož zvětšení již další detaily nebudou patrné).
Obrázek je vytvořen knihovnou PIL, která se současně postará o jeho uložení do souboru formátu PNG:
from PIL import Image mez = (2 << 5) + 50 * 2.5 image = Image.new("RGB", (IMAGE_WIDTH, IMAGE_HEIGHT)) recalc_circle_pattern(image, -mez, -mez, mez, mez) image.save("bw_moare.png")
Obrázek 4: Procedurální textura vytvořená dnešním prvním demonstračním příkladem.
Následuje výpis úplného zdrojového kódu prvního demonstračního příkladu:
#!/usr/bin/env python # Vytvoreni obrazku s "kruznicovym moare" from PIL import Image # velikost obrazku # mocninou cisla 2 IMAGE_WIDTH = 256 IMAGE_HEIGHT = 256 # Funkce provadejici vypocet moare s kruznicovym vzorkem def recalc_circle_pattern(image, xmin, ymin, xmax, ymax): width, height = image.size # rozmery obrazku stepx = (xmax - xmin)/width stepy = (ymax - ymin)/height y1 = ymin for y in range(0, height): x1 = xmin for x in range(0, width): x1 += stepx x2 = x1 * x1 y2 = y1 * y1 i = (int)(x2 + y2) & 255 color = (i, i, i) image.putpixel((x, y), color) y1 += stepy mez = (2 << 5) + 50 * 2.5 image = Image.new("RGB", (IMAGE_WIDTH, IMAGE_HEIGHT)) recalc_circle_pattern(image, -mez, -mez, mez, mez) image.save("bw_moare.png")
5. Druhý demonstrační příklad – aplikace barvové palety
Obrázky vykreslené ve stupních šedi sice mohou být pro některé projekty zajímavé (survival horory atd.), ovšem většinou požadujeme obrázky barevné. Ve skutečnosti je řešení jednoduché – postačuje ke každému vypočtenému indexu vybrat vhodnou barvu z barvové palety. Tradičně mají barvové palety 256 barev, ovšem samozřejmě je v případě potřeby možné vytvořit rozsáhlejší či naopak menší palety. Pro účely našeho demonstračního příkladu použijeme barvovou paletu získanou z datových souborů programu Fractint a převedenou na Pythonovskou n-tici (samozřejmě lze použít i seznam). Upravená funkce pro vytvoření textury bude vypadat následovně:
# Funkce provadejici vypocet moare s kruznicovym vzorkem def recalc_circle_pattern(image, palette, xmin, ymin, xmax, ymax): width, height = image.size # rozmery obrazku stepx = (xmax - xmin)/width stepy = (ymax - ymin)/height y1 = ymin for y in range(0, height): x1 = xmin for x in range(0, width): x1 += stepx x2 = x1 * x1 y2 = y1 * y1 i = (int)(x2 + y2) & 255 color = (palette[i][0], palette[i][1], palette[i][2]) image.putpixel((x, y), color) y1 += stepy
Obrázek 5: Procedurální textura vytvořená dnešním druhým demonstračním příkladem.
Opět následuje výpis úplného zdrojového kódu demonstračního příkladu:
#!/usr/bin/env python # Vytvoreni obrazku s "kruznicovym moare" from PIL import Image # velikost obrazku # mocninou cisla 2 IMAGE_WIDTH = 256 IMAGE_HEIGHT = 256 # taken from Fractint palette = ( (000, 000, 0), (000, 000, 0), (000, 000, 4), (000, 000, 12), (000, 000, 16), (000, 000, 24), (000, 000, 32), (000, 000, 36), (000, 000, 44), (000, 000, 48), (000, 000, 56), (000, 000, 64), (000, 000, 68), (000, 000, 76), (000, 000, 80), (000, 000, 88), (000, 000, 96), (000, 000, 100), (000, 000, 108), (000, 000, 116), (000, 000, 120), (000, 000, 128), (000, 000, 132), (000, 000, 140), (000, 000, 148), (000, 000, 152), (000, 000, 160), (000, 000, 164), (000, 000, 172), (000, 000, 180), (000, 000, 184), (000, 000, 192), (000, 000, 200), (000, 4, 200), (000, 12, 200), (000, 16, 204), (000, 24, 204), (000, 28, 208), (000, 36, 208), (000, 40, 208), (000, 48, 212), (000, 56, 212), (000, 60, 216), (000, 68, 216), (000, 72, 216), (000, 80, 220), (000, 84, 220), (000, 92, 224), (000, 100, 224), (000, 104, 224), (000, 112, 228), (000, 116, 228), (000, 124, 232), (000, 128, 232), (000, 136, 232), (000, 140, 236), (000, 148, 236), (000, 156, 240), (000, 160, 240), (000, 168, 240), (000, 172, 244), (000, 180, 244), (000, 184, 248), (000, 192, 248), (000, 200, 252), (4, 200, 252), (12, 200, 252), (20, 204, 252), (28, 204, 252), (36, 208, 252), (44, 208, 252), (52, 208, 252), (60, 212, 252), (68, 212, 252), (76, 216, 252), (84, 216, 252), (92, 216, 252), (100, 220, 252), (108, 220, 252), (116, 224, 252), (124, 224, 252), (132, 224, 252), (140, 228, 252), (148, 228, 252), (156, 232, 252), (164, 232, 252), (172, 232, 252), (180, 236, 252), (188, 236, 252), (196, 240, 252), (204, 240, 252), (212, 240, 252), (220, 244, 252), (228, 244, 252), (236, 248, 252), (244, 248, 252), (252, 252, 252), (248, 252, 252), (244, 252, 252), (240, 252, 252), (232, 252, 252), (228, 252, 252), (224, 252, 252), (216, 252, 252), (212, 252, 252), (208, 252, 252), (200, 252, 252), (196, 252, 252), (192, 252, 252), (184, 252, 252), (180, 252, 252), (176, 252, 252), (168, 252, 252), (164, 252, 252), (160, 252, 252), (156, 252, 252), (148, 252, 252), (144, 252, 252), (140, 252, 252), (132, 252, 252), (128, 252, 252), (124, 252, 252), (116, 252, 252), (112, 252, 252), (108, 252, 252), (100, 252, 252), (96, 252, 252), (92, 252, 252), (84, 252, 252), (80, 252, 252), (76, 252, 252), (72, 252, 252), (64, 252, 252), (60, 252, 252), (56, 252, 252), (48, 252, 252), (44, 252, 252), (40, 252, 252), (32, 252, 252), (28, 252, 252), (24, 252, 252), (16, 252, 252), (12, 252, 252), (8, 252, 252), (000, 252, 252), (000, 248, 252), (000, 244, 252), (000, 240, 252), (000, 232, 252), (000, 228, 252), (000, 224, 252), (000, 216, 252), (000, 212, 252), (000, 208, 252), (000, 200, 252), (000, 196, 252), (000, 192, 252), (000, 184, 252), (000, 180, 252), (000, 176, 252), (000, 168, 252), (000, 164, 252), (000, 160, 252), (000, 156, 252), (000, 148, 252), (000, 144, 252), (000, 140, 252), (000, 132, 252), (000, 128, 252), (000, 124, 252), (000, 116, 252), (000, 112, 252), (000, 108, 252), (000, 100, 252), (000, 96, 252), (000, 92, 252), (000, 84, 252), (000, 80, 252), (000, 76, 252), (000, 72, 252), (000, 64, 252), (000, 60, 252), (000, 56, 252), (000, 48, 252), (000, 44, 252), (000, 40, 252), (000, 32, 252), (000, 28, 252), (000, 24, 252), (000, 16, 252), (000, 12, 252), (000, 8, 252), (000, 000, 252), (000, 000, 248), (000, 000, 244), (000, 000, 240), (000, 000, 236), (000, 000, 232), (000, 000, 228), (000, 000, 224), (000, 000, 220), (000, 000, 216), (000, 000, 212), (000, 000, 208), (000, 000, 204), (000, 000, 200), (000, 000, 196), (000, 000, 192), (000, 000, 188), (000, 000, 184), (000, 000, 180), (000, 000, 176), (000, 000, 172), (000, 000, 168), (000, 000, 164), (000, 000, 160), (000, 000, 156), (000, 000, 152), (000, 000, 148), (000, 000, 144), (000, 000, 140), (000, 000, 136), (000, 000, 132), (000, 000, 128), (000, 000, 124), (000, 000, 120), (000, 000, 116), (000, 000, 112), (000, 000, 108), (000, 000, 104), (000, 000, 100), (000, 000, 96), (000, 000, 92), (000, 000, 88), (000, 000, 84), (000, 000, 80), (000, 000, 76), (000, 000, 72), (000, 000, 68), (000, 000, 64), (000, 000, 60), (000, 000, 56), (000, 000, 52), (000, 000, 48), (000, 000, 44), (000, 000, 40), (000, 000, 36), (000, 000, 32), (000, 000, 28), (000, 000, 24), (000, 000, 20), (000, 000, 16), (000, 000, 12), (000, 000, 8), (000, 000, 0), (000, 000, 0)) # Funkce provadejici vypocet moare s kruznicovym vzorkem def recalc_circle_pattern(image, palette, xmin, ymin, xmax, ymax): width, height = image.size # rozmery obrazku stepx = (xmax - xmin)/width stepy = (ymax - ymin)/height y1 = ymin for y in range(0, height): x1 = xmin for x in range(0, width): x1 += stepx x2 = x1 * x1 y2 = y1 * y1 i = (int)(x2 + y2) & 255 color = (palette[i][0], palette[i][1], palette[i][2]) image.putpixel((x, y), color) y1 += stepy mez = (2 << 5) + 50 * 2.5 image = Image.new("RGB", (IMAGE_WIDTH, IMAGE_HEIGHT)) recalc_circle_pattern(image, palette, -mez, -mez, mez, mez) image.save("moare.png")
6. Použití ovládacího prvku typu Image
Obrázky pro otestování již máme připraveny, takže se nyní podívejme na to, jakým způsobem je možné obrázek načíst a následně vložit na plochu hlavního okna aplikace. Ve skutečnosti je to velmi snadné, protože přímo při vytváření widgetu s obrázkem můžeme specifikovat soubor, z něhož se má obrázek načíst. Současně se z rozměrů obrázku zjistí velikost widgetu (prvním parametrem se specifikuje identifikátor widgetu):
app.addImage("image", "moare.png")
Obrázek 6: Screenshot příkladu, v němž je použit widget typu Image.
Celý příklad, který po svém spuštění zobrazí rastrový obrázek načtený z externího souboru, vypadá následovně:
#!/usr/bin/env python from appJar import gui import tkinter app = gui() def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addImage("image", "moare.png") app.go()
7. Rastrový obrázek uložený přímo ve zdrojovém kódu aplikace
V některých případech můžeme chtít, například kvůli zjednodušení instalace vyvíjené aplikace, aby byl rastrový obrázek přímo součástí zdrojových kódů. I to je možné zařídit, ovšem tato možnost se obvykle používá pouze pro obrázky s malým rozlišením, protože obrázek uložený přímo ve zdrojových kódech zabere větší prostor (o více než 33%). V dalším textu se dozvíme, proč tomu tak je. Aby bylo možné ukládat data rastrového obrázku přímo ve zdrojových kódech, je nutné je nejprve vhodným způsobem zakódovat, protože ne všechny znaky je možné ve zdrojových kódech použít (některé jsou „jen“ nečitelně či needitovatelné, další pak mají speciální význam).
Z tohoto důvodu podporuje knihovna appJar načítání obrázků zakódovaných s využitím Base64. Výsledek je umístěn do běžného řetězce reprezentovatelného v programovacím jazyku Python. Povšimněte si, že libovolná data zakódovaná do Base64 lze skutečně reprezentovat řetězcem, protože se ve výsledku nebudou nacházet žádné znaky, které by tomu bránily (v Base64 se totiž používají jen znaky a-z, A-Z, 0–9, plus, lomítko, znak = a taktéž znak pro konec řádku). Navíc mají řádky výsledného řetězce jen 64 až 76 znaků na každém řádku, což odpovídá doporučení PEP-8.
8. Postup při uložení obrázku do zdrojového kódu
Podívejme se nyní na způsob uložení dat rastrového obrázku přímo do zdrojových kódů. Zdrojem bude libovolný soubor s rastrovým obrázkem malých rozměrů. Ideální je formát GIF. Obrázek nejprve zakódujeme do Base64 a výsledek uložíme do pomocného textového souboru:
base64 obrázek.gif > obrázek.py
Obrázek 7: Ikona uložená ve formátu GIF, kterou přes utilitu base64 převedeme do plnohodnotného řetězce jazyka Python.
Pokud například budeme převádět ikonu application-exit.gif získanou z adresy (https://github.com/tisnik/presentations/blob/master/Python_GUI/appJar/icons/application-exit.gif), použijeme příkaz:
base64 application-exit.gif > application-exit.py
Zakódováním by měl vzniknout přesně tento soubor:
R0lGODlhFgAWAPZJAAAAAHgCAYcHBYgZGZUJA5cRCpsYEpkkFpkrJKQLBKgYBaQQELQCArEYGKQo Fq4yFrUlBrAoE7g5F6g0J6Y2NqlMO6lJRrVLRLpSSLZaWbxpacg1B9U7BMwpKMw5ONQnJ9w4OMlH FttDBtFOFudJBPNPAPdTAMFbTsZmV8J3d+FiYut9fauCebGHfqyLhK6cnLaMhL+Si7ynp8mIiMaW j86Skt+KitGZmcSsq92trd6yrd+ysuuIh+6oqOS7reW3svKqqurDtM7OztjY2OTMyujT0/DExOjo 6Pj4+P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5 BAEAAEkALAAAAAAWABYAAAf+gEmCg4SFg0hIgz0rIB+Oj5AdjypANoI8Rjs5mzs7Pz2boZw9HoId N6ipqquoOQ2CDDOyLy81sreztTM3C7CyOEdHMriyMsE4MzW9SQwpKURIR0gyzs7G0UQpM8sMGt5F wUc43sbB2RozBoIJGe0aRMFFOMDmGu0pBevt+/BHQ/9DiOzTkIKAoAUUKFS4gAEDEYABUZyoMIFA AYNJFhAQQEBBBAkuILaAoCDBxgICDm7sCIEFRCFCWJAkQMBAyiQGaCZQ0CLYEB8+YA4ZqaDmzZwd Y4QLQoKEj39HYkBIcOAoTRpLSZQwUSLIkGA0FDgIIOgATR/RgojQypVEEGlMP8SSRXLgogIfQTZw WNuUA4cgcQk4SInEQl0FECDoFbFWBIcNG0gqmDDXQgUHDyRICBFihOcRnDU/mICAbJIBAVKrXs16 taHXsAUFAgA7
Povšimněte si, že vytvořený soubor je větší, než originál. Je tomu tak především z toho důvodu, že se v kódování Base64 používá jen 64 vybraných znaků a nikoli všech 256 možných hodnot, tudíž se každé tři bajty (3×8=24 bitů) uloží ve čtyřech znacích (4×6=24 bitů). Navíc se do výsledného souboru ukládají znaky pro konec řádku a na samotném konci pak výplně tvořené znaky „=“.
Ve druhém kroku otevřeme vytvořený soubor „application-exit.py“ v textovém editoru a dopíšeme na první řádek deklaraci proměnné a její inicializaci řetězcem. Vzhledem k tomu, že se jedná o víceřádkový řetězec, použije se trojice uvozovek (konce řádků se v Base64 ignorují, takže vlastní data klidně mohou začít až na řádku následujícím):
image_data = """
Na posledním řádku pak pouze řetězec uzavřeme:
"""
Výsledkem by měl být následující úryvek zdrojového kódu, který je bez problémů interpretovatelný Pythonem:
image_data = """ R0lGODlhFgAWAPZJAAAAAHgCAYcHBYgZGZUJA5cRCpsYEpkkFpkrJKQLBKgYBaQQELQCArEYGKQo Fq4yFrUlBrAoE7g5F6g0J6Y2NqlMO6lJRrVLRLpSSLZaWbxpacg1B9U7BMwpKMw5ONQnJ9w4OMlH FttDBtFOFudJBPNPAPdTAMFbTsZmV8J3d+FiYut9fauCebGHfqyLhK6cnLaMhL+Si7ynp8mIiMaW j86Skt+KitGZmcSsq92trd6yrd+ysuuIh+6oqOS7reW3svKqqurDtM7OztjY2OTMyujT0/DExOjo 6Pj4+P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5 BAEAAEkALAAAAAAWABYAAAf+gEmCg4SFg0hIgz0rIB+Oj5AdjypANoI8Rjs5mzs7Pz2boZw9HoId N6ipqquoOQ2CDDOyLy81sreztTM3C7CyOEdHMriyMsE4MzW9SQwpKURIR0gyzs7G0UQpM8sMGt5F wUc43sbB2RozBoIJGe0aRMFFOMDmGu0pBevt+/BHQ/9DiOzTkIKAoAUUKFS4gAEDEYABUZyoMIFA AYNJFhAQQEBBBAkuILaAoCDBxgICDm7sCIEFRCFCWJAkQMBAyiQGaCZQ0CLYEB8+YA4ZqaDmzZwd Y4QLQoKEj39HYkBIcOAoTRpLSZQwUSLIkGA0FDgIIOgATR/RgojQypVEEGlMP8SSRXLgogIfQTZw WNuUA4cgcQk4SInEQl0FECDoFbFWBIcNG0gqmDDXQgUHDyRICBFihOcRnDU/mICAbJIBAVKrXs16 taHXsAUFAgA7 """
9. Použití ovládacího prvku typu Image a obrázku ze zdrojového kódu
Ve chvíli, kdy již máme zakódovaná data s obrázkem uložena do řetězce, je možné tato data využít pro vytvoření widgetu s obrázkem. Pouze namísto zavolání metody addImage:
app.addImage("image", "moare.png")
použijeme metodu pojmenovanou addImageData, a to následujícím způsobem:
app.addImageData("image", image_data)
Obrázek 8: Screenshot příkladu, v němž je použit widget typu Image a obrázek uložený přímo ve zdrojovém kódu.
Celý příklad, který po svém spuštění zobrazí na ploše okna ikonu, bude vypadat následovně:
#!/usr/bin/env python from appJar import gui import tkinter image_data = """ R0lGODlhFgAWAPZJAAAAAHgCAYcHBYgZGZUJA5cRCpsYEpkkFpkrJKQLBKgYBaQQELQCArEYGKQo Fq4yFrUlBrAoE7g5F6g0J6Y2NqlMO6lJRrVLRLpSSLZaWbxpacg1B9U7BMwpKMw5ONQnJ9w4OMlH FttDBtFOFudJBPNPAPdTAMFbTsZmV8J3d+FiYut9fauCebGHfqyLhK6cnLaMhL+Si7ynp8mIiMaW j86Skt+KitGZmcSsq92trd6yrd+ysuuIh+6oqOS7reW3svKqqurDtM7OztjY2OTMyujT0/DExOjo 6Pj4+P///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5 BAEAAEkALAAAAAAWABYAAAf+gEmCg4SFg0hIgz0rIB+Oj5AdjypANoI8Rjs5mzs7Pz2boZw9HoId N6ipqquoOQ2CDDOyLy81sreztTM3C7CyOEdHMriyMsE4MzW9SQwpKURIR0gyzs7G0UQpM8sMGt5F wUc43sbB2RozBoIJGe0aRMFFOMDmGu0pBevt+/BHQ/9DiOzTkIKAoAUUKFS4gAEDEYABUZyoMIFA AYNJFhAQQEBBBAkuILaAoCDBxgICDm7sCIEFRCFCWJAkQMBAyiQGaCZQ0CLYEB8+YA4ZqaDmzZwd Y4QLQoKEj39HYkBIcOAoTRpLSZQwUSLIkGA0FDgIIOgATR/RgojQypVEEGlMP8SSRXLgogIfQTZw WNuUA4cgcQk4SInEQl0FECDoFbFWBIcNG0gqmDDXQgUHDyRICBFihOcRnDU/mICAbJIBAVKrXs16 taHXsAUFAgA7 """ app = gui() def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addLabel("label", "Image loaded from data (string)") app.addImageData("image", image_data) app.go()
10. Ovládací prvek zobrazující koláčový diagram
Další ovládací prvek, který v knihovně appJar najdeme, dokáže zobrazit koláčový diagram (graf) pro zvolená data. Možnosti koláčového diagramu jsou však omezeny, například nefunguje legenda (tu lze přidat do jiného widgetu), chybí popisky výsečí, není možné jednoduše modifikovat barvy jednotlivých výsečí a jelikož se pro zobrazení používá Canvas z knihovny Tkinter, není použit antialiasing, což je jasně patrné jak na obloucích, tak i na šikmých úsečkách.
Vstupní data pro koláčový graf jsou uložena ve slovníku, kde klíče tvoří popisky výsečí a hodnoty udávají relativní velikost výseče. Pro ukázku jsem použil počet křesel obsazených stranami v posledních volbách. Data jsou seřazena podle čísel stran, nikoli podle preferencí autora :-):
data = { "ODS": 25, "CSSD": 15, "STAN": 6, "KSCM": 15, "Pirati": 22, "TOP 09": 7, "ANO": 78, "KDU-CSL": 10, "SPD": 22}
Koláčový graf se do hlavního okna aplikace přidá velmi jednoduše:
app.addPieChart("piechart", data)
S tímto výsledkem:
Obrázek 9: Koláčový diagram (graf) zobrazený předchozím příkazem.
Podívejme se na úplný kód příkladu:
#!/usr/bin/env python from appJar import gui import tkinter app = gui() def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() data = { "ODS": 25, "CSSD": 15, "STAN": 6, "KSCM": 15, "Pirati": 22, "TOP 09": 7, "ANO": 78, "KDU-CSL": 10, "SPD": 22} app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addPieChart("piechart", data) app.go()
Obrázek 10: Bublinová nápověda zobrazí data u vybrané výseče.
Poznámka: pokud vám nevyhovují barvy jednotlivých výsečí (jsou docela nudné a navzájem podobné), lze je upravit buď přímo ve zdrojovém kódu appjar.py nebo si můžete ze třídy PieChart odvodit třídu vlastní:
class PieChart(Canvas): # constant for available colours COLOURS = [ "#023fa5", "#7d87b9", "#bec1d4", "#d6bcc0", "#bb7784", "#8e063b", "#4a6fe3", "#8595e1", "#b5bbe3", "#e6afb9", "#e07b91", "#d33f6a", "#11c638", "#8dd593", "#c6dec7", "#ead3c6", "#f0b98d", "#ef9708", "#0fcfc0", "#9cded6", "#d5eae7", "#f3e1eb", "#f6c4e1", "#f79cd4"]
11. Zobrazení mřížky LED (emulace počítače Micro Bit)
O tom, že je knihovna appJar určena primárně pro výuku programování, svědčí i existence posledního ovládacího prvku. Jedná se o specializovaný widget, který dokáže zobrazit mřížku 5×5 čtverečků, které svou barvou napodobují mřížku LED z populárního jednodeskového mikropočítače Micro Bit, jenž se poměrně masivně začal používat i v tuzemsku (což je ostatně jen dobře, mj. i díky rozsáhlému ekosystému, který okolo tohoto zařízení vznikl).
Mřížka s dvaceti pěti LED se do okna aplikace vkládá naprosto stejným způsobem, jako jakýkoli jiný ovládací prvek, tj. metodou addJménoWidtgetu(„identifikátor_widgetu“):
app.addMicroBit("microbit")
Pokud budeme chtít rozsvítit či naopak zhasnout všechny LED, je možné použít metodu setMicroBitImage. Této metodě se kromě identifikátoru widgetu předává i řetězec obsahující intenzitu světla všech LED, přičemž intenzita může být v rozsahu od nuly do devíti. LED jsou rozděleny do řádků, které jsou od sebe odděleny dvojtečkou. To znamená, že pokud budeme chtít zobrazit velké písmeno „M“, vytvoříme si nejdříve (například na papír) bitmapu s tvarem písmena:
* * ** ** * * * * * * *
Zvolíme si intenzitu svitu diod – 0 pro zhasnuté diody, 9 pro diody rozsvícené:
90009 99099 90909 90009 90009
Převedeme intenzity na řetězec:
90009:99099:90909:90009:90009
A zavoláme výše zmíněnou metodu setMicroBitImage:
app.setMicroBitImage("microbit", "90009:99099:90909:90009:90009")
Obrázek 11: Písmeno „M“ zobrazené v matici 5×5 „LED“.
Opět si ukažme příklad, v němž se tento neobvyklý ovládací prvek použije:
#!/usr/bin/env python from appJar import gui import tkinter def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addMicroBit("microbit") app.setMicroBitImage("microbit", "90009:99099:90909:90009:90009") app.go()
12. Programová změna světlosti jednotlivých LED
Světlost LED v matici je možné změnit metodou nazvanou setMicroBitPixel, které se předá čtveřice parametrů – identifikátor widgetu, x-ová souřadnice LED v matici (0–4), y-ová souřadnice LED v matici (taktéž 0–4) a světlost pixelu v rozmezí 0 až 9. Všechny celočíselné hodnoty jsou po zavolání metody zkontrolovány oproti uvedeným rozsahům:
app.setMicroBitPixel("microbit", x, y, brightness)
Pokud například budeme chtít zobrazit diagonální gradientní přechod od světlosti 1 do 9, lze to provést následovně:
for y in range(0, 5): for x in range(0, 5): brightness = x + y + 1 app.setMicroBitPixel("microbit", x, y, brightness)
Pokud budete potřebovat všechny LED vypnout, zajistí to metoda:
app.clearMicroBit("microbit")
Obrázek 12: Gradientní přechod zobrazený LED v matici 5×5.
Opět následuje výpis celého zdrojového kódu příkladu:
#!/usr/bin/env python from appJar import gui import tkinter def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) app.addMicroBit("microbit") for y in range(0, 5): for x in range(0, 5): brightness = x + y + 1 app.setMicroBitPixel("microbit", x, y, brightness) app.go()
13. Použití prvku Canvas z Tkinteru
Ve chvíli, kdy je zapotřebí zobrazit složitější grafické prvky, než jsou nemodifikovatelné rastrové obrázky, koláčové diagramy nebo matice 5×5 „LED“, je nutné obejít základní možnosti knihovny appJar a namísto toho použít nám již známé prostředky nabízené knihovnou Tkinter, které jsme si představili v trojici článků [1] [2] a [3]. Využijeme přitom toho, že referenci na objekt představující hlavní okno aplikace lze získat velmi snadno:
app = gui() root = app.topLevel
Kreslicí plátno (canvas) poté na hlavním oknu vytvoříme přesně stejným způsobem, jaký již známe z knihovny Tkinter:
canvas = tkinter.Canvas(root, width=šířka_plátna, height=výška_plátna) canvas.pack()
Jediný problém spočívá v tom, že se plátno sice na hlavní okno umístí, ale knihovna appJar není o této operaci korektně informována. Výsledkem je, že plátno bude vždy zobrazené ve spodní části okna, což však pro mnoho aplikací nebude velkým problémem (pokud ano, je nutné pracovat s interním objektem all.ContainerStack, jehož vlastnosti se ovšem mohou v dalších verzích knihovny appJar lišit).
14. Ukázka použití Canvasu
Podívejme se nyní na způsob použití kreslicího plátna (canvasu) v praxi. Na hlavní okno vložíme kromě kreslicího plátna i další widget, konkrétně hlavní menu:
app = gui() fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) canvas = tkinter.Canvas(app.topLevel, width=256, height=256) canvas.pack()
Na kreslicí plátno poté vykreslíme několik objektů:
canvas.create_oval(10, 10, 100, 100, fill="red", outline="blue", width=3) canvas.create_line(0, 0, 255, 255, width=5) canvas.create_line(0, 255, 255, 0, dash=123) canvas.create_rectangle(70, 140, 230, 180, fill="white") canvas.create_text(150, 160, text="Hello world!", fill="brown", font="Helvetica 20")
Výsledek můžeme vidět na dalším obrázku:
Obrázek 13: Kreslicí plátno vložené do okna aplikace vytvořené přes appJar.
Celý zdrojový kód příkladu s kreslicím plátnem vloženým do aplikace vytvořené přes appJar vypadá takto:
#!/usr/bin/env python from appJar import gui import tkinter app = gui() def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) canvas = tkinter.Canvas(app.topLevel, width=256, height=256) canvas.pack() canvas.create_oval(10, 10, 100, 100, fill="red", outline="blue", width=3) canvas.create_line(0, 0, 255, 255, width=5) canvas.create_line(0, 255, 255, 0, dash=123) canvas.create_rectangle(70, 140, 230, 180, fill="white") canvas.create_text(150, 160, text="Hello world!", fill="brown", font="Helvetica 20") app.go()
Poznámka: knihovna appJar pro pojmenování metod používá CamelCase zatímco knihovna Tkinter u svých identifikátorů používá oddělení slov podtržítkem, takže je výsledek dosti nekonzistentní. To je jedna z nevýhod, s nimiž se setkáme při kombinaci možností obou knihoven.
Kreslicí plátno si zachovává všechny vlastnosti, které již známe z knihovny Tkinter. Můžeme tak zajistit například změnu stylu vybrané entity po najetí myši, například:
canvas.create_rectangle(230, 110, 270, 190, fill=None, activeoutline='yellow', width=5) canvas.create_oval(320, 220, 380, 280, fill=None, activefill='#8080ff', width=5)
Obrázek 14: Kreslicí plátno s několika aktivními prvky reagujícími na najetí myši.
V dalším příkladu je na plátno vloženo patnáct prvků, které reagují na najetí myši (onMouseOver):
#!/usr/bin/env python from appJar import gui import tkinter WIDTH = 400 HEIGHT = 400 GRID_SIZE = 100 def onMenuItemSelect(menuItem): if menuItem == "Quit": app.stop() def basicCanvas(root, width, height, grid_size): canvas = tkinter.Canvas(root, width=width, height=height, background='#ccffcc') canvas.pack() drawGrid(canvas, width, height, grid_size) return canvas def drawGrid(canvas, width, height, grid_size): for x in range(0, width, grid_size): canvas.create_line(x, 0, x, height, dash=7, fill="gray") for y in range(0, height, grid_size): canvas.create_line(0, y, width, y, dash=7, fill="gray") app = gui() app.setSticky("news") fileMenu = ["Quit"] app.addMenuList("File", fileMenu, onMenuItemSelect) canvas = basicCanvas(app.topLevel, WIDTH, HEIGHT, GRID_SIZE) canvas.create_rectangle(10, 30, 90, 70, fill='#ff8080', width=2, activefill='white') canvas.create_rectangle(110, 30, 190, 70, fill='#ff8080', width=2, dash=(5, 5), activedash=1) canvas.create_rectangle(30, 110, 70, 190, fill='#ff8080', activeoutline='yellow') canvas.create_rectangle(20, 220, 80, 280, fill='#ff8080', activeoutline='yellow', activewidth='5') canvas.create_oval(130, 110, 170, 190, fill='#8080ff', width=2, activedash=(10, 10)) canvas.create_oval(120, 220, 180, 280, fill=None, activefill='#8080ff') canvas.create_rectangle(210, 30, 290, 70, fill=None, width=2, activefill='white') canvas.create_rectangle(310, 30, 390, 70, fill=None, width=2, dash=(5, 5), activedash=1) canvas.create_rectangle(230, 110, 270, 190, fill=None, activeoutline='yellow', width=5) canvas.create_rectangle(220, 220, 280, 280, fill=None, activeoutline='yellow', activewidth='5') canvas.create_oval(330, 110, 370, 190, fill=None, width=2, activedash=(10, 10)) canvas.create_oval(320, 220, 380, 280, fill=None, activefill='#8080ff', width=5) canvas.create_line(10, 330, 90, 370, fill='#80ff80', width=2, activefill='white') canvas.create_line(110, 330, 190, 370, fill='#80ff80', width=20, activefill='white') canvas.create_line(210, 330, 290, 370, fill='#80ff80', width=20, activefill='white', dash=10) canvas.pack() app.go()
15. Repositář s demonstračními příklady
Zdrojové kódy všech devíti dnes popsaných demonstračních příkladů byly opět 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:
Poznámka: pro úspěšné spuštění těchto příkladů musíte mít v aktuálním adresáři rozbalenou knihovnu appJar!. Podrobnosti o instalaci jsme si řekli v úvodním článku.
Obrázek 15: Příště si mj. ukážeme použití modulu turtle společně s knihovnou appJar.
16. Odkazy na Internetu
- 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/