BI-PGA Programování grafických aplikací
Jdi na navigaci předmětu

4. Zásuvné moduly pro GIMP (Python) II

Cíl cvičení

Cílem cvičení je demonstrovat tvorbu zásuvného modulu do aplikace GIMP s využitím jazyka Python

Osnova

  • Ukázkové příklad (objektové řešení)
  • Demonstrace zásuvného modulu Hue Map (Jonathan Matějka)

Samostatná práce

Zadání

Vytvořte modul pixelizace, který z definovaného okolí (UI) pixelu vypočítá novou hodnotu a tu mu nastaví. Modul bude fungovat pro režimy RGB* a GRAY*

zdroj

Řešení

Demonstrace zásuvného modulu Hue Map (Jonathan Matějka)

Hue map je plugin do GIMP2, který umožňuje přemapovat odstín podle přechodu.

hue

gimpplugin.plugin

Python pluginy mohou být dvojího druhu. Jednodušší, využívající funkci register, a pokročilé, využívající třídy gimpplugin.plugin. Ta umožňuje vyšší kontrolu nad během pluginu, díky které můžeme vytvářet vlastní GUI, něco si připravit před prvním během, nebo uklidit při odpojení pluginu.

class muj_plugin(gimpplugin.plugin):
    def start(self):
        gimp.main(self.init, self.quit, self.query, self._run)

    def init(self):
        pass

    def quit(self):
        pass

    def query(self):
        gimp.install_procedure(
            "vstupni_bod",
            #...zbytek registrační struktury...
        )

    def vstupni_bod(self, run_mode, image, drawable):
        #kod programu

if __name__ == '__main__':
    muj_plugin().start()

Ve vstupním bodu pak vytváříme a obsluhujeme GUI.

GUI

Vytvoření základního okénka
self.dialog = gimpui.Dialog("Hue map", "hue_map_dialog")
#zde vytvoříme prvky a napojíme na dialog
#metoda run zablokuje běh programu do zavření okna
self.dialog.run()
Vytvoření widgetu

Například checkbox, analogicky vytvoříme i ostatní widgety.

self.checkbox = gtk.CheckButton("Popisek checkboxu")
#můžeme nastavit zaškrtnutí:
self.checkbox.set_active(True)
self.checkbox.show()
Vytvoření rozvržení

V pluginu používám tabulku, ta je vnořená do horizontální a následně vertikálního boxu.

#3x2 non-homogenous table
self.table = gtk.Table(3, 2, False)
self.table.set_row_spacings(8)
self.table.set_col_spacings(8)
self.table.show()

#dialog inner frames
#there is a table inside a hbox inside a vbox
self.dialog.vbox.hbox1 = gtk.HBox(False, 7)
self.dialog.vbox.hbox1.show()
self.dialog.vbox.pack_start(self.dialog.vbox.hbox1, True, True, 7)
self.dialog.vbox.hbox1.pack_start(self.table, True, True, 7)
Napojení widetu
#napoj checkbox do tabulky, do 1. a 2. sloupce a do druhého řádku
#do parametrů funkce se zapisuje, k jakým svislým a vodorovným vodítkům se má prvek přichytit
#tzn. přichytíme zleva k nultému, zprava k druhému, shora k prvnímu a zdola k druhému
self.table.attach(self.checkbox, 0, 2, 1, 2)
Tlačítka a události

Tlačítka v patičce dialogu, registrace eventů (onclick).

    #buttons at the bottom
    #Preview, Ok and Cancel
    self.preview_button = gtk.Button("_Preview")
    self.preview_button.show()
    self.preview_button.connect("clicked", self.preview_clicked)
    if gtk.alternative_dialog_button_order():
        self.dialog.action_area.add(self.preview_button)
        self.ok_button = self.dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
        self.cancel_button = self.dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
    else:
        self.cancel_button = self.dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        self.ok_button = self.dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
        self.dialog.action_area.add(self.preview_button)
    self.ok_button.connect("clicked", self.ok_clicked)
    self.cancel_button.connect("clicked", self.cancel_clicked)

def preview_clicked(self, widget):
    #kliklo se na Preview

def ok_clicked(self, widget):
    #kliklo se na Ok

def cancel_clicked(self, widget):
    #kliklo se na Cancel
Důležité:

Pozor! Při zavření dialogu křížkem nebo stisknutím klávesy ESC se nezavolá metoda cancel_clicked! Pokud potřebujeme něco uklidit, musíme to udělat za voláním metody self.dialog.run()!

