GIMP Python-Fuでグラデーションを描画する

#1.はじめに

GIMP Python-Fuでグラデーションを描画しました。

GIMP 2.10で動作確認をしています。

#2.実行結果

円を描く正確な方法はまだ分かっていないので、曲線描画でほぼ円にしています。

追記：「鉛筆で描画」を選択していてブラシは「1. Pixel」の場合の実行結果です。「ブラシで描画」や他のブラシの場合には若干結果が変わります。

![20191207-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/534247/89a9efbe-82fc-ff10-47e9-51a896b74785.png)

#3.グラデーション関連スクリプト

背景色設定です。
グラデーションでFG_BG_RGB_MODE(前景色から背景色までRGBでグラデーション)を使用するため、追加しています。

```Python
def set_bg_color(r, g, b, a):
  color = (r, g, b, a)
  pdb.gimp_context_set_background(color)
```

以下、メイン関数の中です。
円っぽいものを描画しています。アンチエイリアスありです。

```Python
    # ほぼ円
    points = [
        (+100,    0,   0, -50,   0, +50),
        (-100, +100, +50,   0, -50,   0),
        (-100, -100,   0, +50,   0, -50),
        (+100, -100, -50,   0, +50,   0),
        ]
    make_path_relative2(image, 400, 300, points, 2, TRUE)
```

ファジー選択で円の内側を選択しています。しきい値は150にしています。

```Python
    pdb.gimp_fuzzy_select(layer, 400, 300, 150, 2, TRUE, 0, 0, 0)
```

グラデーション描画です。分かる範囲で記載します。

|引数|引数名|内容|指定した値|
|---|---|---|---|
|1|drawable_ID|drawable|レイヤーを指定しています|
|2|blend_mode||FG_BG_RGB_MODE(前景色から背景色までRGBでグラデーション)|
|3|paint_mode||NORMAL_MODE 色々選べるようですが未確認。GUIでも選べます|
|4|gradient_type|形状|GRADIENT_LINEAR(線形)|
|5|opacity|不透明度|100|
|6|offset|オフセット|0|
|7|repeat|リピート形状を選べるようです|REPEAT_NONE|
|8|reverse|反転|FALSE|
|9|supersample|Do adaptive supersampling.|FALSE
|10|max_depth|Maximum recursion levels for supersampling.|0|
|11|threshold|Supersampling threshold.|0|
|12|dither|Use dithering to reduce banding.|TRUE|
|13|x1|開始X座標|400|
|14|y1|開始Y座標|250|
|15|x2|終了X座標|400|
|16|y2|終了Y座標|350|

```Python
    set_color(255, 255, 0, 1.0)     # 黄色
    set_bg_color(0, 255, 255, 1.0)  # シアン
    pdb.gimp_edit_blend(layer, FG_BG_RGB_MODE, NORMAL_MODE, GRADIENT_LINEAR, 100, 0, REPEAT_NONE, FALSE, FALSE, 0, 0, TRUE, 400, 250, 400, 350)
```

ファジー選択していた分を解除します。

```Python
    pdb.gimp_selection_clear(image)
```

#4.スクリプト全体

長くなっていますが、曲線描画等のためで、グラデーション部分は前出の通りわずかです。

