Python skriptování GIMPu

... Petr Blahoš, 24. 2. 2017 Python

Navazuji na tutorial Pavla Tišnovského o skriptování v GIMPu (1.část, 2.část). Přišel zrovna v době, kdy jsem potřeboval něco malého naskriptovat. V Pavlově textech pluginy v pythonu vždy vytvoří nový obrázek, a něco s ním dělají. Já jsem potřeboval pracovat s obrázkem existujícím, a protože mi chvíli trvalo, než jsem zjistil jak na to, tak se zde podělím o své postřehy.

Aktivní obrázek

Když píšete plugin, Vaše funkce dostane parametry, o které si řeknete, např. image a drawable. Jenže dokud si hrajete a API, potřebujete je ten obrázek v interaktivní konzoli. Nic jako právě aktivní obrázek v API není, což se dá celkem pochopit, protože v GIMPu můžete mít desítky otevřených obrázků, a konzole je jen jedna. Takže si musíme pomoci takto:

>>> gimp.image_list()
[<gimp.Image 'thumb_up.xcf'>]
>>> img = gimp.image_list()[0]
>>> type(img)
<type 'gimp.Image'>

A z obrázku můžeme získat gimp.Image.active_drawable. Existuje taky gimp.Image.active_layer.

>>> img.active_drawable
<gimp.Layer 'Layer'>
>>> img.active_layer
<gimp.Layer 'Layer'>
>>> img.active_layer == img.active_drawable
True

Obrázek je typu gimp.image, takže Procedure Browseru zkusíme napsat gimp.image. Nabídne se nám řada funkcí. Pokračujeme hledáním gimp.image.active_drawable, a zjistíme, že je deprecated. Přitom jsme zjistili, že active_layer tam ani není, ale budeme teď používat funkce get_active_drawable a get_active_layer.

Do akce

Co vlastně budeme dělat? Mám několik obrázků, každý má 3 vrstvy. Potřebuju je vyrenderovat do png, v šířce 300px, jednu verzi s viditelnou pouze jednou konkrétní vrstvou a druhou verzi s ostatníma vrstvama. Jak na to?

Budu pracovat na kopii, to znamená ai = img.duplicate(). Co se stane, když mu změníme velikost? ai.gimp_image_scale(300, 300*ai.width/ai.height)

>>> ai.scale(300, 300*ai.height/ai.width)
>>> ai.width, ai.height
(300, 318)
>>> [(i.width, i.height) for i in ai.layers]
[(300, 318), (300, 318), (300, 318)]

Jednotlivé vrstvy taky vypadají vpořádku. Teď ta viditelnost. Moje vrstvy mají jména. Nejprve chci vidět všechny, kromě belt.

>>> for i in ai.layers:
...     i.visible = i.name != "belt"

Vypadá to, že správná cesta je spojit vrstvy do jedné, a potom obrázek uložit. A protože budu s obrázkem ještě pracovat, znamená to další duplikát.

>>> dup = ai.duplicate()
>>> merged = dup.merge_visible_layers(0)
>>> pdb.file_png_save(dup, merged,
                      "/home/petr/temp_img_0.png", "temp_img_0.png",
                      0, 9, 1, 1, 1, 1, 1)

Celý plugin

Funkce register je trošku problém. Nejlepší dokumentace, kterou jsem našel je zde: https://www.gimp.org/docs/python/. Jenže popis parametrů tam úplně neodpovídá tomu, co použil Pavel Tišnovský ve druhé části svého článku. Přiznám se, že jsem nehledal úplně zevrubně, a spokojil jsem se s verzí, která mi fungovala. Skript patří, jak víte, do ~/.gimp-2.8/plug-ins/ nebo tak nějak, a musí být spustitelný.

#!/usr/bin/env python

import os.path

from gimpfu import (
    register, pdb, main,
    PF_IMAGE,
)


BACKGROUND_LAYER_NAME = "belt"


def save_300xYYY_l(img):
    """
    Scales the current image to 300 x YYY (proportionaly). Then exports 2 png
    files. The first one with all layers but the one named ``belt`` visible.
    The second one just with the layer named ``belt`` visible.

    The files are saved into the original's location with the same name with
    the _fg.png and _bg.png suffix.

    All operations are performed on the copy, the original image is not touched.
    """
    ai = img.duplicate()
    ai.scale(300, 300*ai.height/ai.width)

    path = os.path.dirname(img.filename)
    base_fn = os.path.basename(img.filename)
    if not base_fn.endswith(".xcf"):
        raise Exception("The base filename does not end with .xcf!")

    base_fn = base_fn[:-4]

    dup = ai.duplicate()
    for i in dup.layers:
        i.visible = BACKGROUND_LAYER_NAME != i.name
    merged = dup.merge_visible_layers(0)
    fn = base_fn + "_fg.png"
    pdb.file_png_save(dup, merged,
                    os.path.join(path, fn), fn,
                    0, 9, 1, 1, 1, 1, 1)

    dup = ai.duplicate()
    for i in dup.layers:
        i.visible = BACKGROUND_LAYER_NAME == i.name
    merged = dup.merge_visible_layers(0)
    fn = base_fn + "_bg.png"
    pdb.file_png_save(dup, merged,
                    os.path.join(path, fn), fn,
                    0, 9, 1, 1, 1, 1, 1)


register(
    "save_300xYYY_l",  # name
    "Save 300x??? fg and bg image",  # short description
    save_300xYYY_l.__doc__,  # help
    "Petr Blahos",  # author
    "Public Domain",  # copyright
    "2017-02-22",  # date
    "Save 300x??? pngs",  # menu entry name
    "*",  # image types
    [
        (PF_IMAGE, "img", "Image", None),
    ],  # parameters
    [],  # result
    save_300xYYY_l,  # function
    menu="<Image>/Filters/Petr/",  # menu path
)

main()

Závěr

Výhoda pythonu je, že si v něm poměrně snadno poradíte. API nepracuje s integery (jak by naznačoval Procedure Browser), ale přímo s objetky, takže třeba gimp.image_list Vám nevrátí list integerů, ale přímo list obrázků. Nevoláte pak gimp-image-resize, ale image.resize, tak, jak byste v pythonu čekali. Pamatujte na svého dobrého kamaráda dir, např. [i for i in dir(gimp.Image) if "layer" in i].

Celkem by se mi líbilo, kdyby to šlo celé vzít obráceně. Tedy, nepsat plugin do GIMPu, ale napsat skript v pythonu, který bude volat GIMP přes API.