省型旧型国電の残影を求めて

戦前型旧型国電および鉄道と変褪色フィルム写真を中心とした写真補正編集の話題を扱います。写真補正技法への質問はコメント欄へどうぞ

GIMP / Python 自作プラグイン (ファイルをレイヤーとして読込) 解説

 GIMPにおいて Python-fu を使ったプログラミングを行う場合、特にWindows環境ではデバッグが難しく結構大変です。下手をするとどこが間違っているのかさっぱり分からない場合もしばしばです。私自身もGIMP初歩プログラマですが、それでも私自身の経験も記しておくと、何かのお役に立てるかもしれません。そこで今回私が作成したGIMPで黄変写真補正用素材画像を読み込むプラグインに関する解説をなるべく丁寧に行い、Python-fu を使ったプログラミングを試みる方への参考に資そうと思います。

 なお、GIMPにおけるPythonプログラミングの基本的な参考サイトはこちらのリンクをご覧ください。また、プラグイン配布サイトにプログラミングを行った際に参考としたサイトを載せています。

 

GIMP / Python プラグイン作成の基本

  まず基本的なプラグインのひな型について解説します。青字が注釈です。緑字は適宜変更して記述する場所です。

基本的なひな形

#!/usr/bin/python Pythonインタープリターを起動
# -*- coding: codename -*- ←必要に応じてスクリプトで使う文字コードを宣言
            必要なければこの行自体不要
            メニューに日本語を表示したい場合 codenameutf-8にする
            同時にプラグインファイル全体をuft-8で保存しておく

from gimpfu import * PythonGIMPライブラリを呼び出す                   他のライブラリも呼び出す必要があれば適宜ここで宣言
def  plugin_name( 引数 ): プラグインの本体を以下定義

 

 ここにプラグインの具体的処理内容をインデントをつけて記述


register(   →以下GIMPプラグインとして登録する際の情報を記述
     "proc_name", プラグインコマンド名
     "blurb",  →プロシジャーブラウザに表示するプラグインの説明
     "help",  プラグインのhelpで表示する説明 blurbと同一で可
     "author", プラグイン著者名
     "copyright", プラグイン著作権者名 通常はauthorと同一
     "date", →著作年等
     "label", →メニューの中で表示されるプラグインのラベル
    例えば、"Resize to max..." 等
     "imagetypes", プラグインが処理対象とする画像のタイプ
 具体的には RGB, RGB*, GRAY*, INDEXEDなど
 指定しないなら "" で良い。また2つ以上指定するならカンマで区切り
 "RGB*,GRAY*" などと記述

     [parms],  プラグインに与えるパラメーター
   もしプラグイン起動時に入力ダイアログを表示する場合はここに記述※
   入力ダイアログがなければ のまま
    
,  プラグインの結果 通常は のままでよい
     plugin_name,
        Plugin_nameは、Pythonコードで呼び出す際のメソッドの名前
           def で、定義した名前を使用。
           def で plugin_main としたら
           ここでも、それと同じく plugin_main と書く。

    menu = "menuexpresson")
        →また、menuexpression は、プラグインGIMPのメニューの
         どこに表示するかを指示する。
         
main() プラグイン本体を呼び出し

 

  プラグイン起動時にダイアログを表示する場合、上の [parms]欄に記述しますが、例えば次のように記述します。

    [
        (PF_FILE, "imgfname", "Select Original Image file", ""),
    ]

 の中に(   )で具体的なパラメータを指定しますが、その内容は、

(ダイアログタイプ, "入力した値を格納する変数名","ダイアログに表示するメッセージ","デフォルト値")

の順です。

上の例の場合 PF_FILEというのはファイル選択ダイアログを示し、次に選択したファイル名(パスを含む)をimgfnameという変数に格納し、ダイアログに Select Original Image file というメッセージを表示するとともに、デフォルト値は空白とする、という意味です。ダイアログの種類にはどんなものが使えるかは以下をご覧ください。

https://www.gimp.org/docs/python/index.html#PLUGIN-FRAMEWORK

また、Akkana Peck氏によるダイアログのサンプルがあります。

https://github.com/akkana/gimp-plugins/blob/master/pyui.py

このサンプルをGIMPプラグインフォルダにインストールして実行してみるとプラグインとコードの関係が分かります。

 ここで指定した入力ダイアログの変数名は、プラグインの def 行で引数として指定しなければなりません。

 なお、要注意点として、複数の入力ダイアログがある場合、入力ダイアログ順と同じ順番に引数も指定しなければなりません。プラグインの引数で入力ダイアログの値を引き継ぐとき、実は変数名は見ておらず、順番で入力しています。

