mieki256's diary



2026/03/26(木) [n年前の日記]

#1 [gmic] G'MIC CLIについて勉強中。その3

G'MIC CLI、及び G'MIC-Qt について勉強中。

環境は、Windows11 x64 25H2 + G'MIC CLI 3.7.4 (内部では 3.7.3 という扱いになってる)。

GIMPからG'MIC CLIを呼び出して処理させる :

GIMP 2.10.38 Portable から G'MIC CLI 3.7.4 (gmic.exe) を呼び出して、画像フィルタ処理ができるかどうか実験してみた。環境は Windows11 x64 25H2。

GIMP の Python-Fuスクリプトを作って実験した。
  1. レイヤー内容をベタデータ(raw)にして、
  2. パイプで gmic.exe の標準入力に送り、
  3. gmic.exe に何らかの処理をさせたら結果を標準出力に出させて、
  4. パイプ経由で GIMP の Python-Fuスクリプトが受け取って、
  5. 新規レイヤーを作って書き込む。

念のために書いておくけれど…。G'MIC は GIMP用に G'MIC-Qt plug-in for GIMP というプラグインを用意してあって、そのプラグインを導入すればGUIでパラメータを指定して G'MIC に処理をさせることができてしまう。だから、今回作成したスクリプトに実用性は全く無い。これっぽっちもない。「GIMP で G'MIC を使いたい? G'MIC-Qt plug-in を使いましょうね」で終わる話。

じゃあどうしてこんなスクリプトをわざわざ作成したのかと言うと…。何らかの画像処理ソフトから、G'MIC CLI(gmic.exe) のような外部プログラムに、標準入力/標準出力を使って画像データを送ってフィルタ処理をさせることが本当にできるのかどうかを実証したかったから。

こういうことができるなら、標準入力/標準出力でやり取りすることが前提の、フィルタ処理しかしない実行バイナリを作るだけでも、画像処理ソフト側から利用できるフィルタの種類を増やしていくことができたりするのかなあ、と…。

ソースは以下になった。かなりの部分を AI(Google Gemini)に作ってもらった…。

_m256_gmic_cli_pipe.py
"""
GIMPからG'MIC CLI(gmic.exe)を呼び出して処理ができるか実験。
GIMPのレイヤー内容をベタデータにして、
gmic.exe の標準入力にパイプで送り、
処理結果を標準出力に出させてパイプで受け取り、
新規レイヤーに書き出すPython-fuスクリプト。
外部のexeにフィルタ処理をさせられるかどうかの実験。

by mieki256

* Menu : Filters > Misc > G'MIC CLI Pipe...
* Windows11 x64 25H2 + GIMP 2.10.38 Portable
* Ver. 0.0.1  2026.03.25 初版。
"""

from gimpfu import *
import subprocess
import os
import array


# GIMP(Interleaved) -> G'MIC(Planar)
def convert_to_planar(interleaved_raw, width, height, bpp):
    """GIMPのRGBRGB...形式をG'MICのRRR...GGG...形式に変換する"""
    interleaved_data = array.array("B", interleaved_raw)

    planar_list = []
    for c in range(bpp):
        # 各チャンネルのデータを抽出 (スライス [開始:終了:ステップ] を利用)
        planar_list.append(interleaved_data[c::bpp])

    # 全チャンネルのデータを結合してバイナリ文字列で返す
    return "".join([c_data.tostring() for c_data in planar_list])


# G'MIC(Planar) -> GIMP(Interleaved)
def convert_to_interleaved(planar_raw, width, height, bpp):
    """G'MICのRRR...GGG...形式をGIMPのRGBRGB...形式に復元する"""
    num_pixels = width * height
    received_planar = array.array("B", planar_raw)

    # 空の配列を用意して、チャンネルごとに値を埋め戻す
    output_interleaved = array.array("B", [0] * (num_pixels * bpp))
    for c in range(bpp):
        # c番目のチャンネルデータを抽出
        channel_data = received_planar[c * num_pixels: (c + 1) * num_pixels]
        # インターリーブの位置 [c, c+bpp, c+2bpp...] に代入
        output_interleaved[c::bpp] = channel_data

    return output_interleaved.tostring()