Shelf

Pro ukládání uživatelského nastavení používám knihovnu gimpshelf. Ta zpřístupňuje globální asociativní pole.

Vytvoření a zápis do úložiště
self.shelfkey = "klic_meho_pluginu"

#create settings storage
if not shelf.has_key(self.shelfkey):
    #pokud úložiště nezná náš klíč, můžeme napříkad zapsat nějaké výchozí hodnoty
    shelf[self.shelfkey] = {
        "klic1":    hodnota1,
        "klic2":    hodnota2
    }
Čtení z úložiště
lokalni_promenna = shelf[self.shelfkey]["klic1"]
Vyčtení stavu GUI widgetů
zaskrtnuto = self.checkbox.get_active()
vybrany_prechod = self.gradient_button.get_gradient()

Módy běhu

Mód běhu dostaneme jako vstupní parametr.

def vstupni_bod(self, run_mode, ...
    if run_mode == RUN_INTERACTIVE:
        ...

Náhled

Pro aktualizování náhledu vytvoříme v GUI tlačítko, které po kliknutí spustí metody pro vytvoření náhledové vrstvy.

Vytvoření vrstvy

Vytvoření vrstvy podle daného výběru.

(bx1, by1, bx2, by2) = self.drawable.mask_bounds
bw = bx2 - bx1
bh = by2 - by1
#input layer offset
(ox, oy) = self.drawable.offsets

self.layer = gimp.Layer(self.image, "Název vrstvy", bw, bh, RGBA_IMAGE, 100, NORMAL_MODE)
#set correct position
self.layer.set_offsets(bx1 + ox, by1 + oy)
#add layer into image
self.image.add_layer(self.layer, 0)

#...provedeme změny na vrstvě...

#refresh
self.layer.update(0, 0, bw, bh)
#refresh
gimp.displays_flush()
Odstranění vrstvy
self.image.remove_layer(self.layer)
gimp.delete(self.layer)

Blokové operace

Získání obrazových dat

Získáme řetězec pixelů, co znak to složka - „RGBARGBARGBA…​“ nebo „RGBRGBRGBRGB…​“ v závislosti na přítomnosti alfa kanálu. Můžeme přistupovat na jednotlivé znaky, nebo si je můžeme pro pohodlnost převést do jiného formátu.

src_rgn = self.drawable.get_pixel_rgn(bx1, by1, bw, bh, False, False)
#all the input pixels in one huge array
#src_rgn[...] returns a packed byte array as a string
retezec_pixelu = src_rgn[bx1:bx2, by1:by2]

Pixely procházíme klasicky dvojitým forem.

Zápis obrazových dat
dest_rgn = self.layer.get_pixel_rgn(0, 0, bw, bh, True, True)
dest_rgn[0:bw, 0:bh] = retezec_pixelu
#apply changes
self.layer.flush()
#apply selection mask
self.layer.merge_shadow(True)

Indikátor průběhu

#start a progress bar
gimp.progress_init("Můj dlouho trvající proces")
#update the progress bar
gimp.progress_update(0)
...
gimp.progress_update(0.2)
...
gimp.progress_update(0.9)
...
gimp.progress_update(1)

Převod formátu pixelů

Metoda drawable.get_pixel_rgn() vrací řetězec znaků, což je značně nepraktická struktura.

Řetězec na pole bajtů
#we unpack this string using python array
src_pixels = array.array("B", src_rgn[bx1:bx2, by1:by2])
Pole bajtů na řetězec
dest_rgn[0:bw, 0:bh] = dest_pixels.tostring()
Čtení jednotlivých pixelů
#input can be RGB or RGBA
bpp = self.drawable.bpp
for y in range(0, bh):
    for x in range(0, bw):
        pos = (x + bw*y)*bpp
        #read a pixel as a 3 or 4 byte array
        c_array = src_pixels[pos:(pos+bpp)]
Zápis jednotlivých pixelů
#write a pixel into the output array
dest_pixels[dest_pos:(dest_pos+dest_bpp)] = c_array
Pole bajtů na RGB objekt
#create a RGB struct, if there is no alpha, set it to 100%
c_rgb = gimpcolor.RGB(c_array[0], c_array[1], c_array[2], c_array[3] if bpp == 4 else 255)
RGB objekt na pole bajtů
#RGB -> byte array
c_array[0:dest_bpp] = array.array("B", c_rgb[0:dest_bpp])
Řešení

hue-map-plugin.zip