あと、こんなページもあります。

www.gimplearn.net 

 また、作成したプラグインGIMPメニューでの表示位置は、"menuexpression" の位置に指示を書きます。例えば、

menu="<Image>/Filters/Languages/Python-Fu/"

と書くと、メニューの[フィルター]の下の、[Python-Fu]の下に表示されます。
このメニューのパラメータについては、以下の記事を参照してください。

shallowsky.com

 

■自作のGIMP用ファイル読み込みプラグイン解説

 以下、私が黄変補正用素材ファイル読込GIMPプラグインの内容について説明をしていきます。このプラグインが行っていることは複数のtifファイルをGIMP xcf形式のファイルのレイヤーとして読み込むとともに、レイヤーの一部を別のレイヤーのマスクとして貼り付ける、という作業を行っています。

#!/usr/bin/env python
# -*- coding: utf-8 -*- 文字コードUTF-8で記述するという宣言
             もし日本語でメニューやコメントを書くなら必ず宣言

from gimpfu import * Pythonのライブラリ呼び出し宣言
import os, glob  

def do_load_files(imgfname) : ←プロシジャー名をdo_load_filesとし、
                 プラグインを実行する際、引数として、
                 読み込むべきファイル名を入れた変数
                 (imgfname)
を取る。
                 このファイル名を入れる変数名は
                 下のregisterの中で定義

     img = pdb.gimp_file_load(imgfname + "_R.tif", "")
    ↑
   imgfnameの末尾に"_R.tif"を付加したファイルを背景画像としてimgという名の
   imageオブジェクトに読み込む。2番目の引数は、URL等でファイルを指定する
   場合に使うので、空白("")でよい
# load R ch. file as background
     img.filename = imgfname + "_RGB.xcf" #change image's filename
    
   imgのファイル名を imgfnameに"_RGB.xcf"を付加したファイル名に変更
   なお、この時点では名前が変更されただけでまだ保存されない。
  
     disp = pdb.gimp_display_new(img) #display image
    
  読み込んだimgをGIMP内で表示させる

    filenames = [imgfname + "_Periphery_Adjst_Mask.tif",
    imgfname + "_Binary_Mask.tif",
    imgfname + "_R+128.tif",
    imgfname + "_B.tif",
    imgfname + "_B_foreground.tif",
    imgfname + "_B_Background.tif",
    imgfname + "_G+R.tif",
    imgfname + "_Dark.tif",
    imgfname + "_G.tif", imagefile + "_R.tif"]
    
  filenamesという変数配列に、レイヤーとして読み込むべきファイル名を
  下から順番に指定し読み込む
    for filename in filenames:
    ↑
  forループを使いファイルごとに以下の処理を繰り返す
        try:
    ↑
  例外処理。以下でエラーが出たら except Exception, errorに飛ぶ
            image_id, layer_ids = pdb.gimp_file_load_layers(img, filename)

        ↑
  gimp_file_load_layersは、新規に追加するレイヤーのid番号しか返さない。従って...
            for id in layer_ids:

        ↑
  新規追加レイヤーのid番号がある限り、以下の処理を繰り返す
                new_layer = gimp.Item.from_id(id)
        ↑
  gimp.Item.from_idメソッドでid番号からレイヤーの内容をnew_layerに取得する

                pdb.gimp_image_add_layer(img, new_layer, 0)
           
         imgにnew_layerを追加する。
         3番目の引数はレイヤーを挿入する位置(何番目のレイヤーと
         して挿入するか) だが、一番上のレイヤーに追加していくので
         0でよい (番号は 0,1,2... の順になる)

                pdb.gimp_layer_add_alpha (new_layer)
           
         今追加したnew_layerにアルファチャンネルを付加

                if filename == filenames[4] or filename == filenames[5] or filename == filenames[6]:
           
         5~7番目に読み込んだレイヤーについて以下の処理を実施
                     mask = pdb.gimp_layer_create_mask(new_layer, ADD_WHITE_MASK)
           ↑
         レイヤーマスクを完全不透明で初期化してnew_layer用に作成
                     pdb.gimp_layer_add_mask(new_layer, mask)
         new_layer用に作成したマスクをレイヤーに追加


        except Exception, error:
            print error
     
 途中でエラーが発生したときの例外処理。エラーメッセージを表示し終了する。
        image = gimp.image_list()[0]
    
 レイヤー追加終了後、現在開いているファイル全体を変数 imageに取得
 gimp.image_list()は、今GIMPに読み込んでいるファイルの一覧を取得するメソッドでその一覧のうち最初(0番目)のイメージ(ファイル)をimageに取得するという意味

