トップ > Script-Fuスクリプトの制作 >
Script-Fuプログラミングの基礎を学ぶ(バージョン 3)(その2)

  
  
この記事はGIMP 3.0系のScript-Fu バージョン 3向けの記事です。 GIMP 2.10系を利用している方は、Script-Fu バージョン 2 / 3 共通の記事まで読み飛ばしてください。 または、SCript-Fu バージョン 2向けの記事まで戻ってください。

Script-Fuプログラミングの基礎(続き)

前の記事の『Script-Fuプログラミングの基礎を学ぶ』からの続きです。 引き続き、Script-Fuスクリプトを制作する上で知っておくべき事柄について解説していきます。

この記事では、利用者に各種情報を入力させる方法について紹介します。 利用者にファイル名を入力させる・線を引く太さを決定させる、のような場面で必要になります。

利用者に2つの数値を入力させて合計する

では、利用者に2つの数値を入力させ、それを合計して表示するスクリプトを作成してみましょう。 スクリプトファイル myscript.scm を以下のように書き換えてください。

  1. (define (myscript innumber1 innumber2)
  2. (let
  3. (
  4. (outnumber 9)
  5. )

  6. (set! outnumber (myfunc-addnumber innumber1 innumber2))

  7. (gimp-message (string-append "Number is " (number->string outnumber)))
  8. )
  9. )

  10. (define (myfunc-addnumber x y)
  11. (+ x y)
  12. )

  13. (script-fu-register-procedure
  14. "myscript" ; 登録する関数の名前
  15. "My Script" ; メニュー項目のラベル
  16. "自作の練習用スクリプトです" ; メニュー項目の説明
  17. "My Name" ; 作成者の名前
  18. "January 1, 2023" ; 作成日(改訂日)
  19. SF-ADJUSTMENT "Enter Number 1" '(2 0 9999 1 10 0 0) ; 入力ダイアログからの入力値その1
  20. SF-ADJUSTMENT "Enter Number 2" '(7 0 9999 1 10 0 0) ; 入力ダイアログからの入力値その2
  21. )

  22. (script-fu-menu-register
  23. "myscript" ; 対象の関数の名前
  24. "<Image>/Filters" ; メニュー項目の位置
  25. )
 

では、スクリプトを実行しましょう。 プルダウンメニューの"フィルター(R) -> My Script"を実行します。

1. 入力ダイアログが表示される
1. 入力ダイアログが表示される

上図のように入力ダイアログが表示されます。 "Enter Number 1" と "Enter Number 2" という見出しの項目があり、それぞれ 2 と 7 が初期表示されています。

2. 数値を変更する
2. 数値を変更する

上図のように "Enter Number 1" と "Enter Number 2" の数値を変更します。 上の例では、11 と 47 に変更しています。 変更したら[OK(O)]ボタンを押します。

  
入力欄で直接入力してもいですし、スライダや[-][+]ボタンで増減させてもいいです。
3. ウィンドウ下部に "Number is 58" と表示される
3. ウィンドウ下部に "Number is 58" と表示される

上図のようにウィンドウ下部に "Number is 58" と表示されます。 入力した2つの数値が加算されています。


それでは、今回のスクリプトを詳しく見ていきましょう。 最初に注目すべき点は、1行目の、