def python_fu_gmic_cli_pipe(image, drawable, gmic_dir, gmic_command):
    """メイン処理"""

    # G'MIC CLIの場所を一時的に環境変数PATHの先頭に追加する
    if gmic_dir and os.path.isdir(gmic_dir):
        current_path = os.environ.get("PATH", "")
        os.environ["PATH"] = gmic_dir + os.pathsep + current_path
    else:
        gimp.message("Invalid directory.")
        return

    # ユーザー入力をスペースで分割してリスト化
    user_cmds = gmic_command.split() if gmic_command else ["blur", "2"]

    image.undo_group_start()  # undoできるようにしておく

    # レイヤーの横幅、縦幅、チャンネル数を取得
    width = drawable.width
    height = drawable.height
    bpp = drawable.bpp  # RGBなら3, RGBAなら4

    # GIMP(Interleaved) -> Planar 変換
    gimp.progress_init("Preparing Planar data...")
    pr = drawable.get_pixel_rgn(0, 0, width, height, False, False)
    planar_data = convert_to_planar(pr[0:width, 0:height], width, height, bpp)

    # G'MICの引数構築
    # 入力: -.raw,uint8,幅,高さ,1,チャンネル数 (G'MICのraw指定は w,h,d,s)
    # 出力: -.raw,uint8
    # "-" は標準入力/標準出力を示す
    gmic_exe = "gmic.exe" if os.name == "nt" else "gmic"
    cmd = [
        gmic_exe,
        "i",
        "-.raw,uint8,{w},{h},1,{c}".format(w=width, h=height, c=bpp),
    ]
    cmd.extend(user_cmds)  # ユーザーが入力したコマンドを挿入
    cmd.extend(["cut", "0,255", "o", "-.raw,uint8"])  # 出力設定を追加

    # パイプの構築
    try:
        proc = subprocess.Popen(
            cmd,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            env=os.environ,
            shell=(os.name == "nt"),
        )

        # パイプへ送信してgmic側で受信
        pdb.gimp_progress_set_text("G'MIC processing...")
        stdout_data, stderr_data = proc.communicate(input=planar_data)

        if proc.returncode != 0:
            gimp.message("G'MIC Error: " + stderr_data)
            return

        # Planar -> GIMP(Interleaved) 復元
        pdb.gimp_progress_set_text("Restoring Interleaved data...")
        interleaved_bytes = convert_to_interleaved(stdout_data, width, height, bpp)

        # レイヤーへの書き出し
        new_layer = gimp.Layer(
            image, "G'MIC Output", width, height, drawable.type, 100, NORMAL_MODE
        )
        image.add_layer(new_layer, 0)

        pr_new = new_layer.get_pixel_rgn(0, 0, width, height, True, True)
        pr_new[0:width, 0:height] = interleaved_bytes

        new_layer.flush()
        new_layer.merge_shadow(True)
        new_layer.update(0, 0, width, height)

    except Exception as e:
        gimp.message("Error: " + str(e))

    image.undo_group_end()
    gimp.displays_flush()  # 画像の表示を更新
    return


# ----------------------------------------
# GIMPへのメニュー登録、ダイアログの指定
register(
    "python-fu-gmic-cli-pipe",  # プロシジャの名前
    "Process layer with G'MIC CLI via pipe",  # プロシジャの説明文
    "Sends raw pixel data to gmic.exe and receives processed data.",  # PDBに登録する追加情報
    "mieki256",  # 作者名
    "mieki256",  # Copyright
    "2026.03.25",  # 作成日
    "G'MIC CLI Pipe...",  # メニュー名
    "RGB*",  # 対応する画像タイプ
    # ダイアログの指定
    [
        # (型, 変数名, 説明文, デフォルト値)
        (PF_IMAGE, "image", "Input image", None),
        (PF_DRAWABLE, "drawable", "Input drawable", None),
        (PF_DIRNAME, "gmic_dir", "G'MIC CLI location", "D:\\home2\\bin\\gmic_dir\\"),
        (PF_STRING, "gmic_command", "G'MIC Command", "blur 10"),
    ],
    [],  # 戻り値の定義
    python_fu_gmic_cli_pipe,  # 処理を埋け持つ関数名
    menu="<Image>/Filters/Misc",  # メニューの登録場所
)

main()  # プラグインを駆動させるための関数

  • GIMP側では、フィルタ → Misc → G'MIC CLI Pipe... で呼び出せる。
  • ダイアログ上で、G'MIC CLI が入っているディレクトリと、任意の G'MIC用コマンドを指定できる。

実行すれば、G'MIC CLI でフィルタ処理をして結果を取得することができる。

これで一応、標準入力/標準出力を利用して、外部の実行バイナリに画像データを送ったり、受け取ったりすることができそうだと分かった。おそらくだけど、ImageMagick等もこんな感じで利用できる可能性がありそう。

ただ、一般的にはこういうのって共有メモリを使ってやり取りするものじゃないのかなという気も…。その場合OS毎に異なる処理を書かないといけないよな…。クロスプラットフォーム対応にはならないかも…。いやまあ、今回書いたスクリプトも、おそらく Windows上でしか動かないものになってそうだけど。

一番簡単なのは、一時ファイルを作成してやり取りすることかもしれない。毎回ストレージを汚す(?)のがちょっと気になるけれど…。


余談。今回、ベタデータの並びの違いでちょっとハマった。
  • GIMP側のベタデータは RGB,RGB,RGB... の順で並んでる。
  • G'MIC CLI側のベタデータは RRRRR,GGGGG,BBBBB の順で並んでる。

G'MIC側で並びを変更することができないかなと、 _permute というコマンドを試してみたけれど、その場合フィルタ処理が正しく行われない状態になってしまって…。仕方なく、Python-Fu側で並びを変換してから送り付けて、受け取ったデータもPython-Fu側で並びを変換して使うことにした。