#
        pdb.gimp_edit_copy(image.layers[7]) # Copy Mask Image Layer
    
 上から8番目のレイヤーをクリップボードにコピー
 imageオブジェクト.layers[x] はimageオブジェクトに読み込まれているファイルのレイヤーを取得するメソッドで、上から順に 0, 1, 2という順番になる。
 image.layers[7]は上から8番目のレイヤーとなる。

        pdb.gimp_layer_set_edit_mask(image.layers[5], TRUE) # Activate Mask Edit Mode
    ↑
 上から6番目のレイヤーのレイヤー編集モードをオンにする
        floating_sel = pdb.gimp_edit_paste(image.layers[5].mask, TRUE) # Paste Mask Image as Mask
    ↑
 上から6番目のレイヤーのマスクに未固定でクリップボードのイメージを貼り付ける
 gimp_edit_pastの2番目の引数は、選択範囲をクリアして貼り付けるかどうかだがTRUEでもFALSEでもどちらでもよい。なお、いきなり固定してマスクとして貼り付けることはできない (実操作と同じ)。
        pdb.gimp_floating_sel_anchor(floating_sel) # Paste Layer Fixed
    ↑
 未固定で張り付けたイメージを6番目のレイヤーのマスクとして固定
# 以下対象となるレイヤーの順番が違うだけで同様な処理を2回繰り返す
        pdb.gimp_edit_copy(image.layers[8])
        pdb.gimp_layer_set_edit_mask(image.layers[4], TRUE)
        floating_sel = pdb.gimp_edit_paste(image.layers[4].mask, False)
        pdb.gimp_floating_sel_anchor(floating_sel)
#
        pdb.gimp_edit_copy(image.layers[9])
        pdb.gimp_layer_set_edit_mask(image.layers[3], TRUE)
        floating_sel = pdb.gimp_edit_paste(image.layers[3].mask, False)
        pdb.gimp_floating_sel_anchor(floating_sel)
#
    return

以下、プラグインの登録情報

register(
    "do_load_files_v2",
    "Load files for Photo Color adjustment",
    
 入力ダイアログ等に表示されるプラグイン
    "Load files for Photo Color adjustment",
    "Ohnishi, Yasuo",
    "Ohnishi, Yasuo",
    "2021",
    "Load Files...",
    "",  ←画像タイプは指定せず
    [
        (PF_FILE, "imgfname", "Select Original Image file", ""),
    ],
   
 プラグイン実行時にファイル選択ダイアログを表示
 PF_FILEは
ファイル選択ダイアログ表示の指定
 "imgfname"は選択したファイル名(パスを含む)を格納する変数名
 "Select Original Image file" はダイアログに表示する指示表示
    [], ←結果は指定する必要なし
    do_load_files, ←def do_load_files と定義したので、それを入力

    menu="<Image>/My Plugins/Photo Adjustment Ver2" )
    
 メニューに My Pluginsという新しい項目を作りその下に更に
 Photo Adjustment Ver2という項目を作りその下にプラグインを表示

main()

 

 で、このプラグインを最初に書いたときに分からなくて非常に往生したのが、個別のtifファイルを一つのxcfファイルの中のレイヤーとして読み込ませるコードです。つまり以下の部分ですが、

  image_id, layer_ids = pdb.gimp_file_load_layers(img, filename)
  for id in layer_ids:
    new_layer = gimp.Item.from_id(id)
    pdb.gimp_image_add_layer(img, new_layer, 0)

※imgが読込先のxcfファイル、filenameはレイヤーとして読み込む読み込み元のtifファイル名になります。


 このファイルをレイヤーとして読み込む部分がどうしても分かりませんでした。結局ネットにあるサンプルコードをパクってきたのですが、実は、いくらリファレンス資料を見ても分かる筈がないのです。

 というのは、gimp_file_load_layers というプロシジャーは Python-fu上では本来の仕様通りに動かないというのです(これは Script-fuで使われるLISPベースのSchemeと、Pythonとのデータアクセスの仕方の違いに起因するようです)。それでPython上では付加するレイヤーのidしか吐かないので、結局、これを回避するために GIMP 2.8から新たに弥縫策として付け加えられた gimp.Item.from_id というメソッドを使って、idからレイヤーの内容をnew_layerに取得し、それをgimp_image_add_layerを使って、今開いているファイルである imgにレイヤーとして追加するしかないようです。*1 従って、gimp_file_load_layers が本来通りの動きをしないので、いくらレファレンス資料を読んでみても分かるわけがないのです。因みに、Item.from_id というメソッドに関する説明は、GIMPのヘルプ→プロシージャブラウザにもありませんし、どのレファレンス資料にも出ていません。Q & Aフォーラムのようなものしか見るしかありません。本来ならば、Python用のgimpfuライブラリの gimp_file_load_layers プロシジャーがちゃんと書き換えられるべきところを、応急措置としてこのような対応がなされているため、ちゃんとしたレファレンス情報に出ていないのだと思います。このような情報は日本語では断片的な情報しか入手できず、英語で書かれた情報を読むことが必須になります。コマンドの一覧だけはコンソールから dir (gimp.Item) コマンドで確認できます。