(define (myscript innumber1 innumber2)

という部分です。 利用者が入力した2つの数値を受け取るための引数 innumber1 と innumber2 が定義されています。

なお、引数に画像番号やレイヤ番号(ベクタ)は受け取っていません。 このスクリプトは開いている画像にはアクセスしないからです(手続き script-fu-register-procedureを利用しています)。

次の注目点は、25・26行目の、

SF-ADJUSTMENT   "Enter Number 1"    '(2 0 9999 1 10 0 0)         ; 入力ダイアログからの入力値その1
SF-ADJUSTMENT   "Enter Number 2"    '(7 0 9999 1 10 0 0)         ; 入力ダイアログからの入力値その2

という2行です。 手続き script-fu-register-procedureに渡している引数の一部になります。

今までは手続き script-fu-register-procedureには5つの引数を渡していました。 しかし、今回のスクリプトでは引数が2行ほど多いです。 引数の数でいえば、

  1. SF-ADJUSTMENT
  2. "Enter Number 1"
  3. '(2 0 9999 1 10 0 0)
  4. SF-ADJUSTMENT
  5. "Enter Number 2"
  6. '(7 0 9999 1 10 0 0)

のように6つ増えています

手続き script-fu-register-procedureの引数は3ずつ増える

手続き script-fu-register-procedureに渡す引数の数は、最低で5つです。 今回の例では6つ目以降の引数を渡していますが、これは、

  1. 独自の関数myscriptが利用者の入力を受け取れるようにするため

です。 利用者の入力を受け取れるようにするなら、手続き script-fu-register-procedureへ渡す引数を増やす必要があるのです。

そしてここが重要なのですが、script-fu-register-procedureの引数を増やす場合は必ず3ずつ増やして記述します。

なお、なぜ3ずつ増やすのかは、後ほど説明します

作成するスクリプトが
受け取る引数の数
script-fu-register-procedureへ
渡す引数の数
無し 5
1 8
2 11
3 14

同様に、手続き script-fu-register-filterに渡す引数の数も3ずつ増えます。 こちらは最低で8つの引数が必要です。

作成するスクリプトが
受け取る引数の数
script-fu-register-filterへ
渡す引数の数
無し 8
1 11
2 14
3 17

今回のスクリプトは利用者が入力した2つの数値を受け取りますから、2 x 3で6個の引数が増えたというわけです。 増えた6つの引数の意味は以下の通りです。

引数 意味
SF-ADJUSTMENT スクリプトが受け取る1つ目の引数の型
"Enter Number 1" 入力欄1の見出し
'(2 0 9999 1 10 0 0) 入力欄1の初期値と振る舞い
※リスト
----
※初期値が 2
※最小値が 0
※最大値が 9999
※最小増減値が 1
※PgUp/PgDnによる増減値が 10
※小数点以下の桁数が 0
※外観はスライダ(0) or スピンボックス(1)
SF-ADJUSTMENT スクリプトが受け取る2つ目の引数の型
"Enter Number 2" 入力欄2の見出し
'(7 0 9999 1 10 0 0) 入力欄2の初期値と振る舞い

なお、スクリプトが受け取る引数の型 SF-ADJUSTMENT の意味は以下の通りです。

スクリプトへの引数の型 意味
SF-ADJUSTMENT 数値を受け渡す
※入力ダイアログが開く

スクリプトの記述で注目すべきは以上です。 それ以外で目新しいものはありません。

入力ダイアログは自動的に表示される

今回のスクリプトには、入力ダイアログを表示させる命令らしき部分は見当たりませんでした。 それにも関わらず、実行すると入力ダイアログが表示され2つの数値を入力することができました。 なぜでしょうか。

実は、入力ダイアログは自動的に表示されます。 手続き script-fu-register-procedureに渡している引数に、

SF-ADJUSTMENT   "Enter Number 1"    '(2 0 9999 1 10 0 0)         ; 入力ダイアログからの入力値その1
SF-ADJUSTMENT   "Enter Number 2"    '(7 0 9999 1 10 0 0)         ; 入力ダイアログからの入力値その2

という2行が含まれていたからです。

手続き script-fu-register-procedureへの引数を元に、入力ダイアログが必要かどうかが判断されます。 必要と判断されれば指定した入力欄を持つダイアログが自動で表示されます

手続き script-fu-register-filterについても同様です。 入力ダイアログが必要かどうかは自動的に判断されます。

利用者にファイルを選択させてファイル名を表示する

続いては、利用者にファイルを選択させてみましょう。 ファイルが選択されればファイル名を表示し、選択されなければ選択を促す警告を表示します。

では、スクリプトファイル myscript.scm を以下のように書き換えてください。

  1. (define (myscript openfile)
  2. (if (or (string=? openfile "") (string=? (substring openfile 0 5) "file:"))
  3. (begin
  4. (gimp-message "読み込むファイルを指定してください。")
  5. )
  6. (begin
  7. (gimp-message (string-append "読み込むファイルは" openfile "です。"))
  8. )
  9. )
  10. )

  11. (script-fu-register-procedure
  12. "myscript" ; 登録する関数の名前
  13. "My Script" ; メニュー項目のラベル
  14. "自作の練習用スクリプトです" ; メニュー項目の説明
  15. "My Name" ; 作成者の名前
  16. "January 1, 2023" ; 作成日(改訂日)
  17. SF-FILENAME "Open File" "" ; 入力ダイアログで選択されたファイル名
  18. )

  19. (script-fu-menu-register
  20. "myscript" ; 対象の関数の名前
  21. "<Image>/Filters" ; メニュー項目の位置
  22. )
 

では、スクリプトを実行しましょう。 プルダウンメニューの"フィルター(R) -> My Script"を実行します。

1. 入力ダイアログが表示される
1. 入力ダイアログが表示される

上図のように入力ダイアログが表示されます。 ファイル名を選択・確認するための入力欄 "Open File" が表示されています。

現状、"(なし)" となっており、まだ何のファイルも選択されていないことがわかります。 では、何もファイルを選択せずに[OK(O)]ボタンを押してください。

2. ウィンドウ下部に "読み込むファイルを指定してください。" と表示される
2. ウィンドウ下部に "読み込むファイルを指定してください。" と表示される

上図のようにウィンドウ下部に "読み込むファイルを指定してください。" と表示されます。 ファイルを選択しなかったため、警告が表示されました。

では、もう一度スクリプトを実行してみましょう。 プルダウンメニューの"フィルター(R) -> My Script"を実行します。

3. 任意のファイルを選択する
3. 任意のファイルを選択する

上図のように何でもいいので任意のファイルを選択します。 選択したら[OK(O)]ボタンを押してください。

4. ウィンドウ下部に選択したファイルが表示される
4. ウィンドウ下部に選択したファイルが表示される

上図のようにウィンドウ下部に選択したファイルが表示されます。


それでは、今回のスクリプトを詳しく見ていきましょう。 まず目を引くのが20行目の、

SF-FILENAME     "Open File"         ""                           ; 入力ダイアログで選択されたファイル名

の部分です。 新たな引数の型である SF-FILENAME が登場しています。 名前からもわかるように、これはファイル名を受け渡すための型です。

スクリプトへの引数の型 意味
SF-FILENAME ファイル名を受け渡す
※入力ダイアログが開く

なお、SF-FILENAME に続く引数の "Open File" は、入力ダイアログでのラベルです。 さらに続く "" はファイル名の初期値です。

続いて注目すべきは2行目の、

(if (or (string=? openfile "") (string=? (substring openfile 0 5) "file:"))

という制御文です。 引数 openfile(つまり利用者が選択したファイル名)が "" または先頭の5文字が "file:" かどうかを判断しています。

ファイル名の初期値は "" ですので、渡された引数が "" であれば利用者はファイルを選択していないことになります。

GIMP 3.0系からは前回選択したファイル名が残されているようで、今回の入力ダイアログでファイル名を "(なし)" としても前回選択していた "file:C:¥User¥Username¥Desctop¥filename.xcf" のような値が返されます。 よって、先頭の5文字が "file:" かどうかも確認する必要があります。

ファイルは未選択だとみなされると、

(gimp-message "読み込むファイルを指定してください。")

が実行されます。 条件を満たさなかった場合(つまり、ファイルが選択されている場合)は、

(gimp-message (string-append "読み込むファイルは" openfile "です。"))

が実行されます。

スクリプトの解説は以上になります。

利用者にフォルダを選択させてそのフォルダのXCFファイルを列挙する

続いては、利用者にフォルダを選択させてみましょう。 加えて、選択されたフォルダに置かれているXCFファイルを探し、ファイル名を列挙します

では、スクリプトファイル myscript.scm を以下のように書き換えてください。

  1. (define (myscript loadfrom runonwindows)
  2. (let
  3. (
  4. (filepattern "")
  5. (matchedfilelist "")
  6. (matchedfilename "")
  7. (matchedfilecount 0)
  8. (message "")
  9. )

  10. (if (= runonwindows TRUE)
  11. (begin
  12. (set! filepattern (string-append loadfrom "\\*.xcf"))
  13. )
  14. (begin
  15. (set! filepattern (string-append loadfrom "/*.xcf"))
  16. )
  17. )

  18. (set! matchedfilelist (car (file-glob filepattern 1)))

  19. (set! message (string-append "Found Files : "))

  20. (while (not (null? matchedfilelist))
  21. (set! matchedfilename (car matchedfilelist))

  22. (if (> matchedfilecount 0)
  23. (begin
  24. (set! message (string-append message "\n"))
  25. )
  26. )

  27. (set! message (string-append message matchedfilename))

  28. (set! matchedfilecount (+ matchedfilecount 1))

  29. (set! matchedfilelist (cdr matchedfilelist))
  30. )

  31. (gimp-message message)
  32. )
  33. )

  34. (script-fu-register-procedure
  35. "myscript" ; 登録する関数の名前
  36. "My Script" ; メニュー項目のラベル
  37. "自作の練習用スクリプトです" ; メニュー項目の説明
  38. "My Name" ; 作成者の名前
  39. "January 1, 2023" ; 作成日(改訂日)
  40. SF-DIRNAME "Load from" "" ; 入力ダイアログで選択されたフォルダ名
  41. SF-TOGGLE "On Windows?" FALSE ; 入力ダイアログでオン・オフされた値
  42. )

  43. (script-fu-menu-register
  44. "myscript" ; 対象の関数の名前
  45. "<Image>/Filters" ; メニュー項目の位置
  46. )
 

では、スクリプトを試してみましょう。

このスクリプトは、指定されたフォルダに置かれているXCFファイルを探してファイル名を列挙するためのものです。 よって、最初にテスト用のXCFファイルを準備しておく必要があります。

1. XCFファイルを準備する
1. XCFファイルを準備する

上図のように任意のフォルダに複数のXCFファイルを準備します。 今回の例では "夏祭り.xcf" / "花火大会.xcf" / "満月.xcf" の3ファイルです。

では、スクリプトを実行しましょう。 プルダウンメニューの"フィルター(R) -> My Script"を実行します。

2. 入力ダイアログが表示される
2. 入力ダイアログが表示される

上図のように入力ダイアログが表示されます。 フォルダ名を選択・確認するための入力欄 "Load from" が表示されています。

なお、"On Window?" はWindowsで実行しているかどうかをスクリプトに伝えるためのものです。

3. 任意のフォルダを選択する
3. 任意のフォルダを選択する

上図のようにテスト用のXCFファイルを配置したフォルダを選択します。 選択したら[OK(O)]ボタンを押してください。

  
Windowsで実行しているなら "On Window?" をチェックしてください。
4. メッセージダイアログに見つかったXCFファイルが列挙される
4. メッセージダイアログに見つかったXCFファイルが列挙される

上図のように指定されたフォルダに置かれているXCFファイルが列挙されました。 やりました、成功です。


それでは、今回のスクリプトを詳しく見ていきましょう。 まず目を引くのが52・53行目の、

SF-DIRNAME      "Load from"         ""                           ; 入力ダイアログで選択されたフォルダ名
SF-TOGGLE       "On Windows?"       FALSE                        ; 入力ダイアログでオン・オフされた値

の部分です。 新たな引数の型である SF-DIRNAME と SF-TOGGLE が登場しています。 SF-DIRNAME はフォルダ名を受け渡すための型で、SF-TOGGLEはオン・オフ状態を受け渡すための型、つまりチェックボックスです。

スクリプトへの引数の型 意味
SF-DIRNAME フォルダ名を受け渡す
※入力ダイアログが開く
SF-TOGGLE オン・オフ状態を受け渡す
※入力ダイアログが開く

続いて注目すべきは11行目から18行目の、

(if (= runonwindows TRUE)
    (begin
        (set! filepattern (string-append loadfrom "\\*.xcf"))
    )
    (begin
        (set! filepattern (string-append loadfrom "/*.xcf"))
    )
)

という制御文です。 検索するファイルのパタンとなる文字列を生成しています

入力ダイアログの "On Windows?" のチェックボックスがオンなら、

(set! filepattern (string-append loadfrom "\\*.xcf"))

の行が実行され、そうでなければ、

(set! filepattern (string-append loadfrom "/*.xcf"))

の行が実行されます。 つまり、Windowsかどうかで処理を分けています

以下の解説のように、UNIX系OSとWindowsではフォルダの区切り文字が異なります。 OSによってフォルダの区切り文字を変えるため、制御文で処理を分けています。

  
/(スラッシュ)は、UNIX系OSでのフォルダの区切り文字です。 例えば、フォルダ "a" の下のフォルダ "b" の下のファイル "c.xcf" であれば、"/a/b/c.xcf" という表記になります。
  
一方、Windowsではフォルダの区切り文字は \(バックスラッシュ) で表現されます。 ただし、過去の記事で説明したように文字列の中に \(バックスラッシュ) を含めるには \(バックスラッシュ) を連続させる必要があります。 よって、Windowsでは "\\" と2つ連続させて記述する必要があるのです。
  
\ はバックスラッシュです。 バックスラッシュはUNIX系OSでは \(バックスラッシュ) として正しく表示されますが、Windows では ¥(円マーク) として表示されます。 どちらにしても、日本語キーボードからは ¥(円マーク) で入力します。
  
ウェブ上でのバックスラッシュの表示は、ウェブブラウザによって異なります。 \(バックスラッシュ) として表示するものもあれば ¥(円マーク) として表示されるものもあります。
  
file-globは、OSの違いによるフォルダの区切り文字の違いを吸収してくれません。 UNIX系OSなら /(スラッシュ) Windowsなら \(バックスラッシュ) でキッチリと区切る必要があります。

生成したパタン(変数 filepattern)と一致するファイルを検索しているのが、20行目の、

(set! matchedfilelist (car (file-glob filepattern 1)))

という行です。 手続き file-globがファイルを検索するための手続きになります。

手続き 機能
file-glob パタンに一致するファイル名の一覧を返す

手続き file-globにファイルのパタン(変数 filepattern)を渡し、戻り値を変数 matchedfilelistに格納しています。

手続き file-globからの戻り値はファイル名の一覧です。 なお、戻り値(ファイル名の一覧)はベクタではなくリストです

つまり、変数 matchedfilelistに格納したデータはリスト、というわけです。 変数 matchedfilelistの内容を入れ子のコンスセルで表現すると、

夏祭り.xcf
満月.xcf
花火大会.xcf
<空っぽ>

となります。

変数 matchedfilelistに格納したファイル名の一覧を処理しているのが、24行目から38行目の、

(while (not (null? matchedfilelist))
    (set! matchedfilename (car matchedfilelist))

    (if (> matchedfilecount 0)
        (begin
            (set! message (string-append message "\n"))
        )
    )

    (set! message (string-append message matchedfilename))

    (set! matchedfilecount (+ matchedfilecount 1))

    (set! matchedfilelist (cdr matchedfilelist))
)

の部分です。 繰り返しの条件が、

(not (null? matchedfilelist))

となっていますが、これは『リストが空でない間は繰り返す』という意味になります。

繰り返しの内側では、25行目で、

(set! matchedfilename (car matchedfilelist))

という命令によって、検索されたファイル名のリストから最初の情報が取り出されます。 正確に表現すれば、最初のコンスセルのcar部が取り出されます。 繰り返しの1回目であれば、

夏祭り.xcf

が取り出されます。

同じく繰り返しの内側となる27行目から31行目の、

(if (> matchedfilecount 0)
    (begin
        (set! message (string-append message "\n"))
    )
)

は、メッセージに改行を足しているだけです。

  
文字列の中の \n は改行を表します。

同じく繰り返しの内側の37行目の、

(set! matchedfilelist (cdr matchedfilelist))

は、とても重要な命令になります。 変数 matchedfilelistの最初のコンスセルのcdr部を取り出し、自分自身に代入しているのです

実行前の変数 matchedfilelistの内容が、

夏祭り.xcf
満月.xcf
花火大会.xcf
<空っぽ>

だとすると、実行後は、

満月.xcf
花火大会.xcf
<空っぽ>

となります。 次の繰り返しで再度実行されると、

花火大会.xcf
<空っぽ>

となるのです。 つまり、実行されるたびにリストの先頭の情報が1つずつ消えていくのです。

リストの内容を1件ずつ処理するには、このように自分自身に手続き cdr部を実行するのが手軽で便利です

Windows上で実行されているかどうかの判断について

このスクリプトでは、Windows上で実行されているかどうかは利用者がチェックボックスで入力します。

それでは不便なので自動的に判別できないか、と考えて思いついたのがファイル /etc/hosts の有無を調べるという方法です。

21行目の、

(if (= runonwindows TRUE)

という制御分を、

(if (= (length (car (file-glob "/etc/hosts" 1))) 0)

に変更することで自動判別できそうです。

  
手続き length でリストの情報件数を取得することができます。
  
  

まとめ

スクリプト実行時に入力ダイアログを表示させるには、手続き script-fu-register-procedure(または script-fu-register-filter)に追加の引数を渡す必要があります。 追加の引数は3つずつ増えますが、最初の引数にはスクリプトに渡す引数の型を記述します。

スクリプトに渡す引数の型には以下があります。

スクリプトへの引数の型 意味
SF-ADJUSTMENT 数値を受け渡す
※入力ダイアログが開く
SF-STRING 文字列を受け渡す
※入力ダイアログが開く
SF-FILENAME ファイル名を受け渡す
※入力ダイアログが開く
SF-DIRNAME フォルダ名を受け渡す
※入力ダイアログが開く
SF-TOGGLE オン・オフ状態を受け渡す
※入力ダイアログが開く

なお、入力ダイアログは必要であれば自動的に表示されます。 入力ダイアログを表示せせるための手続きはありません。

スクリプトへの引数の型に SF-FILENAME や SF-DIRNAME を指定すると入力ダイアログでファイルやフォルダを指定することができます。 その際のフォルダの区切り文字はOSによって異なります。 UNIX系OSでは /(スラッシュ) が、Windowsでは \(バックスラッシュ) がフォルダの区切り文字になります。

手続き file-globは指定したパタンに一致するファイルを検索するための手続きです。 フォルダの区切り文字の変換は行わないため、UNIX系OSなら /(スラッシュ) を、Windowsでは \(バックスラッシュ) を区切り文字としてキッチリ記述する必要があります。

手続き 機能
file-glob パタンに一致するファイル名の一覧を返す

手続き file-globが返すファイル名の一覧はリストです。 ベクタではないので注意が必要です。

リストが空でないかどうかは、

(not (null? listname))

で判断することができます。

リストが空でないなら、

(car listname)

とすることで、先頭の1件の情報を取り出すことができます。

先頭の1件の情報を取り出したら、

(set! listname (cdr listname))

とすれば、先頭の1件の情報を消去してリストを詰めることができます。

なお、手続き lengthを利用することでリストの情報件数を取得することができます。

手続き 説明
length リストの情報件数を取得する
メニュー