GIMPのPython-Fuでピクセル単位のアクセスで高速塗りつぶし

コンピュータ
Python-Fuでピクセル単位のアクセスは遅いイメージがあって半ば使用をあきらめていたのですが、高速にアクセスする記事を見つけました。
Fast Pixel Ops in GIMP-Python (Shallow Thoughts)

速度を確認するために動作検証用のスクリプトを書いてみました。
#!/usr/bin/env python
# coding: utf8

from gimpfu import *
from array import array
from time import time

def getset_pixel_type(layer, w, h):
    # 範囲選択水平ループ
    for x in range(0, w):
        # 範囲選択垂直ループ
        for y in range(0, h):
            # ピクセルを取得
            ch, pixel = pdb.gimp_drawable_get_pixel(layer, x, y)
            pixel = (pixel[0], 0, 0, 255)
            # ピクセルをセット
            pdb.gimp_drawable_set_pixel(layer, x, y, ch, pixel)

def rgn_type(layer, w, h):
    src_rgn = layer.get_pixel_rgn(0, 0, w, h, False, False)
    dst_rgn = layer.get_pixel_rgn(0, 0, w, h, False, True)
    # 範囲選択水平ループ
    for x in range(0, w):
        # 範囲選択垂直ループ
        for y in range(0, h):
            p = map(ord, src_rgn[x, y])
            dst_rgn[x, y] = array("B", [ 0, p[1], 0, 255 ]).tostring()

def rgnbatch_type(layer, w, h):
    src_rgn = layer.get_pixel_rgn(0, 0, w, h, False, False)
    dst_rgn = layer.get_pixel_rgn(0, 0, w, h, False, True)
    src_pixels = array("B", src_rgn[0:w, 0:h])
    pixel_size = len(src_rgn[0,0])
    dest_pixels = array("B", "\x00" * (w * h * pixel_size))
    # 範囲選択水平ループ
    for x in range(0, w):
        # 範囲選択垂直ループ
        for y in range(0, h):
            pos = (x + w * y) * pixel_size
            val = src_pixels[pos: pos + pixel_size]
            val[0] = 0
            val[1] = 0
            val[2] = val[2]
            dest_pixels[pos : pos + pixel_size] = val
    dst_rgn[0:w, 0:h] = dest_pixels.tostring() 

def plugin_main(filltype):
    w = 192
    h = 192
    image = pdb.gimp_image_new(w, h, 0)
    layer = pdb.gimp_layer_new(image, w, h, 1, "Background", 100, 0)
    image.add_layer(layer)
    color = (255, 255, 255, 1.0)
    pdb.gimp_context_set_foreground(color)
    pdb.gimp_drawable_edit_fill(layer, 0)
    pdb.gimp_display_new(image)
    start1 = time()
    if filltype == "getset":
        getset_pixel_type(layer, w, h)
    elif filltype == "rgn":
        rgn_type(layer, w, h)
    elif filltype == "rgnbatch":
        rgnbatch_type(layer, w, h)
    proc_time = time() - start1
    gimp.message("実行時間:{0}".format(proc_time))
    layer.flush()
    layer.merge_shadow()
    layer.update(0, 0, w, h)

register("FillBench", "", "", "", "", "",
    "FillBench", 
    "",
    [
    (PF_RADIO, "filltype", "塗りつぶし方法", "getset", (("getset_pixel", "getset"), ("rgn", "rgn"), ("rgnbatch", "rgnbatch"))),
    ],
    [],
    plugin_main,
    menu = "<Image>/Filters")

main()

スクリプトの内容は192×192サイズの画像を作成し単色で塗りつぶしています。
ちなみに実行しているPCのCPUはRyzen5 5600Xです。

getset_pixel_type()はpdb.gimp_drawable_get_pixel()でピクセルを取得しgimp_drawable_set_pixel()でピクセルを書き換えます。
処理時間は24秒弱で一番遅い結果となりました。

rgn_type()はget_pixel_rgn()を使いピクセルをアクセスしています。
処理時間は6秒ちょっとでした。

rgnbatch_type()はget_pixel_rgn()で取得した文字配列?をフラットな数値配列に一括変換して、数値配列を書き換えを行っています。
処理時間は0.15秒とかなり短時間で処理が終了しました。

python-fuでピクセル単位でアクセスするスクリプトは実用的な速度が出ないため、半ばあきらめていましたが希望が見えてきました。

追記
個人的によく扱う画像サイズが4000×6400なのですが、試したところ処理時間は8.4秒ほどでした。

コメント