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

1. Užitečné knihovny a moduly pro Python: kreslení a pokročilé manipulace s obrázky v knihovně Pillow

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

5. Zvýšení a snížení jasu

6. Změna sytosti barev

7. Získání základních statistik o rastrovém obrázku

8. Výpočet histogramu

9. Operace pro manipulaci s jednotlivými pixely v rastrovém obrázku

10. Vykreslení RGB palety

11. Vykreslení HSL palety

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

20. Odkazy na Internetu

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:

# Demonstrační příklad Popis Cesta
1 16_image_enhance_sharpness.py zaostření a rozostření obrázku https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/16_image_enhance_sharpness.py
2 17_image_enhance_contrast.py zvýšení a snížení kontrastu obrázku https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/17_image_enhance_contrast.py
3 18_image_enhance_brightness.py změna světlosti https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/18_image_enhance_brightness.py
4 19_image_enhance_color.py změna barevného podání https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/19_image_enhance_color.py
5 20_image_statistic.py čtení základních statistických informací u barevného obrázku https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/20_image_statistic.py
6 21_bw_image_statistic.py čtení základních statistických informací u černobílého obrázku https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/21_bw_image_statistic.py
7 22_histogram.py získání histogramu obrázku https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/22_histogram.py
8 23_rgb_palette.py vykreslení RGB palety https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/23_rgb_palette.py
9 24_hsl_palette.py vykreslení HSL palety https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/24_hsl_palette.py
10 25_draw_grid.py vykreslení mřížky do načteného obrázku https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/25_draw_grid.py
11 26_colors_rgb.py použití definice RGB barev při kreslení https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/26_colors_rgb.py
12 27_polyline.py vykreslení obrazce tvořeného lomenou čarou https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/27_polyline.py
13 28_circles.py vykreslení obrazce z několika kružnic https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/28_circles.py
14 29_text.py vykreslení jednoduchého textu https://github.com/tisnik/most-popular-python-libs/blob/master/pillow/29_text.py

Příklady pro jazyk Python 2 a knihovnu PIL:

# Demonstrační příklad Popis Cesta
1 16_image_enhance_sharpness.py zaostření a rozostření obrázku https://github.com/tisnik/most-popular-python-libs/blob/master/pil/16_image_enhance_sharpness.py
2 17_image_enhance_contrast.py zvýšení a snížení kontrastu obrázku https://github.com/tisnik/most-popular-python-libs/blob/master/pil/17_image_enhance_contrast.py
3 18_image_enhance_brightness.py změna světlosti https://github.com/tisnik/most-popular-python-libs/blob/master/pil/18_image_enhance_brightness.py
4 19_image_enhance_color.py změna barevného podání https://github.com/tisnik/most-popular-python-libs/blob/master/pil/19_image_enhance_color.py
5 20_image_statistic.py čtení základních statistických informací u barevného obrázku https://github.com/tisnik/most-popular-python-libs/blob/master/pil/20_image_statistic.py
6 21_bw_image_statistic.py čtení základních statistických informací u černobílého obrázku https://github.com/tisnik/most-popular-python-libs/blob/master/pil/21_bw_image_statistic.py
7 22_histogram.py získání histogramu obrázku https://github.com/tisnik/most-popular-python-libs/blob/master/pil/22_histogram.py
8 23_rgb_palette.py vykreslení RGB palety https://github.com/tisnik/most-popular-python-libs/blob/master/pil/23_rgb_palette.py
9 24_hsl_palette.py vykreslení HSL palety https://github.com/tisnik/most-popular-python-libs/blob/master/pil/24_hsl_palette.py
10 25_draw_grid.py vykreslení mřížky do načteného obrázku https://github.com/tisnik/most-popular-python-libs/blob/master/pil/25_draw_grid.py
11 26_colors_rgb.py použití definice RGB barev při kreslení https://github.com/tisnik/most-popular-python-libs/blob/master/pil/26_colors_rgb.py
12 27_polyline.py vykreslení obrazce tvořeného lomenou čarou https://github.com/tisnik/most-popular-python-libs/blob/master/pil/27_polyline.py
13 28_circles.py vykreslení obrazce z několika kružnic https://github.com/tisnik/most-popular-python-libs/blob/master/pil/28_circles.py
14 29_text.py vykreslení jednoduchého textu https://github.com/tisnik/most-popular-python-libs/blob/master/pil/29_text.py

20. Odkazy na Internetu

  1. PIL: The friendly PIL fork (home page)
    https://python-pillow.org/
  2. Python Imaging Library (PIL), (home page)
    http://www.pythonware.com/products/pil/
  3. PIL 1.1.6 na PyPi
    https://pypi.org/project/PIL/
  4. Pillow 5.2.0 na PyPi
    https://pypi.org/project/Pillow/
  5. Python Imaging Library na Wikipedii
    https://en.wikipedia.org/wiki/Python_Imaging_Library
  6. Pillow na GitHubu
    https://github.com/python-pillow/Pillow
  7. Pillow – dokumentace na readthedocs.io
    http://pillow.readthedocs.io/en/5.2.x/
  8. How to use Pillow, a fork of PIL
    https://www.pythonforbeginners.com/gui/how-to-use-pillow
  9. Lenna (Wikipedia)
    https://en.wikipedia.org/wiki/Lenna
  10. Seriál Grafický formát GIF
    https://www.root.cz/serialy/graficky-format-gif/
  11. PNG is Not GIF
    https://www.root.cz/clanky/png-is-not-gif/
  12. JPEG – král rastrových grafických formátů?
    https://www.root.cz/clanky/jpeg-kral-rastrovych-grafickych-formatu/
  13. Grafický formát BMP – používaný a přitom neoblíbený
    https://www.root.cz/clanky/graficky-format-bmp-pouzivany-a-pritom-neoblibeny/
  14. Grafický formát PCX – výlet do historie PC
    https://www.root.cz/clanky/graficky-format-pcx-vylet-do-historie-pc/
  15. Grafické formáty ve znamení Unixu
    https://www.root.cz/clanky/graficke-formaty-ve-znameni-unixu/
  16. Grafický formát TGA – jednoduchý, oblíbený, používaný
    https://www.root.cz/clanky/graficky-format-tga-jednoduchy-oblibeny-pouzivany/
  17. Konvoluce
    https://cs.wikipedia.org/wiki/Konvoluce
  18. Počítačová grafika
    https://cs.wikipedia.org/wiki/Kategorie:Po%C4%8D%C3%ADta%C4%8Dov%C3%A1_grafika
  19. Pixel
    https://cs.wikipedia.org/wiki/Pixel
  20. Rastrová grafika
    https://cs.wikipedia.org/wiki/Rastrov%C3%A1_grafika
  21. Pixel art aneb umění práce s body
    https://www.root.cz/clanky/pixel-art-aneb-umeni-prace-s-body/
  22. Jak se dělá pixel art?
    https://www.root.cz/clanky/jak-se-dela-pixel-art/
  23. Nástroje pro pixel art
    https://www.root.cz/clanky/nastroje-pro-pixel-art/
  24. RGB color model
    https://en.wikipedia.org/wiki/RGB_color_model
  25. HSL and HSV
    https://en.wikipedia.org/wiki/HSL_and_HSV
  26. Color picker
    https://en.wikipedia.org/wiki/Color_picker
  27. HCL color space
    https://en.wikipedia.org/wiki/HCL_color_space