GIMPに組み込まれているPython-Fu(Python)を試してみたました。
Pythonのバージョン
2.7.17
現行のPythonのバージョンは3系ですので若干古いです。
デバック用にコンソールを表示させる起動オプション
PowerShellで実行(gimp-2.10.exeのパスはインストール環境に合わせて変更)
. "C:\Program Files\GIMP 2\bin\gimp-2.10.exe" --console-messages
スクリプト内で
gimp.message("文字列")
と記述するとコンソールに文字列が表示されます。スクリプト登録の確認用
フィルター用プラグイン(.pyスクリプト)の保存場所
メニュー→「編集(E)」→「設定(P)」→「フォルダー」→「プラグイン」
デフォルトでは以下のディレクトリ
%Userprofile%\AppData\Roaming\GIMP\2.10\plug-ins
こちらに保存されたフィルタスクリプトがプラグインとしてGIMPに登録され実行することが出来るようになります。
対話式スクリプト実行コンソールの起動
メニュー→「フィルター(R)」→「Python-Fu」→「コンソール(C)」
表示されるコンソールにスクリプトを直接記述(コピペ)すると最終行で「…」と表示されますので、さらにエンターキーを押すとスクリプトが実行されます。
一括処理スクリプト
PowerShellから実行する場合は以下の記事を参照