GIMP の Python-Fu が Pillow を使えたら、並びを変換するあたりの処理をもっと簡単に書くことができたんだけど…。画像処理ライブラリを、GIMP側とPython側で2つ持ってしまうことになるだろうけど、Python側でも持っていれば楽になるところが多いのではないかと思うのだよなあ…。

endlについて :

G'MIC には、かつて watercolor というフィルタが存在していたらしいのだけど、何らかの理由で現行版のリストから削除されてしまったそうで…。

_G'MIC Watercolor Effect? - GIMP Chat
_watercolor effect? - Software - discuss.pixls.us

一説によると、RGB画像なら処理できるけどRGBA画像は処理できなくていきなりエラーが出るから削除されたとか、作者がメンテナンスしなくなったので削除されたとか、コードが多過ぎるので削除されたとか…。理由ははっきりしていないっぽい。

しかし、ソースコードは残っているので、user.gmic に自分で追加して使えた時期があるらしい。ただ、G'MIC 3.7.4 の段階ではエラーを出して動作しない。

どうにか動かせないものかなと試していたら、G'MICの仕様がちょっとだけ分かったのでメモ。

まず、中で fx_gaussian_blur を使おうとしているけれど、そういう名前のフィルタは現行版G'MICでは無くなっている。ただ、fx_blur_gaussian という名前のフィルタは存在している模様。試しに fx_blur_gaussian に置換してみたら該当箇所でエラーは出なくなった。

もう一つ、一番最後の「endl a c endl done」という行で「endl なんてコマンドは知らねえよ」とエラーが出る。何だこれは…。どういう意味の指定なんだ…。

ググっていたら、ちょっと分かってきた。

_G'MIC - GREYC's Magic for Image Computing: A Full-Featured Open-Source Framework for Image Processing
_local(l) … done

  • G'MIC は local - endlocal というブロック?を指定することができる。
  • endlocal は endl と省略形を書いて済ませることができたらしい。
  • 現行版 G'MIC では、local - done と書いてしまっていいことになってる。

AI(Google Gemini) によると、「最近の G'MIC はブロックの終了を示すコマンドについて、done と書いておけば済むことになりました」とのこと。AIがそう言っている。嘘かもしれんけど。

すると「endl a c endl done」は、以下の指定だったのだろう。
  • local - endlocal のブロックを2つ閉じて、
  • もう一つ何かのブロックを done で閉じる。

そして、現行版に合わせて書くなら done を3つ書けばいいということになりそう。

途中に入ってる「a c」は何だろう? もしかすると「append c」の略だったりしないか…? リファレンスの「Command Shortcuts:」のあたりで、a は append と書いてあるし…。c は cut の省略形とも書いてあるけれど、ソースの上のほうに変数らしき感じで何度も出現してるから、ここでは変数扱いなのではないかな…。

_gmic_reference.pdf

そんなわけで、以下のように書き換えてみた。
endl a c endl done

↓

done
append c
done
done

この状態にしたらエラーが出ずに動作するようになった。

ただ、以前行っていた処理内容と全く同じ処理をしているのか、そこは分からない…。途中で使われている各種フィルタの仕様が変わってしまっている可能性もあるので…。ただ、一応それっぽい感じのフィルタ処理になっている感じはする…。

消えると怖いので一応置いておく。

_watercolor.gmic.txt

G'MIC-Qt plug-inの中にサンプル画像があった :

GIMP + G'MIC-Qt plug-in の動作を確認していたら、サンプル画像を出すこともできると分かった。「sample」で検索すれば「Sample Image」が出てくる。lena や dog や tiger 等々選び放題。

キャンバスサイズよりサンプル画像のサイズが大きい場合は、キャンバスサイズがサンプル画像の大きさに変更される。

GIMP側で、画像 → キャンバスをレイヤーに合わせる、を選べば、キャンバスサイズをレイヤーサイズに変更することができる。

G'MICのdemoがスゴイ :

G'MIC-Qt のフィルタ種類について確認していたら。「Games & Demos」というのがあって、選んでみてちょっと驚いてしまった。パックマンが動いてる…。まあ、起動するまで時間がかかるけど…。

つまり、G'MICのスクリプトはこのぐらいの処理なら全然実現できる可能性を秘めてますよ、ということなんだろうな…。そりゃまあ、変数やIF文が使えるみたいだし、プログラムだって書けるということなんだろうけど…。

日本語名のフィルタがほとんど動かない :

Windows11 x64 25H2 + GIMP + G'MIC-Qt 3.7.4 を試用していたら、日本語名で表示されてるフィルタのほとんどが動作しないことに気づいた。何故…。全部動かないのかと思ったら、中にはちゃんと動くものもある。何故…。

ただ、同じ処理をするフィルタが英語名でも用意されてるようで、そちらを使えば実用上の問題は無いのかなと…。

以上です。

過去ログ表示

Prev - 2026/03 -
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project