因みに、

 image_id, layer_ids = pdb.gimp_file_load_layers(img, filename)

で、layer_idsは、新しく追加するレイヤーのid番号を入れる変数配列だとして、最初に取得する image_id変数に入るのは何の値か、ということですが、ネットに転がっているサンプルを見ると、countという名前で受けたり、num_layersという名前で受けているようですので、どうやらレイヤー数と思われます。またfilenameに入るファイルが複数のレイヤーを持っていれば、layer_idsも複数になると思いますが、このケースではレイヤーは1つしかないので、layer_idsも一つしかないはずです。従ってこの場合、pdb.gimp_file_load_layers(img, filename)[1]という形で参照することも可能だと思います (なお、添字が[0]ではなく[1]になるのは、 pdb.gimp_file_load_layers(img, filename)[0]はレイヤー数になるはずですので )。

 なお、この部分、for id in layer_ids: とforループで回していますが、これはパクリ元のコードがそうなっていたから踏襲したまでで、コピー元のファイルのレイヤーが一つしかないことが明らかなら、forループで回さず、
 new_layer = gimp.Item.from_id(layer_ids[0])
でも良いと思います。しかし、本当にそれでよいかどうかはやってみないと分かりません。

 ちなみにGIMP 2.6以前の場合は、Python-fuでは、直接ファイルをレイヤーとして読み込むことはできず、一旦GIMP内に別ファイルとして読み込んで、その画像をコピーし、それをレイヤーに貼り付けるしか手段がないそうです。もっとも8bit画像しか扱えない GIMP2.8以前をわざわざ使う意味はないと思いますので...

 いずれにせよ、Python-fu でレイヤー周りをいじるときは要注意で、レファレンスマニュアル類だけ見ていてもプログラミングできない、ということです。

 ちなみにPython-fuコンソールからコマンド一覧を見ると gimp.Item に他にもメソッドがあるようなので、Python-fu上で仕様通りに動かないプロシジャーは他にもありそうです。

 

また、レイヤーに画像を貼り付けるメソッドですが、

 floating_sel = pdb.gimp_edit_paste(layer, True or False)

とやれば、レイヤー画像として貼り付けることになり、

 floating_sel = pdb.gimp_edit_paste(layer.mask, True or False)

とやれば、レイヤーマスクとして貼り付けることになります。
※緑色の部分は、実際の名前や、値に応じて変える部分です
例えば、仮に今開いているファイルが、imgという変数に入っているとすると、具体的なlayerは、例えば上から2段目のレイヤーだったら img.layers[1] と指定することになります。番号は一番上から順に、0,1,2,3... という番号になります。True or False というところはTrueかFalseのどちらかを入れてください。

 

レイヤーにマスクをつける際には

一旦、

 mask = pdb.gimp_layer_create_mask(new_layer, ADD_WHITE_MASK)

でマスクを作成し、そのあと

  pdb.gimp_layer_add_mask(new_layer, mask)

で、マスクを貼り付けていますが、gimp_layer_create_mask だけではマスクが付加されないので要注意です。createで付加先のレイヤーまで指定しているのですから、それだけでマスクまで付加してくれよ... と思うところですが、そういう仕様になっていません。これはレイヤーを読み込む時に load だけではレイヤーを追加してくれず、addしないとレイヤーが追加されないのと同じですね。
また作成できるマスクの種類ですが、

 

f:id:yasuo_ssi:20210117184601j:plain

レイヤーマスクの追加ダイアログ

 上の、それぞれに対応する定数パラメータとして、

ADD_MASK_WHITE
ADD_MASK_BLACK
ADD_MASK_ALPHA
ADD_MASK_ALPHA_TRANSFER
ADD_MASK_SELECTION
ADD_MASK_COPY
ADD_MASK_CHANNEL

の6つの選択肢があります。

 

因みに、このようなGIMPで使える定数パラメータは、Python-fuコンソールから、

import gimpenums

と打ってからさらに

help (gimpenums)

と打つと、パラメータの一覧が出ます。ただしパラメータの説明はありません。

*1:この記述の出典は以下のサイトです。

stackoverflow.com

また、こちらのサイトもご覧ください。

stackoverflow.com