画像フォーマット変換
pngファイルをGMIPの標準ファイル形式であるxcfに一括変換します。
対話式コンソールにコピー&ペーストで実行
import os, glob
path = "H:\\python-fu\\png"
outdir = os.path.join(path, "output")
if not os.path.exists(outdir):
os.mkdir(outdir)
for src_file in glob.glob(os.path.join(path, "*.png")):
img = pdb.gimp_file_load(src_file, src_file)
disp = pdb.gimp_display_new(img)
dst_file = os.path.join(outdir, (os.path.splitext(os.path.basename(src_file))[0]+'.xcf'))
pdb.gimp_file_save(img, img.active_layer, dst_file, dst_file)
pdb.gimp_display_delete(disp)
pngファイルを読み込んで拡張子をxcfに変更して保存しているだけに見えますが、これで画像フォーマットの変換がされているようです。
layerを追加してxcf保存
import os, glob
path = "D:\\"
layer_dir = "D:\\layer\\"
outdir = os.path.join(path, "xcf")
if not os.path.exists(outdir):
os.mkdir(outdir)
for src_file in glob.glob(os.path.join(path, "*.png")):
img = pdb.gimp_file_load(src_file, src_file)
disp = pdb.gimp_display_new(img)
pdb.gimp_layer_add_alpha(img.active_layer)
layer_path = os.path.join(layer_dir, os.path.basename(src_file))
gimp.message(layer_path)
layer = pdb.gimp_file_load_layer(img,layer_path)
pdb.gimp_layer_add_alpha(layer)
gimp.message("aaa")
img.add_layer(layer, 1)
dst_file = os.path.join(outdir, (os.path.splitext(os.path.basename(src_file))[0]+'.xcf'))
pdb.gimp_file_save(img, img.active_layer, dst_file, dst_file)
pdb.gimp_display_delete(disp)
pngファイルを読み込みlayer_dirディレクトリにある同名のpngファイルをlayerとして読み込みます。各レイヤーにアルファチャンネルを追加しxcfで保存します。
選択ガウスぼかし
import os, glob
path = "D:\\python-fu\\png"
outdir = os.path.join(path, "output")
if not os.path.exists(outdir):
os.mkdir(outdir)
for src_file in glob.glob(os.path.join(path, "*.png")):
img = pdb.gimp_file_load(src_file, src_file)
disp = pdb.gimp_display_new(img)
pdb.plug_in_sel_gauss(img, img.active_drawable, 5.0, 51)
dst_file = os.path.join(outdir, (os.path.splitext(os.path.basename(src_file))[0]+'.png'))
pdb.gimp_file_save(img, img.active_layer, dst_file, dst_file)
pdb.gimp_display_delete(disp)
pdb.plug_in_sel_gaussで選択ガウスぼかしを実行しています。引数でぼかしを調整します。
拡大縮小で画像の高さを揃える
import os, glob, math
from gimpfu import *
path = "D:\\python-fu\\x2"
outdir = os.path.join(path, "output")
if not os.path.exists(outdir):
os.mkdir(outdir)
for src_file in glob.glob(os.path.join(path, "*.png")):
img = pdb.gimp_file_load(src_file, src_file)
sw = img.width
sh = img.height
dh = 1080 * 2
dw = round(float(sw) * (float(dh) / float(sh)))
disp = pdb.gimp_display_new(img)
interpolation = 3 # NoHalo
if (dh < sh):
interpolation = 4 # LoHalo
gimp.message("dw:{} dh:{} interpolation:{}".format(dw, dh, interpolation))
pdb.gimp_image_scale_full(img, dw, dh, interpolation)
dst_file = os.path.join(outdir, (os.path.splitext(os.path.basename(src_file))[0]+'.png'))
pdb.gimp_file_save(img, img.active_layer, dst_file, dst_file)
pdb.gimp_display_delete(disp)
揃えたい高さを変数dh
にセット。
補完方法は拡大の場合NoHalo縮小の場合LoHalo。
xcfファイルをpng形式一括エクスポート
import os, glob
path = "F:\\"
for s in glob.glob(os.path.join(path, "*.xcf")):
img = pdb.gimp_file_load(s, s)
disp = pdb.gimp_display_new(img)
d = os.path.join(path, (os.path.splitext(os.path.basename(s))[0]+'.png'))
pdb.gimp_file_save(img, img.active_layer, d, d)
pdb.gimp_display_delete(disp)
plug-insスクリプト
plug-insディレクトリに保存しフィルターとして実行するタイプのスクリプト。
選択範囲の隣の範囲をコピーするフィルタスクリプト
#!/usr/bin/env python
# coding: utf8
from gimpfu import *
from array import array
def get_offset(x1, y1, x2, y2, width, height):
sw = x2 - x1 + 1
sh = y2 - y1 + 1
offset_x = 0
offset_y = 0
if ((sw > sh) and (y1 - sh) > 0 and (y2 - sh) > 0):
# Up
offset_y = sh * -1
elif ((sw > sh) and (y1 + sh) <= height and (y2 + sh) <= height):
# Down
offset_y = sh
elif ((x1 - sw) > 0 and (x2 - sw) > 0):
# Left
offset_x = sw * -1
elif ((x1 + sw) <= width and (x2 + sw) <= width):
# Rihgt
offset_x = sw
return (offset_x, offset_y)
def plugin_main(image, layer):
w = layer.width
h = layer.height
# gimp.message("A")
(non_empty,x1,y1,x2,y2) = pdb.gimp_selection_bounds(image)
if (non_empty == 0):
return
pdb.gimp_layer_add_alpha(layer)
# gimp.message("B")
offset_x = 0
offset_y = 0
(offset_x, offset_y) = get_offset(x1, y1, x2, y2, w, h)
dst_rgn = layer.get_pixel_rgn(0, 0, w, h, False, True)
src_rgn = layer.get_pixel_rgn(0, 0, w, h, False, False)
# gimp.message("C")
for i in range(x1,x2):
for j in range(y1, y2):
r = pdb.gimp_selection_value(image, i, j)
if (r != 0):
# gimp.message("V")
p = map(ord, src_rgn[i+offset_x, j+offset_y])
red = p[0]
green = p[1]
blue = p[2]
alpha = p[3]
dst_rgn[i, j] = array("B", [red, green, blue, alpha]).tostring()
# gimp.message("S")
layer.flush()
layer.merge_shadow()
layer.update(0,0,w,h)
register("mycpla", "", "", "", "", "",
"mycpla",
"RGB*",
[
(PF_IMAGE, "image", "Input image", None),
(PF_DRAWABLE, "drawable", "Drawable", None)
],
[],
plugin_main,
menu = "<Image>/Filters")
main()
ファイル名をmycpla.pyでプラグインディレクトリに保存し、GMIPを再起動するとメニュー→「フィルター(R)」の下の方にmycplaが追加されます。失敗すると追加されないわけですが、エラーも表示されません。以下のデバック用コンソールを駆使して調査することになります。
フィルタの機能は範囲選択した状態で実行すると選択範囲の上部又は左側のピクセルを選択範囲にコピーするスクリプトになります。
ピクセルを一点一点読み込んでは貼り付ける作業をしていますので非常に時間がかかります。コピースタンプ機能を自動化しようと思いましたが、自分のプログラミング能力ではこの辺りが限界でした。
明るい色(白)を透明に暗い色(黒)を不透明にするスクリプト
#!/usr/bin/env python
# coding: utf8
from gimpfu import *
from array import array
def plugin_main(image, layer):
w = layer.width
h = layer.height
# gimp.message("A")
pdb.gimp_layer_add_alpha(layer)
(non_empty,x1,y1,x2,y2) = pdb.gimp_selection_bounds(image)
if (non_empty == 0):
return
# gimp.message("B")
dst_rgn = layer.get_pixel_rgn(0, 0, w, h, False, True)
src_rgn = layer.get_pixel_rgn(0, 0, w, h, False, False)
# gimp.message("C")
for i in range(x1,x2):
for j in range(y1, y2):
r = pdb.gimp_selection_value(image, i, j)
if (r != 0):
# gimp.message("V")
p = map(ord, src_rgn[i, j])
red = p[0]
green = p[1]
blue = p[2]
alpha = 255 - int((p[0] + p[1] + p[2])/3)
dst_rgn[i, j] = array("B", [red, green, blue, alpha]).tostring()
# gimp.message("S")
layer.flush()
layer.merge_shadow()
layer.update(0,0,w,h)
# gimp.message("Q")
register("sctone", "", "", "", "", "",
"sctone",
"RGB*",
[
(PF_IMAGE, "image", "Input image", None),
(PF_DRAWABLE, "drawable", "Drawable", None)
],
[],
plugin_main,
menu = "<Image>/Filters")
main()
選択範囲で切り取り新規レイヤーにコピー
#!/usr/bin/env python
# coding: utf8
from gimpfu import *
from array import array
# 選択範囲で切り取り新規レイヤーにコピー
def plugin_main(image, layer):
pdb.gimp_layer_add_alpha(layer)
w = layer.width
h = layer.height
# gimp.message("A")
(non_empty,x1,y1,x2,y2) = pdb.gimp_selection_bounds(image)
# 選択範囲がない場合終了
if (non_empty == 0):
return
# gimp.message("B")
# 新しいレイヤーの追加
name = "new"
width = image.width
height = image.height
type = RGB_IMAGE
opacity = 100
mode = NORMAL_MODE
new_layer = gimp.Layer(image, name, width, height, type, opacity, mode)
# 最前列にレイヤーを追加
position = 0
image.add_layer(new_layer, position)
# アルファチャンネルの追加
pdb.gimp_layer_add_alpha(new_layer)
pdb.gimp_edit_clear(new_layer)
# 透明色で塗りつぶす
new_layer.fill(TRANSPARENT_FILL)
# 選択範囲を切り取り
pdb.gimp_edit_cut(layer)
# 新しいレイヤーに貼り付け
floating_sel = pdb.gimp_edit_paste(new_layer, 0)
# 貼り付けを固定
pdb.gimp_floating_sel_anchor(floating_sel)
# 元レイヤーをアクティブに
pdb.gimp_image_set_active_layer(image, layer)
register("CutToNewLayer", "", "", "", "", "",
"CutToNewLayer",
"RGB*",
[
(PF_IMAGE, "image", "Input image", None),
(PF_DRAWABLE, "drawable", "Drawable", None)
],
[],
plugin_main,
menu = "<Image>/Filters")
main()
範囲選択した状態で実行すると、レイヤーが新規に追加され、選択範囲が元レイヤーから切り取られ、新しいレイヤーに貼り付けられます。
エラーコンソールの表示
メニュー「ウィンドウ(W)」→「ドッキング可能なダイアログ(D)」→「エラーコンソール(N)」
gimp.message("文字列")
で文字列がコンソールに出力されます。
現在のイメージとアクティブレイヤーの取得
# 現在のイメージを取得
image = gimp.image_list()[0]
# アクティブレイヤー
layer = image.active_layer
対話式コンソールなどでPython-Fuを試す場合、まず現在開いているイメージオブジェクトとアクティブレイヤーを取得する必要があります。gimpで用意されている関数の多くは、この2つを引数に取る場合が多いので、とりあえず抑えておきたいと思います。
ピクセル単位のアクセス
指定座標のピクセル情報を取得ch, pixel = pdb.gimp_drawable_get_pixel(layer, x, y)
指定座標のピクセル情報をセットpdb.gimp_drawable_set_pixel(layer, x, y, ch, pixel)
ch…チャンネル数
pixel…[Red(0-255),Green(0-255),Blue(0-255),Alpha(0-255)]
上記スクリプトとの方法とは異なる機能。速度差は感じられない。
(両方とも遅い。実用的な速度が出れば色々なフィルターが作れるのに…)
スクリプトのエンコーディング
# coding: utf8
指定しておかないと日本語のコメントでスクリプトが停止しました。(Python2だから?)
プログレスバー
フィルターの実行で時間がかかる場合など進捗状況を表示する。
プログレスバーの初期化
gimp.progress_init("文字列")
最初に文字列にフィルター名などをセット。
プログレスバーの更新
gimp.progress_update(0.0~1.0)
引数はfloat型で0.0(0%)~1.0(100%)をとる。フィルターの進捗割合を計算してセット。
割り算で値を計算する場合、計算する値はfloat型にキャストしてから計算すること。
プログレスバーの終了
gimp.progress_end()
フィルター終了時に実行
上手く動作しない場合確認する場所
エラーメッセージが表示されていない様でエラーが発生する直前までスクリプトが実行されてしまうので異常が気づきにくい。
・シンタックスエラーなどはGIMPのPython-Fuではなく通常のpython環境で確認する。
・インデントの誤り。空行を削除するとトラブルが少なくなる。
Undo
pdb.gimp_image_undo_group_start(image)
終了
pdb.gimp_image_undo_group_end(image)
最後に
Python-Fuは多機能なGIMPに組み込まれたマクロ的な代物なので、魅力的だと思うのですが意外と日本語の情報が少なくて残念です。
個人的にはOpenCVあたりと組み合わせると楽しいことが出来そうだと思ったのですが、私にはちょっと難しそうです。
開発環境、特にフィルタを作って試す為に何度もGIMPの起動と終了を繰り返すことになり中々大変です。まず自分の環境できちんと動作するフィルタ登録部分の雛形スクリプトを用意するところから始めると良さそうです。
コメント
[…] Windows版GIMP-2.10でPython-Fuを試す。 | 迷惑堂本舗 […]