関数は[GIMP Python-Fuでパスを使用して直線・曲線を描く時に、最後を閉じないと線の欠けが発生する - Qiita](https://qiita.com/mayo00/items/2c8181b55956fdb8761a)と同じですが、set_bg_color()を追加しています。

今回も下記関数は、[5 分で始める GIMP Python-Fu - Qiita](https://qiita.com/dhomma/items/f33ecd1c4d4c6ece7685)のものを使用させて頂いています。
create_image()
add_layer()
set_color()
set_line_width()
display_image()

```Python
# Python-Fu のサンプル・スクリプト
# GIMP の Python-Fu コンソールにコピペして実行してください

# 画像データの作成
## 指定したサイズで画像データを作成する
### width : 画像データの幅 (px)
### height : 画像データの高さ (px)
def create_image(width, height):
  # 画像データを生成
  return gimp.Image(width, height, RGB)

# レイヤーの追加
## 指定した名前のレイヤーを新規に作成し、画像データに挿入する
### image : レイヤーを追加する画像データ
### name : 新規に作成するレイヤーの名前（文字列）
def add_layer(image, name):
  # レイヤーの作成に必要なパラメータ
  width   = image.width
  height  = image.height
  type    = RGB_IMAGE
  opacity = 100
  mode    = NORMAL_MODE
  #
  # パラメータをもとにレイヤーを作成
  layer = gimp.Layer(image, name, width, height, type, opacity, mode)
  #
  # レイヤーを背景色で塗りつぶす（GIMP のデフォルトの挙動に合わせています）
  layer.fill(1)
  #
  # 画像データの 0 番目の位置にレイヤーを挿入する
  position = 0
  image.add_layer(layer, position)
  #
  return layer

# 描画する色を変更する
## パレットの前景色を変更して描画色を設定する
### r : 赤要素 (0-255)
### g : 緑要素 (0-255)
### b : 青要素 (0-255)
### a : 透明度 (0-1.0)
def set_color(r, g, b, a):
  color = (r, g, b, a)
  pdb.gimp_context_set_foreground(color)

def set_bg_color(r, g, b, a):
  color = (r, g, b, a)
  pdb.gimp_context_set_background(color)

# 描画する線の太さを変える
## ブラシのサイズを変更して線の太さを設定する
### width : 線の太さ
def set_line_width(width):
  pdb.gimp_context_set_brush_size(width)

# アンチエイリアスを設定する
## アンチエイリアスを設定する
### antialias : TRUE:有効、FALSE:無効
def set_antialias(antialias):
  pdb.gimp_context_set_antialias(antialias)

# 画像の表示
## 新しいウィンドウを作成し、画像データを表示する
### image : 表示する画像データ
def display_image(image):
  gimp.Display(image)

# パス作成
## パスを作成
### image : レイヤーを追加する画像データ
### points : パス座標
### mode : 0:描画しない、1:閉じないストローク(初期値)、2:閉じるストローク、3:塗りつぶし
### delete_path : TRUE:パスを削除する(初期値)、FALSE:パスを残す
def make_path(
    image,
    points,
    mode=1,
    delete_path=TRUE,
    ):
    """ パスを作成する。 """
    #
    # フラグ設定
    #
    is_draw   = FALSE
    is_closed = FALSE
    is_fill   = FALSE
    is_stroke = FALSE
    if mode == 1:           # 閉じないストローク
        is_draw   = TRUE
        is_closed = FALSE
        is_fill   = FALSE
        is_stroke = FALSE
    elif mode == 2:         # 閉じるストローク
        is_draw   = TRUE
        is_closed = TRUE
        is_fill   = FALSE
        is_stroke = FALSE
    elif mode == 3:         # 塗りつぶし
        is_draw   = TRUE
        is_closed = TRUE        # TRUE/FALSEどちらでも変わらない
        is_fill   = TRUE
        is_stroke = FALSE
    elif mode == 4:         # 閉じないストローク(アンチエイリアスが効かない)
        is_draw   = TRUE
        is_closed = FALSE       # 効かない
        is_fill   = FALSE
        is_stroke = TRUE
    elif mode == 5:         # 閉じるストローク(アンチエイリアスが効かない)
        is_draw   = TRUE
        is_closed = TRUE
        is_fill   = FALSE
        is_stroke = TRUE
    #
    vectors = pdb.gimp_vectors_new(image, 'path')  # パス新規作成
    pdb.gimp_image_add_vectors(image, vectors, 0)  # パスを画像に追加
    #
    # パスとしてストロークを追加
    #
    stroke_id = pdb.gimp_vectors_stroke_new_from_points(vectors, 0,
            len(points), points, is_closed)
    #
    # パスを表示
    #
    pdb.gimp_vectors_set_visible(vectors, TRUE)
    #
    # 描画するよう指定されてたら、パスから選択範囲を作って描画する。
    #
    if is_draw:
        #
        # 選択範囲解除
        #
        pdb.gimp_selection_none(image)
        #
        # パスから選択範囲作成
        #
        pdb.gimp_vectors_to_selection(
            vectors,
            CHANNEL_OP_REPLACE,
            TRUE,
            FALSE,
            0,
            0,
            )
        #
        # 前景色で描画
        #
        drawable = pdb.gimp_image_active_drawable(image)
        if is_fill:
            pdb.gimp_edit_fill(drawable, FOREGROUND_FILL)
        elif is_stroke:
            pdb.gimp_edit_stroke(drawable)
        else:
            pdb.gimp_drawable_edit_stroke_item(drawable, vectors)
        #
        # 選択範囲解除
        #
        pdb.gimp_selection_none(image)
    #
    # パス削除が指定されていたらパスを削除する。
    #
    if delete_path:
        # パス削除
        #
        pdb.gimp_image_remove_vectors(image, vectors)
    #
    return

def get_absolute_point(list):
    """ """ 
    rlist = []
    for pos in list:
        if len(pos) == 6:
            (x, y, rx1, ry1, rx2, ry2) = pos
            x1 = x + rx1
            y1 = y + ry1
            x2 = x + rx2
            y2 = y + ry2
        else:
            (x, y) = pos
            x1 = x
            y1 = y
            x2 = x
            y2 = y
        rlist.extend([
            x1,
            y1,
            x,
            y,
            x2,
            y2,
            ])
    return rlist

# 絶対座標取得
def get_absolute_point2(x, y, list):
    """ """
    rlist = []
    bx = x
    by = y
    for pos in list:
        if len(pos) == 6:
            (rx0, ry0, rx1, ry1, rx2, ry2) = pos
            bx = bx + rx0
            by = by + ry0
            x1 = bx + rx1
            y1 = by + ry1
            x2 = bx + rx2
            y2 = by + ry2
        else:
            (rx0, ry0) = pos
            bx = bx + rx0
            by = by + ry0
            x1 = bx
            y1 = by
            x2 = bx
            y2 = by
        rlist.extend([
            x1,
            y1,
            bx,
            by,
            x2,
            y2,
            ])
    return rlist

# パス作成(相対座標指定)
## パスを作成
### image : レイヤーを追加する画像データ
### points : パス座標
### mode : 0:描画しない、1:最後を閉じないストローク(初期値)、2:最後を閉じるストローク、3:塗りつぶし
### delete_path : TRUE:パスを削除する(初期値)、FALSE:パスを残す
def make_path_relative(
    image,
    points,
    mode=1,
    delete_path=TRUE,
    ):
    """ パスを作成する。 """
    new_points = get_absolute_point(points)
    make_path(image, new_points, mode, delete_path)

# パス作成(座標・アンカー・制御点相対座標指定)
## パスを作成
### image : レイヤーを追加する画像データ
### x : 開始X座標
### y : 開始Y座標
### points : パス座標
### mode : 0:描画しない、1:最後を閉じないストローク(初期値)、2:最後を閉じるストローク、3:塗りつぶし
### delete_path : TRUE:パスを削除する(初期値)、FALSE:パスを残す
def make_path_relative2(
    image,
    x,
    y,
    points,
    mode=1,
    delete_path=TRUE,
    ):
    """ パスを作成する。 """
    new_points = get_absolute_point2(x, y, points)
    make_path(image, new_points, mode, delete_path)

# メイン
def main():
    set_bg_color(255, 255, 255, 1.0)
    image = create_image(800, 600)
    layer = add_layer(image, "背景")
    #layer = add_layer(image, "テスト")
    set_color(0, 0, 0, 1.0)
    set_line_width(10)
    set_antialias(TRUE)
    # 値は(アンカーX, Y[, 制御開始点増分X, Y, 制御終了点増分X, Y])
    #
    # ほぼ円
    points = [
        (+100,    0,   0, -50,   0, +50),
        (-100, +100, +50,   0, -50,   0),
        (-100, -100,   0, +50,   0, -50),
        (+100, -100, -50,   0, +50,   0),
        ]
    make_path_relative2(image, 400, 300, points, 2, TRUE)
    #
    pdb.gimp_fuzzy_select(layer, 400, 300, 150, 2, TRUE, 0, 0, 0)
    set_color(255, 255, 0, 1.0)     # 黄色
    set_bg_color(0, 255, 255, 1.0)  # シアン
    pdb.gimp_edit_blend(layer, FG_BG_RGB_MODE, NORMAL_MODE, GRADIENT_LINEAR, 100, 0, REPEAT_NONE, FALSE, FALSE, 0, 0, TRUE, 400, 250, 400, 350)
    pdb.gimp_selection_clear(image)
    #
    display_image(image)

main()
```