Ve druhém článku o knihovně Pillow určené programátorům, kteří potřebují manipulovat s rastrovými obrázky ve skriptech nebo i rozsáhlejších aplikacích psaných v Pythonu, si ukážeme některé pokročilejší operace nabízené touto knihovnou. Nejprve si představíme modul nazvaný ImageEnhance určený pro manipulaci se základními charakteristikami rastrových obrázků. Dále si řekneme, jak je možné získat základní statistické informace o obrázku, a to včetně histogramu. Druhá polovina článku bude věnována popisu vybraných kreslicích operací, způsobu specifikace barev kreslení apod.
Obsah
2. Modul ImageEnhance určený pro manipulace se základními charakteristikami rastrových obrázků
3. Zaostření nebo rozostření obrázku
4. Zvýšení a snížení kontrastu
7. Získání základních statistik o rastrovém obrázku
9. Operace pro manipulaci s jednotlivými pixely v rastrovém obrázku
12. Kreslicí operace využívající modul ImageDraw
13. Nakreslení mřížky do načteného obrázku
14. Vykreslení různobarevných čtverců
15. Použití polyčar (lomených čar) pro tvorbu složitějších obrazců
16. Vykreslení obrazce složeného z kružnic
17. Základy práce s textem: rasterizace a vykreslení textu do obrázku
18. Obsah následující části seriálu
19. Repositář s demonstračními příklady
1. Užitečné knihovny a moduly pro Python: kreslení a pokročilé manipulace s obrázky v knihovně Pillow
V předchozím článku seriálu o užitečných modulech určených pro programovací jazyk Python jsme si ve stručnosti představili knihovnu Pillow určenou pro manipulaci s rastrovými obrázky přímo v Pythonu. Připomeňme si, že tato knihovna dokáže načítat a ukládat rastrové obrázky v mnoha formátech, samozřejmě včetně většiny nejpopulárnějších rastrových grafických formátů, mezi něž patří především formáty GIF, JPEG, PNG, TIFF (různé varianty) a například i WebP či BMP. Do určité míry jsou podporovány i formáty PSD a dokonce i PDF, což samozřejmě není čistý rastrový formát. Tato knihovna však dokáže provádět i další operace, z nichž některé si popíšeme v dnešním článku. Především se jedná o manipulaci se základními charakteristikami rastrových obrázků (zaostření, kontrast, ...) a taktéž o vykreslování do obrázků, buď na úrovni jednotlivých pixelu či s využitím vysokoúrovňových 2D operací.
Obrázek 1: Testovací obrázek Lenny použitý ve většině dnešních demonstračních příkladů.
2. Modul ImageEnhance určený pro manipulace se základními charakteristikami rastrových obrázků
První modul, s nímž se dnes seznámíme, se jmenuje ImageEnhance. Tento modul slouží pro manipulaci se základními charakteristikami rastrového obrázku. Jedná se především o zaostření popř. naopak o rozostření (rozmazání) obrázku, změnu kontrastu, změnu jasu a změnu barevného podání (resp. poněkud nepřesně řečeno barevného kontrastu). Všechny tyto operace jsou globální, tj. mají dopad na všechny pixely rastrového obrázku (na rozdíl od operací kreslení, které jsou omezeny na přesně vymezenou oblast). Modul ImageEnhance se používá zhruba následovně:
Nejprve obrázek, který potřebujeme modifikovat, samozřejmě načteme:
# načtení originálního obrázku Leny
test_image = Image.open(filename)
test_image.load()
Dále je zapotřebí provést inicializaci modulu ImageEnhance. To se provádí nepřímo – zavoláním některého ze speciálních konstruktorů, které přímo popisují, jaká operace se provede. Například při snaze o zaostření nebo rozostření obrázku použijeme konstruktor ImageEnhance.Sharpness, kterému předáme obrázek, na který se má operace aplikovat:
# inicializace "vylepšovače" obrázků
enhancer = ImageEnhance.Sharpness(test_image)
Ve chvíli, kdy je objekt typu ImageEnhance zkonstruován, můžeme zavolat jeho metodu enhance(), které se předává kladné reálné číslo. To zhruba odpovídá otočení ovladače na starém analogovém televizoru – hodnoty pod 1,0 odpovídají snížení úrovně (zde úrovně zaostření), hodnoty větší než 1,0 naopak zvýšení úrovně. Většinou není stanovena horní mez, tu si však můžete vyzkoušet sami:
sharper_image = enhancer.enhance(5.0)
Výsledkem zavolání metody enhance() je nový rastrový obrázek, který si můžeme uložit, zobrazit ho, aplikovat na něj další operaci atd.:
# uložení upraveneného obrázku sharper_image.save("enhancer_sharpness_sharper.png") # zobrazení upraveného obrázku sharper_image.show() blurred_image.show()
Poznámka: z technologického hlediska jsou všechny operace v modulu ImageEnhance implementovány formou takzvaných parametrizovatelných konvolučních filtrů, s jejichž jednoduchými obdobami jsme se již setkali v předchozím článku.
3. Zaostření nebo rozostření obrázku
V dnešním prvním demonstračním příkladu je ukázán způsob aplikace zaostření popř. rozostření s využitím filtru ImageEnhance.Sharpness. Výsledky dvou aplikací tohoto filtru jsou zobrazeny na následujících dvou obrázcích:
Obrázek 2: Výsledek operace zaostření (koeficient=5,0).
Obrázek 3: Výsledek operace rozostření (koeficient=0,5).
Úplný zdrojový kód tohoto příkladu vypadá následovně:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
from PIL import Image
from PIL import ImageEnhance
filename = "Lenna.png"
try:
# načtení originálního obrázku Leny
test_image = Image.open(filename)
test_image.load()
# inicializace "vylepšovače" obrázků
enhancer = ImageEnhance.Sharpness(test_image)
# vytvoření "vylepšených" i "zhoršených" obrázků
sharper_image = enhancer.enhance(5.0)
blurred_image = enhancer.enhance(0.5)
# uložení upravených obrázků
sharper_image.save("enhancer_sharpness_sharper.png")
blurred_image.save("enhancer_sharpness_blurred.png")
# zobrazení originálu i upravených obrázků
test_image.show()
sharper_image.show()
blurred_image.show()
except Exception as e:
print(e)
4. Zvýšení a snížení kontrastu
Druhý demonstrační příklad je podobný příkladu prvnímu, ovšem namísto zaostřování a rozostřování rastrového obrázku zde upravujeme jeho kontrast:
Obrázek 4: Výsledek operace zvýšení kontrastu (koeficient=2,0).
Obrázek 5: Výsledek operace snížení kontrastu (koeficient=0,5).
Opět se podívejme na úplný zdrojový kód tohoto příkladu:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
from PIL import Image
from PIL import ImageEnhance
filename = "Lenna.png"
try:
# načtení originálního obrázku Leny
test_image = Image.open(filename)
test_image.load()
# inicializace "vylepšovače" obrázků
enhancer = ImageEnhance.Contrast(test_image)
# vytvoření "vylepšených" i "zhoršených" obrázků
more_contrast_image = enhancer.enhance(2.0)
less_contrast_image = enhancer.enhance(0.5)
# uložení upravených obrázků
more_contrast_image.save("enhancer_contrast_more.png")
less_contrast_image.save("enhancer_contrast_less.png")
# zobrazení originálu i upravených obrázků
test_image.show()
more_contrast_image.show()
less_contrast_image.show()
except Exception as e:
print(e)
5. Zvýšení a snížení jasu
Pokud modul ImageEnhance dokáže měnit kontrast, asi každého čtenáře napadne, že podobně tomu bude i v případě jasu. A skutečně – pomocí filtru ImageEnhance.Brightness můžeme velmi snadno manipulovat i s jasem celého obrázku s následujícími výsledky:
Obrázek 6: Výsledek operace zvýšení jasu (koeficient=2,0).
Obrázek 7: Výsledek operace snížení jasu (koeficient=0,5).
Opět si pro úplnost ukažme úplný zdrojový kód tohoto dnešního v pořadí již třetího příkladu:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
from PIL import Image
from PIL import ImageEnhance
filename = "Lenna.png"
try:
# načtení originálního obrázku Leny
test_image = Image.open(filename)
test_image.load()
# inicializace "vylepšovače" obrázků
enhancer = ImageEnhance.Brightness(test_image)
# vytvoření "vylepšených" i "zhoršených" obrázků
more_brightness_image = enhancer.enhance(2.0)
less_brightness_image = enhancer.enhance(0.5)
# uložení upravených obrázků
more_brightness_image.save("enhancer_brightness_more.png")
less_brightness_image.save("enhancer_brightness_less.png")
# zobrazení originálu i upravených obrázků
test_image.show()
more_brightness_image.show()
less_brightness_image.show()
except Exception as e:
print(e)
6. Změna sytosti barev
Posledním filtrem podporovaným modulem ImageEnhance je filtr pro změnu sytosti barev (někdy se setkáme i s termínem barevný kontrast a skutečně jsme mohli podobný ovládací prvek u starších barevných televizí nalézt). Pro tento účel slouží filtr nazvaný Color a výsledky jeho aplikace můžeme vidět na dalších dvou screenshotech:
Obrázek 8: Výsledek operace zvýšení barevného kontrastu (koeficient=2,0).
Obrázek 9: Výsledek operace snížení barevného kontrastu (koeficient=0,5).
Úplný zdrojový kód tohoto příkladu čtvrtého demonstračního příkladu vypadá následovně:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
from PIL import Image
from PIL import ImageEnhance
filename = "Lenna.png"
try:
# načtení originálního obrázku Leny
test_image = Image.open(filename)
test_image.load()
# inicializace "vylepšovače" obrázků
enhancer = ImageEnhance.Color(test_image)
# vytvoření "vylepšených" i "zhoršených" obrázků
more_colored_image = enhancer.enhance(2.0)
less_colored_image = enhancer.enhance(0.5)
# uložení upravených obrázků
more_colored_image.save("enhancer_color_more.png")
less_colored_image.save("enhancer_color_less.png")
# zobrazení originálu i upravených obrázků
test_image.show()
more_colored_image.show()
less_colored_image.show()
except Exception as e:
print(e)
7. Získání základních statistik o rastrovém obrázku
Další oblastí, v níž je možné v případě potřeby využít možnosti nabízené knihovnou Pillow, je výpočet základních statistických informací o rastrových obrázcích. Především se jedná o zjištění maximální, minimální, průměrné atd. úrovně jednotlivých pixelů, standardní odchylky apod. Výpočty těchto hodnot do značné míry závisí na použitém barvovém modelu. U černobílých obrázků a obrázků reprezentovaných ve stupních šedi je většina statistických informací vrácena ve formě skalární hodnoty uložené do jednoprvkového seznamu, ovšem u obrázků, v nichž se používá barvový model RGB, je každá statistická hodnota vypočtena a vrácena formou trojice hodnot v seznamu – každý prvek seznamu odpovídá jednomu barvovému kanálu R (red), G (green), B (blue). Z obrázku se získají statistické informace následovně:
# inicializace třídy se statistikami o obrázku stat = ImageStat.Stat(test_image)
Výpočet se provede automaticky, takže jen můžeme přistoupit ke čtení atributů objektu stat:
pocet_pixelu = cnt=stat.count prumerne_hodnoty = stat.mean median = stat.median odchylka = stat.var
Nepatrně složitější je zjištění minimálních a maximálních hodnot všech barvových složek – ty je nutné vyčíst z jedné n-tice obsahující dvojici (min, max), tedy minimální a maximální hodnoty dané barvové složky:
min = [v[0] for v in stat.extrema] max = [v[1] for v in stat.extrema]
Podívejme se nejdříve na způsob získání statistických informací o klasickém obrázku Leny používajícím barvový prostor RGB. Nejdříve si uvedeme zdrojový kód tohoto příkladu, který vypadá následovně:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
from PIL import Image
from PIL import ImageStat
filename = "Lenna.png"
try:
# načtení originálního obrázku Leny
test_image = Image.open(filename)
test_image.load()
# inicializace třídy se statistikami o obrázku
stat = ImageStat.Stat(test_image)
# zobrazení statistických informací o obrázku
print("Pixel count: {cnt}".format(cnt=stat.count))
print("Min values: {min}".format(min=[v[0] for v in stat.extrema]))
print("Max values: {max}".format(max=[v[1] for v in stat.extrema]))
print("Average values: {val}".format(val=stat.mean))
print("Median values: {med}".format(med=stat.median))
print("Variance: {var}".format(var=stat.var))
print("Std.deviation values: {stddev}".format(stddev=stat.stddev))
except Exception as e:
print(e)
Výsledky se zjištěnými statistickými informacemi by měly vypadat takto:
Pixel count: [262144, 262144, 262144] Min values: [54, 3, 8] Max values: [255, 248, 225] Average values: [180.22365951538086, 99.05121612548828, 105.41025161743164] Median values: [197, 97, 100] Variance: [2405.7822785146855, 2796.0318388835876, 1159.9422168342717] Std.deviation values: [49.04877448534964, 52.87751732904626, 34.05792443520702]
Povšimněte si, že se skutečně vrací trojice hodnot, tj. například odchylka v barvovém kanálu Red, odchylka v kanálu Green a nakonec odchylka v barvovém kanálu Blue.
Pro zajímavost si ještě v dalším demonstračním příkladu ukážeme tentýž výpočet, ovšem pro čistě černobílý obrázek, který získáme operací:
# převod na černobílý obrázek
modified_image = ImageMath.eval("convert(src, '1')", src=test_image)
Příklad vypadá následovně:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
from PIL import Image
from PIL import ImageMath
from PIL import ImageStat
filename = "Lenna.png"
try:
# načtení originálního obrázku Leny
test_image = Image.open(filename)
test_image.load()
# převod na černobílý obrázek
modified_image = ImageMath.eval("convert(src, '1')", src=test_image)
# inicializace třídy se statistikami o obrázku
stat = ImageStat.Stat(modified_image)
# zobrazení statistických informací o obrázku
print("Pixel count: {cnt}".format(cnt=stat.count))
print("Min value: {min}".format(min=[v[0] for v in stat.extrema]))
print("Max value: {max}".format(max=[v[1] for v in stat.extrema]))
print("Average value: {val}".format(val=stat.mean))
print("Median value: {med}".format(med=stat.median))
print("Variance: {var}".format(var=stat.var))
print("Std.deviation value: {stddev}".format(stddev=stat.stddev))
except Exception as e:
print(e)
Výsledky tentokrát obsahují vždy jen jednu hodnotu, i když stále uloženou do (jednoprvkového) seznamu:
Pixel count: [262144] Min value: [0] Max value: [255] Average value: [123.50492477416992] Median value: [0] Variance: [16240.289373939959] Std.deviation value: [127.43739393890617]
8. Výpočet histogramu
Velmi často se setkáme s potřebou vypočítat histogram nějakého rastrového obrázku. Ten je opět získán pro jednotlivé barvové složky, tj. ve skutečnosti se při použití barvového modelu RGB vypočítají tři histogramy – první pro červenou barvovou složku, druhý pro složku zelenou a konečně třetí pro složku modrou. Pro černobílé obrázky popř. pro obrázky ve stupních šedi se samozřejmě vypočte jen jediný histogram, který navíc pro černobílé obrázky bude obsahovat pouze dvě hodnoty. Histogram se získá velmi snadno – použitím metody Image.histogram:
# načtení originálního obrázku Leny
test_image = Image.open(filename)
test_image.load()
histogram = test_image.histogram()
Pro obrázky ve stupních šedi se vrátí seznam 256 hodnot, pro RGB obrázky se vrátí 768 hodnot, které je nutné rozdělit na třetiny:
h1 = histogram[0:255]
h2 = histogram[256:511]
h3 = histogram[512:767]
Podívejme se nyní na demonstrační příklad, který načte obrázek Leny a zobrazí pro něj histogram:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
from PIL import Image
filename = "Lenna.png"
try:
# načtení originálního obrázku Leny
test_image = Image.open(filename)
test_image.load()
histogram = test_image.histogram()
print(histogram[0:255])
print()
print(histogram[256:511])
print()
print(histogram[512:767])
print()
except Exception as e:
print(e)
Výsledek (notně zkrácený) by měl vypadat zhruba takto (obrázek je podle očekávání barevně nevyvážený):
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0... 357, 313, 302, 242, 178, 67] [0, 0, 0, 11, 65, 111, 164, 261, 308, 431, 537, 682, 846, ... 1, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, ... 0, 0, 0, 0, 0, 0, 0]
9. Operace pro manipulaci s jednotlivými pixely v rastrovém obrázku
Mezi nízkoúrovňové operace, které knihovna Pillow programátorům nabízí, patří samozřejmě operace umožňující manipulovat s jednotlivými pixely načteného rastrového obrázku – jedná se o čtení hodnoty pixelu a o zápis nové barvy do pixelu. Teoreticky by se celé vykreslování a modifikace obrázků mohla realizovat pouze dvěma operacemi typu putpixel() a getpixel(); prakticky je to však zbytečně pracné a jak uvidíme dále, i poměrně pomalé. Popišme si nejprve nízkoúrovňovou „kreslicí“ operaci putpixel(). Ta je programátorům dostupná ve formě metody pojmenované příhodně Image.putpixel(). Této metodě se typicky předává dvojice se souřadnicemi pixelu (x,y) a dále trojice celočíselných hodnot reprezentujících barvu pixelu v barvovém prostoru RGB.
Nejjednodušší příklad, který načte testovací obrázek a do jeho prostředního pixelu (resp. do pixelu nejblíže středu) vykreslí čistě červenou barvu, by vypadal následovně:
test_image_1 = Image.new("RGB", (256, 256))
color_rgb = (255, 0, 0)
test_image_1.putpixel((127, 127), color_rgb)
Povšimněte si, jakým způsobem je reprezentována souřadnice pixelu i zapisovaná barva.
10. Vykreslení RGB palety
V dalším demonstračním příkladu si ukážeme, jakým způsobem je možné vytvořit prázdné obrázky a vykreslit do nich RGB paletu, resp. přesněji řečeno barevnou škálu, v níž se postupně mění červená a modrá barvová složka. Složka zelená bude u prvního obrázku nastavena na 0, u druhého obrázku naopak na maximální hodnotu 255:
# vykreslení různobarevných pixelů
for y in range(0, height):
for x in range(0, width):
color_rgb = (x, 0, y)
test_image_1.putpixel((x, y), color_rgb)
color_rgb = (x, 255, y)
test_image_2.putpixel((x, y), color_rgb)
Výsledky by měly vypadat takto:
Obrázek 10: Část barevné škály se všemi kombinacemi červené a modré barvové složky. Zelená složka je nastavena na minimální hodnotu 0.
Obrázek 11: Část barevné škály se všemi kombinacemi červené a modré barvové složky. Zelená složka je nastavena na minimální hodnotu 255.
Následuje výpis úplného zdrojového kódu tohoto demonstračního příkladu:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
from PIL import Image
from PIL import ImageColor
try:
# vytvoření prázdných obrázků
test_image_1 = Image.new("RGB", (256, 256))
test_image_2 = Image.new("RGB", (256, 256))
# rozměry obrázku
width = test_image_1.size[0]
height = test_image_2.size[1]
# vykreslení různobarevných pixelů
for y in range(0, height):
for x in range(0, width):
color_rgb = (x, 0, y)
test_image_1.putpixel((x, y), color_rgb)
color_rgb = (x, 255, y)
test_image_2.putpixel((x, y), color_rgb)
# uložení upraveného obrázku
test_image_1.save("rgb_palette_1.png")
test_image_2.save("rgb_palette_2.png")
# zobrazení upraveného obrázku
test_image_1.show()
test_image_2.show()
except Exception as e:
print(e)
11. Vykreslení HSL palety
Programátoři používající knihovnu Pillow mohou do jisté míry využívat i barvový prostor HSL využívaný v některých typech GUI dialogů pro výběr barvy. Existuje totiž funkce nazvaná ImageColor.getrgb(), které je možné předat řetězec ve formátu (i s příslušnými procenty):
hsl(hue, saturation%, lightness%)
Tento řetězec je následně zparsován a na základě jeho obsahu jsou hodnoty hue, saturation a lightness převedeny do barvy v barvovém prostoru RGB. A takovou barvu již dokážeme snadno vykreslit (resp. přesněji řečeno zapsat do pixelu v obrázku):
# vykreslení různobarevných pixelů
for saturation in range(0, height):
for hue in range(0, width):
color_hsl = "hsl({hue}, {saturation}%, 50%)".format(hue=hue, saturation=saturation//2)
color = ImageColor.getrgb(color_hsl)
test_image.putpixel((hue, saturation), color)
Poznámka: na 50% světlosti je dostupné celé spektrum barev; naopak u 0% a 100% máme k dispozici jen čistě černou resp. čistě bílou barvu.
Jedinou nevýhodou je relativně malá rychlost převodu barvy HSL → RGB, což je ovšem pochopitelné (ovšem knihovna Pillow není určena pro tvorbu her ani pro podobné účely, v nichž je rychlost vykreslování prioritou).
Výsledek aplikace předchozího úryvku kódu by měl vypadat takto:
Obrázek 12: Vykreslení barvové škály HSL s nastavením světlosti všech pixelů přesně na 50%.
Opět následuje výpis úplného zdrojového kódu příkladu, kterým byl předchozí obrázek vykreslen:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
from PIL import Image
from PIL import ImageColor
try:
# vytvoření prázdného obrázku
test_image = Image.new("RGB", (360, 200))
# rozměry obrázku
width = test_image.size[0]
height = test_image.size[1]
# vykreslení různobarevných pixelů
for saturation in range(0, height):
for hue in range(0, width):
color_hsl = "hsl({hue}, {saturation}%, 50%)".format(hue=hue, saturation=saturation//2)
color = ImageColor.getrgb(color_hsl)
test_image.putpixel((hue, saturation), color)
# uložení upraveného obrázku
test_image.save("hsl_palette.png")
# zobrazení upraveného obrázku
test_image.show()
except Exception as e:
print(e)
12. Kreslicí operace využívající modul ImageDraw
Po popisu nízkoúrovňových kreslicích operací se podívejme na způsob vykreslování základních dvourozměrných entit, tj. především úseček, kružnic, oblouků, ale například i textu. Tyto operace jsou realizovány přes objekt získaný zavoláním konstruktoru ImageDraw.Draw(), kterému předáme referenci na již existující rastrový obrázek. Obrázek nejprve načteme tak, jak jsme již zvyklí:
test_image = Image.open(filename)
test_image.load()
Následně zavoláme konstruktor ImageDraw.Draw():
# objekt umožňující kreslení do obrázku
draw = ImageDraw.Draw(test_image)
Po tomto řádku je již možné vykreslovat všechny podporované 2D entity. A po vykreslení je vhodné (i když nikoli nutné) objekt explicitně uvolnit z paměti:
# explicitní vymazání objektu umožňujícího kreslení
del draw
Tuto poslední operaci se doporučuje používat ve chvíli, kdy celý program zpracovává desítky a stovky obrázků.
13. Nakreslení mřížky do načteného obrázku
Základní vysokoúrovňovou kreslicí operací je operace určená pro nakreslení obyčejné úsečky. Tato operace je realizována metodou line, které se typicky předává n-tice obsahující koncové prvky úsečky, tedy body [x1, y1] a [x2, y2]:
(x1, y1, x2, y2)
Dále se typicky této metodě předává barva úsečky, a to přes nepovinný (pojmenovaný) parametr fill. Hodnotou tohoto parametru je trojice představující barvu v RGB prostoru:
draw.line((x1, y1, x2, y2), fill=(255, 255, 255))
Pro nakreslení úsečky se používá klasický Bresenhamův algoritmus, který ovšem (ve své původní podobě) nedokáže využít antialiasing. To nám ovšem nebude vadit v prvním demonstračním příkladu, v němž budeme vykreslovat mřížku složenou pouze z vodorovných a svislých úseček. Nejprve vytvoříme objekt typu Draw a následně zjistíme rozměry obrázku přečtením atributu Image.size:
# objekt umožňující kreslení do obrázku
draw = ImageDraw.Draw(test_image)
# rozměry obrázku
width = test_image.size[0]
height = test_image.size[1]
Následně již můžeme ve dvou programových smyčkách vykreslit všechny svislé úsečky a poté všechny úsečky vodorovné:
# vertikální úsečky
for x in range(0, width, 30):
draw.line((x, 0, x, height-1), fill=(255, 255, 255))
# horizontální úsečky
for y in range(0, height, 30):
draw.line((0, y, width-1, y), fill=(255, 255, 255))
Výsledek by měl vypadat takto:
Obrázek 13: Obrázek Leny, do něhož jsme následně zakreslili mřížku (sekvenci vertikálních a horizontálních úseček).
Opět si ukažme, jak vypadá úplný zdrojový kód demonstračního příkladu pro vykreslení bílé mřížky popsaného v této kapitole:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
from PIL import Image
from PIL import ImageDraw
filename = "Lenna.png"
try:
# načtení originálního obrázku Leny
test_image = Image.open(filename)
test_image.load()
# objekt umožňující kreslení do obrázku
draw = ImageDraw.Draw(test_image)
# rozměry obrázku
width = test_image.size[0]
height = test_image.size[1]
# vertikální úsečky
for x in range(0, width, 30):
draw.line((x, 0, x, height-1), fill=(255, 255, 255))
# horizontální úsečky
for y in range(0, height, 30):
draw.line((0, y, width-1, y), fill=(255, 255, 255))
# uložení upraveného obrázku
test_image.save("grid.png")
# zobrazení upraveného obrázku
test_image.show()
except Exception as e:
print(e)
14. Vykreslení různobarevných čtverců
Další kreslicí funkcí, která může být v některých případech užitečná, je funkce určená pro nakreslení osově orientovaného obdélníka nebo čtverce. Velikost obdélníka/čtverce se specifikuje opět čtveřicí číselných hodnot, které určují dva krajní body [x1, y1] a [x2, y2] (ty leží na koncích úhlopříčky). Vzhledem k tomu, že se jedná o uzavřené obrazce (s plochou uvnitř), je možné specifikovat jak barvu obrysu pomocí parametru outline, tak i barvu vyplněné plochy s využitím parametru fill (pokud chcete výplň realizovat):
draw.rectangle((x1, y1, x2, y2), outline=(255, 255, 255), fill=(x, 255, y))
Následující úryvek programového kódu vykreslí do obrázku mřížku vytvořenou ze čtverců (obdélníků se stejnou délkou všech stran), přičemž každý čtverec bude mít bílý okraj a proměnnou barvu výplně:
# vykreslení různobarevných čtverců
for y in range(0, height, 32):
for x in range(0, width, 32):
draw.rectangle((x+1, y+1, x+28, y+28), outline=(255, 255, 255), fill=(x, 255, y))
Výsledkem by měl být následující obrázek:
Obrázek 14: Prázdná plocha, do které byla vykreslena mřížka čtverců s různou barvou výplně.
Zdrojový kód příkladu pro vykreslení různobarevných čtverců vypadá takto:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
from PIL import Image
from PIL import ImageDraw
try:
# vytvoření prázdného obrázku
test_image = Image.new("RGB", (512, 512))
# objekt umožňující kreslení do obrázku
draw = ImageDraw.Draw(test_image)
# rozměry obrázku
width = test_image.size[0]
height = test_image.size[1]
# vykreslení různobarevných čtverců
for y in range(0, height, 32):
for x in range(0, width, 32):
draw.rectangle((x+1, y+1, x+28, y+28), outline=(255, 255, 255), fill=(x, 255, y))
# uložení upraveného obrázku
test_image.save("color_rectangles.png")
# zobrazení upraveného obrázku
test_image.show()
except Exception as e:
print(e)
15. Použití polyčar (lomených čar) pro tvorbu složitějších obrazců
Poměrně neobvyklé, ale o to užitečnější je chování metody line ve chvíli, kdy této metodě nepředáme pouze dva koncové body, ale bodů více. V takovém případě se vykreslí lomená čára neboli polyčára (polyline). Ukažme si toto chování na příkladě, po jehož spuštění se vykreslí známý „domeček jedním tahem“ (mimochodem – je použit čistě černobílý obrázek):
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
from PIL import Image
from PIL import ImageDraw
try:
# vytvoření prázdného obrázku
test_image = Image.new("1", (512, 512))
# objekt umožňující kreslení do obrázku
draw = ImageDraw.Draw(test_image)
endpoints = [100, 500,
400, 200,
100, 200,
250, 50,
400, 200,
400, 500,
100, 200,
100, 500,
400, 500]
draw.line(endpoints, fill=255)
# uložení upraveného obrázku
test_image.save("house.png")
# zobrazení upraveného obrázku
test_image.show()
except Exception as e:
print(e)
Následuje ovšem nepatrně složitější příklad, v němž se koncové body polyčáry počítají programově, a to konkrétně následujícím způsobem:
def f(angle):
return width/2 + width * 0.4 * math.cos(angle * 3)
def g(angle):
return height/2 + height * 0.4 * math.sin(angle * 2)
endpoints = list(chain.from_iterable((f(angle), g(angle)) for angle in range(0, 360)))
draw.line(endpoints, fill=(255, 255, 256))
Výsledkem bude tento obrazec:
Obrázek 15: Složitější obrazec vykreslený jedinou polyčárou (polyline).
Opět se podívejme na úplný zdrojový kód tohoto demonstračního příkladu:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import math
from PIL import Image
from PIL import ImageDraw
from itertools import chain
try:
# vytvoření prázdného obrázku
test_image = Image.new("RGB", (512, 512))
# objekt umožňující kreslení do obrázku
draw = ImageDraw.Draw(test_image)
# rozměry obrázku
width = test_image.size[0]
height = test_image.size[1]
def f(angle):
return width/2 + width * 0.4 * math.cos(angle * 3)
def g(angle):
return height/2 + height * 0.4 * math.sin(angle * 2)
endpoints = list(chain.from_iterable((f(angle), g(angle)) for angle in range(0, 360)))
draw.line(endpoints, fill=(255, 255, 256))
# uložení upraveného obrázku
test_image.save("polyline.png")
# zobrazení upraveného obrázku
test_image.show()
except Exception as e:
print(e)
16. Vykreslení obrazce složeného z kružnic
V dnešním předposledním demonstračním příkladu si ukážeme použití metody ellipse pro vykreslení kružnic nebo elips. Tato metoda akceptuje stejné parametry jako výše zmíněná metoda pro vykreslení obdélníků, tj. lze zadat krajní body obdélníku, do kterého se elipsa vepíše. Pokud zadáme krajní body tvořící čtverec, samozřejmě se vykreslí kružnice. A opět platí – neprovádí se žádný antialiasing, takže výsledky nemusí odpovídat představám autora. Tento problém se někdy dá obejít takzvaný supersamplingem, který si popíšeme příště.
Obrázek 16: Složitější obrazec vykreslený z kružnic.
Opět následuje zdrojový kód příkladu, v němž se vykreslí obrazec složený pouze z kružnic. Jediné volání metody rectangle slouží k vykreslení bílého pozadí obrázku:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import math
from PIL import Image
from PIL import ImageDraw
try:
# vytvoření prázdného obrázku
test_image = Image.new("RGBA", (512, 512))
# objekt umožňující kreslení do obrázku
draw = ImageDraw.Draw(test_image)
# rozměry obrázku
width = test_image.size[0]
height = test_image.size[1]
draw.rectangle((0, 0, width, height), fill=(255, 255, 255))
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 = width / 2 + b * math.cos(a)
y = height / 2 + b * math.sin(a)
draw.ellipse((x - r, y - r, x + r, y + r), fill=(red, green, blue), outline="black")
# uložení upraveného obrázku
test_image.save("circle_pattern.png")
# zobrazení upraveného obrázku
test_image.show()
except Exception as e:
print(e)
17. Základy práce s textem: rasterizace a vykreslení textu do obrázku
Na závěr si – prozatím bez podrobnějšího vysvětlení – ukažme, jakým způsobem je možné do rastrového obrázku vložit nějaký text. Pro vykreslení (rasterizaci) textu se používá metoda text, ovšem ještě před jejím použitím je nutné vytvořit instanci třídy ImageFont. V současné verzi podporuje knihovna Pillow jak bitmapové fonty, tak i fonty definované obrysem vytvořeným Bézierovými křivkami (TrueType). S podrobnostmi o této relativně obsáhlé problematice se seznámíme příště, takže si dnes jen ukažme zdrojový kód příkladu pro vykreslení jednoduchého textu:
#!/usr/bin/env python3
# vim: set fileencoding=utf-8
import math
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
try:
# vytvoření prázdného obrázku
test_image = Image.new("RGBA", (512, 512))
# objekt umožňující kreslení do obrázku
draw = ImageDraw.Draw(test_image)
# rozměry obrázku
width = test_image.size[0]
height = test_image.size[1]
# načtení fontu
font = ImageFont.truetype('FreeMono.ttf', 72)
# vykreslení jednoduchého textu
draw.text((50, height/2 - 36), "Pillow", font=font, fill=(255, 128, 128))
# uložení upraveného obrázku
test_image.save("text.png")
# zobrazení upraveného obrázku
test_image.show()
except Exception as e:
print(e)
Obrázek 17: Text vykreslený do původně prázdného obrázku.
18. Obsah následující části seriálu
V následující části tohoto seriálu popis knihovny Pillow dokončíme. Zabývat se budeme především modulem nazvaným ImageMath, který uživatelům nabízí nejvíce možností (ovšem je také nejsložitější na pochopení). Ovšem nezapomeneme ani na popis některých dalších modulů, které mohou být v praxi velmi užitečné. Jedná se například o moduly se jmény ImageMorph, ImageOps (dokončení, již jsme si některé jeho možnosti popsali) či o modul ImagePath.
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes popsaných demonstračních příkladů určených pro Python 3 a knihovnu Pillow i pro Python 2 a starší knihovnu PIL byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má doslova několik kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující dvojici tabulek:
Příklady pro jazyk Python 3 a knihovnu Pillow:
Příklady pro jazyk Python 2 a knihovnu PIL:
20. Odkazy na Internetu
- PIL: The friendly PIL fork (home page)
https://python-pillow.org/ - Python Imaging Library (PIL), (home page)
http://www.pythonware.com/products/pil/ - PIL 1.1.6 na PyPi
https://pypi.org/project/PIL/ - Pillow 5.2.0 na PyPi
https://pypi.org/project/Pillow/ - Python Imaging Library na Wikipedii
https://en.wikipedia.org/wiki/Python_Imaging_Library - Pillow na GitHubu
https://github.com/python-pillow/Pillow - Pillow - dokumentace na readthedocs.io
http://pillow.readthedocs.io/en/5.2.x/ - How to use Pillow, a fork of PIL
https://www.pythonforbeginners.com/gui/how-to-use-pillow - Lenna (Wikipedia)
https://en.wikipedia.org/wiki/Lenna - Seriál Grafický formát GIF
https://www.root.cz/serialy/graficky-format-gif/ - PNG is Not GIF
https://www.root.cz/clanky/png-is-not-gif/ - JPEG - král rastrových grafických formátů?
https://www.root.cz/clanky/jpeg-kral-rastrovych-grafickych-formatu/ - Grafický formát BMP - používaný a přitom neoblíbený
https://www.root.cz/clanky/graficky-format-bmp-pouzivany-a-pritom-neoblibeny/ - Grafický formát PCX - výlet do historie PC
https://www.root.cz/clanky/graficky-format-pcx-vylet-do-historie-pc/ - Grafické formáty ve znamení Unixu
https://www.root.cz/clanky/graficke-formaty-ve-znameni-unixu/ - Grafický formát TGA - jednoduchý, oblíbený, používaný
https://www.root.cz/clanky/graficky-format-tga-jednoduchy-oblibeny-pouzivany/ - Konvoluce
https://cs.wikipedia.org/wiki/Konvoluce - Počítačová grafika
https://cs.wikipedia.org/wiki/Kategorie:Po%C4%8D%C3%ADta%C4%8Dov%C3%A1_grafika - Pixel
https://cs.wikipedia.org/wiki/Pixel - Rastrová grafika
https://cs.wikipedia.org/wiki/Rastrov%C3%A1_grafika - Pixel art aneb umění práce s body
https://www.root.cz/clanky/pixel-art-aneb-umeni-prace-s-body/ - Jak se dělá pixel art?
https://www.root.cz/clanky/jak-se-dela-pixel-art/ - Nástroje pro pixel art
https://www.root.cz/clanky/nastroje-pro-pixel-art/ - RGB color model
https://en.wikipedia.org/wiki/RGB_color_model - HSL and HSV
https://en.wikipedia.org/wiki/HSL_and_HSV - Color picker
https://en.wikipedia.org/wiki/Color_picker - HCL color space
https://en.wikipedia.org/wiki/HCL_color_space