プログラムでおかえしできるかな

Python、フェイジョア、日常のあれこれでお返し、元SEの隠居生活。

コラージュ処理をPython-FuスクリプトでPythonのGUIを付けて【GIMP、Python】

このエントリーをはてなブックマークに追加

4枚の画像(写真)を上下左右に並べて1枚にするコラージュ画像を自動で作るツールを作成したので紹介します。

画像編集ソフトの GIMP とそのスクリプトを利用してバッチ処理にしています。
それを Python で作成した GUI から起動しています。
GUI 上で出来上がりを確認しながら使えます。

目次

◆アプリのサンプル画像と機能・特長

▽起動後の画面

▽画像読み込み後の画面

▽生成画像

【機能・特長】

  • 画面上でコラージュ画像の出来上がりを確認できます
  • 生成画像の幅を指定できます
  • 境界線の幅を指定できます
  • 境界線の色を指定できます
    • 色選択ダイアログで色を指定できます
    • 色選択コンボボックスで色名で色を選択できます

【考え方】

こちらの記事で4つの画像を上下左右に並べて一つの画像にする GIMPスクリプトをバッチファイルにした方法を紹介しました。
この記事で行っている処理を PythonGUI 上から操作できるようにしました。

先に使い方が知りたい方は、こちらをクリックすると移動します。
アプリの取得方法と使い方 ⤵

ベースにしているバッチ処理に対して、
画面を用意して画面上で、画像をコラージュ表示して、それを画像に出力します。

Python-Fu スクリプトを作成して、コラージュ画像を作成するバッチの作り方の説明は、こちらの記事に掲載しています。
 📖 コラージュ処理をPython-Fuスクリプトでバッチ処理【GIMP】 🔗

Python-Fu スクリプトをバッチで動かす基本的な説明は、こちらの記事に掲載しています。
 📖 Python-Fuスクリプトでバッチ処理【GIMP】 🔗

Python-Fuスクリプトバッチ処理を起動

GIMPPython-Fu スクリプトは、GIMPコマンドラインオプションで指定して起動することができます。

ここでは、subprocess モジュールを使用して GIMP を起動し、Python-Fu スクリプトが実行されるようにします。

◇外部プログラムの起動

外部プログラムの起動には、subprocess モジュールの run() メソッドを使用します。

GIMP を起動する時に標準入力から Python-Fu スクリプトを読み込ませます。
run() メソッドは、標準入力からの入力を input 引数で提供しています。

▽subprocess.run()メソッド

引数 args で指定されたコマンドを実行します。コマンドの完了を待って、
CompletedProcess インスタンスを返します。
内部で Popen() メソッドが呼び出され、ほとんど引数は、Popen() メソッドに渡されます。

使用にはインポートが必要です。

  • インポート:import subprocess

  • 【構文】subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None, **other_popen_kwargs)

  • ここでの使い方(サンプル)
    【構文】subprocess.run(_cmd, input=_script, encoding=_script_encoding)
  • 主な引数
    • args:プログラムのシーケンスか文字列か path-like オブジェクト
      シーケンスを推奨。シーケンスの最初の要素は実行されるプログラム
      例:["notepad", "readme.txt"]
      例:[r"\windows\system32\notepad.exe", "readme.txt"]
    • input:内容をサブプロセスの標準入力に渡します
      内容は、バイト列か文字列
      文字列の場合、encoding, errors が指定されているか text が True が必要
    • capture_output:true にすると stdout と stderr が補足されます 1
    • check:true の場合、終了コードが 0 でない時に CalledProcessError 例外が送出されます

▽CompletedProcessクラス

run() メソッドの戻り値である CompletedProcess クラスの属性について簡単に説明します。

  • 属性
    • args   :プロセスを起動した時の引数
    • returncode:子プロセスの終了コード。一般的に 0 が正常終了
    • stdout  :子プロセスから補足された標準出力
    • stderr  :子プロセスから補足された標準エラー出力

▽コード:Python-Fuスクリプトバッチを指定したGIMPの起動

    def do_gimp_script(self, _files_t:list, border_rgb:tuple, img_width:int, border_width:int, event=None) -> int:
        """
        GIMPスクリプト(バッチ)の実行
        Args:
            list:   ファイルパス(4つ)
        Returns:
            int:    バッチの結果(0:OK)
        """
        _shori = 2

        # 中略

        gimp1 = settings_collage.gimp1
        gimp2 = settings_collage.gimp2

        _script_encoding = 'utf-8'

        if hasattr(sys, '_MEIPASS'): # under pyinstaller
            scriptpath = os.path.join(sys._MEIPASS, settings_collage.scriptpath)
            # _script_encoding = 'cp932'
        else:
            scriptpath = os.path.join('..', settings_collage.scriptpath)

        with open(scriptpath, encoding=_script_encoding) as f:
            _script = f.readlines()
        _script = "".join(_script)

        # 中略

        # GIMPのコマンドライン引数の指定
        if _shori == 2:
            # コンソールモード
            _cmd = [f"{gimp2}", "-i", "-n", "-c", "-d", "-s", "-f"
                , "--batch-interpreter", "python-fu-eval", "-b", "-", "-b", "pdb.gimp_quit(1)"]
        else:
            # 通常モード
            _cmd = [f"{gimp1}", "-n", "-c", "-d", "-s", "-f"
                , "--batch-interpreter", "python-fu-eval", "-b", "-", "-b", "pdb.gimp_quit(1)"]
            # スクリプトにエラーがあった場合、"pdb.gimp_quit(1)"を削除するとGIMP画面が残る

        # GIMPの起動
        result = subprocess.run(_cmd, input=_script, encoding=_script_encoding)

        return result.returncode

◇変数の内容を置換

Python-Fu スクリプトに引数を渡したいのですが、引数の渡し方が分かりませんでした。
そこで、Pyton-Fu スクリプトファイルを読み込み、引数で渡す部分を文字列の置換で対応しました。

置換した文字列は、subprocess の run() メソッドの input 引数を使用して標準入力として与えます。

【置換しているもの】

  • 画像ファイル名
    _files_b = (r'image_file_name1', …
    for f_b, f_t in zip(_files_b, _files_t):
      _script = _script.replace(f_b, f_t)
  • GIMPのコンソールモード
    _script = _script.replace('123456', 'False')
  • 境界線の色
    _script = _script.replace('gimpcolor.RGB(255, 255, 255)', f'gimpcolor.RGB({border_rgb[0]},{border_rgb[1]} ,{border_rgb[2]})')
  • 作成画像の幅
    _script = _script.replace('_width = 800', f'_width = {img_width}')
  • 境界線の幅
    _script = _script.replace('border_width = 4', f'border_width = {border_width}')
  • 保存パス
    _script = _script.replace('replace_path', f'{os.path.dirname(sys.executable)}')
    アプリ起動時はアプリのあるフォルダを指定しています

▽コード:変数内容の置換

    def do_gimp_script(self, _files_t:list, border_rgb:tuple, img_width:int, border_width:int, event=None) -> int:

        # 中略

        _shori = 2

        _files_b = (r'image_file_name1', 
                    r'image_file_name2', 
                    r'image_file_name3',  
                    r'image_file_name4')

        # 中略

        if hasattr(sys, '_MEIPASS'): # under pyinstaller
            scriptpath = os.path.join(sys._MEIPASS, settings_collage.scriptpath)
            # _script_encoding = 'cp932'
        else:
            scriptpath = os.path.join('..', settings_collage.scriptpath)

        with open(scriptpath, encoding=_script_encoding) as f:
            _script = f.readlines()
        _script = "".join(_script)

        # 画像ファイル名置換
        for f_b, f_t in zip(_files_b, _files_t):
            _script = _script.replace(f_b, f_t)

        # 境界線の色置換
        _script = _script.replace('gimpcolor.RGB(255, 255, 255)', f'gimpcolor.RGB({border_rgb[0]},{border_rgb[1]} ,{border_rgb[2]})')

        # 作成画像の幅置換
        _script = _script.replace('_width = 800', f'_width = {img_width}')

        # 境界線の幅置換
        _script = _script.replace('border_width = 4', f'border_width = {border_width}')

        # 保存パス置換
        if hasattr(sys, '_MEIPASS'): # under pyinstaller
            _script = _script.replace('replace_path', f'{os.path.dirname(sys.executable)}')
        else:
            _script = _script.replace('replace_path', '.')

        # GIMPのコンソールモードを置換
        if _shori == 2:
            _script = _script.replace('123456', 'False')

◆4つの画像をコラージュするPython-Fuスクリプト(UTF-8)

Python-Fu スクリプトは基本的に下記の記事の説明と同じです。

▶説明は別記事を参照してください
 📖 ◆4つの画像をコラージュするPython-Fuスクリプト(UTF-8) - コラージュ処理をPython-Fuスクリプトでバッチ処理【GIMP】 🔗

ただし、次の点を変えてあります。

  • 生成ファイルを exe のあるフォルダに保存

◇コード:Python-Fu スクリプト

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from gimpfu import *
import gimpcolor
import os

_width = 800                           # 変更不可(置換している)完成画像の幅(1つの画像の幅ではない)
border_width = 4                       # 変更不可(置換している)境界線の幅(偶数で)
_color = gimpcolor.RGB(255, 255, 255)    # 変更不可(置換している)色のRGBを指定(255,255,255は白)

_debug = 123456                            # デバッグ用バッチで変更するのでここでは変更不可
_save_path = "replace_path"             # 変更不可(置換している)画像の保存フォルダ
offset = border_width // 2
# コラージュ画像の幅、高さ、生成画像の高さを計算
_swidth = (_width - border_width * 3) // 2  # 横に2分割した時のコラージュ画像の幅
_sheight = _swidth * 3 // 4   # コラージュ画像の高さ アスペクト比4:3で計算
_height = (_sheight * 2) + (border_width * 3)   # 生成画像の高さ

_type = 0 # RGB(0), GRAY(1), INDEXED(2)
# ダミーの名前を定義、後で置換して目的のファイル名にする
_files = (r'image_file_name1', 
            r'image_file_name2', 
            r'image_file_name3',  
            r'image_file_name4')

pdb.gimp_message("My Python-fu開始(My Python-fu start)")
img = pdb.gimp_image_new(_width, _height, _type)
layer_ids = []
for _filename in _files:
    layer_id = pdb.gimp_file_load_layer(img, _filename)
    layer_ids.append(layer_id)
    pdb.gimp_image_insert_layer(img, layer_id, None, 0)    # parent(None):no group, position(0):top

pdb.gimp_image_resize_to_layers(img)                # キャンバスのサイズをレイヤーに合わせる
# pdb.gimp_image_scale(img, _swidth, _sheight)     # 画像の拡大縮小
for _layer in layer_ids:
    pdb.gimp_layer_scale(_layer, _swidth, _sheight, TRUE)
pdb.gimp_image_resize_to_layers(img)                # キャンバスのサイズをレイヤーに合わせる

pdb.gimp_message("images scaled to w:{}, h:{}".format(_swidth, _sheight))
pdb.gimp_image_resize(img, _width, _height, 0, 0) # キャンバスのサイズ変更

pdb.gimp_message("整列開始(Alignment start)")
pdb.gimp_layer_set_offsets(layer_ids[0], border_width, border_width)
pdb.gimp_layer_set_offsets(layer_ids[1], _swidth + border_width * 2, border_width)
pdb.gimp_layer_set_offsets(layer_ids[2], border_width, _sheight + border_width * 2)
pdb.gimp_layer_set_offsets(layer_ids[3], _swidth + border_width * 2, _sheight + border_width * 2)

pdb.gimp_message("グリッドフィルター開始(Start grid filter)")
# グリッド用レイヤーの作成(画像、幅、高さ、タイプ(1:RGBA(透明付 ))、名前、透明度、モード)
grid = pdb.gimp_layer_new(img, _width, _height, 1, "grid", 100, 0)
pdb.gimp_image_insert_layer(img, grid, None, 0)    # parent(None):no group, position(0):top
pdb.gimp_message("グリッドレイヤー作成完(Grid layer created)")
# グリッド(レガシー)
# 画像、対象、水平線幅、間隔、オフセット、色、透明度(0-255)、垂直線高さ、間隔、オフセット、色、透明度、交点幅、間隔、オフセット、色、透明度
hspace_width = _width // 2 - offset
vspace_width = _height // 2 - offset
# pdb.gimp_message('bd:{}, hspace:{}, vspace:{}'.format(border_width, hspace_width, vspace_width))
pdb.plug_in_grid(img, grid, border_width, vspace_width, offset, _color, 255
                          , border_width, hspace_width, offset, _color, 255
                          , 0, 0, 0, _color, 0)
pdb.gimp_message("グリッド作成完(Grided)")

if _debug:
    pdb.gimp_display_new(img)
    pdb.gimp_message("イメージ表示(image display)")

# 表示されているレイヤーをマージして一つのレイヤーに
layer = pdb.gimp_image_merge_visible_layers(img, EXPAND_AS_NECESSARY)

# 画像の保存
from datetime import datetime
out_f_name = "GIMP" + datetime.now().strftime('%y%m%d%H%M') + ".jpg"
pdb.gimp_message("ファイル名:{}\{}".format(_save_path, out_f_name))
pdb.gimp_file_save(img, layer, os.path.join(_save_path, out_f_name), "")

※1:コラージュする画像の高さを幅から求めるよう変更
   生成画像の高さをコラージュする画像の高さと境界線幅から求めるように変更 更新:2023-10-06

GUI作成に伴う処理

コラージュした画像を表現するために4つの画像を表示するラベルとそのラベルを配置するフレームを用意しました。
ラベルとラベルの間には隙間を設けました。
その隙間にはフレームの背景色が表れます。
それを境界線とみなします。

生成画像の幅を基準に境界線の幅を考慮してコラージュする画像の幅を決定します。
実際にコラージュした画像と同じになるように画面を作成します。

◇画像の読み込みと縮小

画像は、tkinter の PhotoImage クラスでは、JPEG が扱えないので、pillow を使用します。
また、縮小には thumbnail() メソッドを使用しました。
thumbnail() メソッドは縮小しかできませんが、アスペクト比が変わらないので使い勝手が良いです。

▽コード:画像の読み込み

    def load_image(self, path:str) -> ImageTk.PhotoImage:
        """
        画像を読み込んで縮小(pillowを使用)
        self.thumbnail_xyの幅と高さに縮小
        """
        if path:
            _img = Image.open(path)
            _img.thumbnail((self.thumbnail_xy[0], self.thumbnail_xy[0]), Image.BICUBIC)
        else:
            # 画像がない時の初期画像
            _img = Image.new('RGBA', (self.thumbnail_xy[0], self.thumbnail_xy[1]), (255, 0, 0, 0))  # 透明なものにしないとgifの色が変わる
        return ImageTk.PhotoImage(_img)

◇色選択ダイアログ

境界線の色を変更するために色選択ボタンを用意し、色選択ダイアログを表示します。
境界線の色にあたるフレームの背景色を色を選択した後に設定します。

▽colorchooserダイアログ

tkinter では色を選択するためのダイアログが用意されています。
ダイアログ表示メソッドの戻り値で選択した色が返るのでフレームの背景色に設定します。

使用にはインポートが必要です。

  • インポート:from tkinter import colorchooser
  • 【構文】colorchooser.askcolor(color=None, **options)
  • 主な引数
    • color:初期色。デフォルトは「light gray」
    • title:ダイアログのタイトル。デフォルトは「Color」
  • 戻り値:(RGB値, カラー値) のタプル、キャンセルの場合 (None, None)
    • RGB値:(R, G, B)の整数のタプル
    • カラー値:#a0a0a0 の16進値

▽コード:色選択ダイアログの表示

        # 色選択ボタン
        self.btn_color = tk.Button(frm_buttons, text="色選択", command=self.get_color)
        self.btn_color.pack(fill=tk.X, pady=(0,5))

    def get_color(self, event=None):
        """
        色の選択
        色選択ダイアログを表示して選択された色を取得し、frm_imagesの背景に設定
        """
        _color = colorchooser.askcolor(title="色選択")  # ダイアログを表示し色を選択
        self.frm_images.config(background=_color[1])    # 背景色を設定

◇色選択コンボボックス

tkinter で用意されている色選択ダイアログは色の選び方が難しいと感じました。
また、tkinterウィジェットの色指定には、色名が使えます。
そこで、色名を選択できるコンボボックスを作成することにしました。

ここで問題になったのが色名のリストです。
いろいろ探してみたのですが、結果として pillow パッケージにあるマップを使うことにしました。

ImageColor.colormap:キーが色名、値が色の16進値の辞書

使用にはインポート(from PIL import ImageColor)が必要です。

この辞書のキーを色のリストにします。

また、コンボボックスに文字列を入れると色のリストをフィルタリングするようにしました。
フィルタリングには入力検証の機能 2 を使用します。

▽コンボボックスの候補のフィルタリング

コンボボックスで候補をフィルタリングするために次の処理を行います。

  • コンボボックスインストラクタで postcommand 引数を指定
    ドロップダウンメニューを表示する前に値を変更する関数を指定します
    関数内でコンボボックスウィジェットvalues オプションを更新します
    今回はラムダ式で実装します
    postcommand = lambda : self.cmb_color.config( values=self.colors)
  • 入力検証で色名リストを抽出
    上記で使用している self.colors に表示する色のリストを設定します
    入力検証用関数の中で色のリストを入力された文字でフィルタリングします
    フィルタリングは正規表現で行います
    [s for s in ImageColor.colormap.keys() if re.match( f".*{modify_str}.*", s)]
    フィルタリングの結果が空の場合、色のリストすべてを対象とします

▽Combobox インストラク

  • 【構文】ttk.Combobox(parent, option=value, ...)
  • 主なオプション
    • values     :表示リストデータ設定
    • postcommand  :コンボボックスを選択する直前に呼び出される関数を指定
    • validate    :入力値の判定タイミングを指定
    • validatecommand:判定する関数を指定
    • invalidcommand :入力値が正しくない場合に実行する関数を指定
    • textvariable  :コンボボックスの値(選択されているもの)とリンクさせる変数名を指定

▽コード:色選択コンボボックス

        # 色選択コンボボックス
        self.colors = list(ImageColor.colormap.keys())  # 選択候補用データ
        var_color = tk.StringVar()
        self.cmb_color = ttk.Combobox(frm_buttons, values=self.colors, textvariable=var_color
                                    , postcommand=lambda : self.cmb_color.config(values=self.colors)
                                    , validate='key'
                                    , validatecommand=(self.register(self.cmb_validate), '%d', '%P'))
        self.cmb_color.pack(fill=tk.X, pady=(0,5))
        self.cmb_color.bind('<<ComboboxSelected>>', self.set_cmb_color)

    def set_cmb_color(self, event=None):
        """
        色を設定
        色選択コンボボックスで選択された色を取得し、frm_imagesの背景に設定
        コンボボックスから色を選択した時にイベントが発生して起動
        """
        self.frm_images.config(background=self.cmb_color.get())

    def cmb_validate(self, action:str, modify_str:str) -> bool:
        """
        コンボボックスの入力検証
        テキストの内容で色種のリストから一致するものをコンボボックスの出力対象にする
        Args:
            str:    アクション(削除:0、挿入:1、その他:-1)
            str:    変更後のテキスト
        """
        # 入力文字を含むものを色のリストから抽出
        _colors = [s for s in ImageColor.colormap.keys() if re.match(f".*{modify_str}.*", s)]
        # 抽出結果が空なら元のリスト全体を設定
        if _colors:
            self.colors = _colors
        else:
            self.colors = list(ImageColor.colormap.keys())
        return True

◇コラージュする画像の幅

▽プロパティの使用

今回、初めてプロパティを使用しました。
属性と違い、演算結果を値として返せることが特徴です。
プロパティは、読み出し専用、読み書き可能にすることができます。
今回は、読み出し専用のプロパティにしました。
読み出し専用のプロパティは getter メソッドで値を返します。
getter メソッドを定義するには @property デコレータを付加します。
プロパティは関数のように定義しますが、属性の様に参照します。

@property
def プロパティ名(self):
    値 = 演算
    return

今回プロパティを使用したのは、アスペクト比を固定して、生成画像の幅または境界線の幅が変わった時にコラージュする画像の幅と高さを何度も記述しなくて良いようにするためです。

▽コード:プロパティ

    @property
    def thumbnail_xy(self):
        """
        コラージュする個々の画像の幅と高さ(生成画像の幅と境界の幅により可変)
        Returns:
            int:    コラージュする画像の幅
            int:    コラージュする画像の高さ
        """
        _width = self.var_width.get()
        _swidth = (_width - self.var_border.get() * 3) // 2  # 横に2分割した時のコラージュ画像の幅
        _sheight = _swidth * 3 // 4   # コラージュ画像の高さ アスペクト比4:3で計算
        return (_swidth, _sheight)

▽生成画像、境界線、コラージュ画像の幅の変更

今回は、生成画像の幅と境界線の幅を画面で変更できるようにしました。

それぞれ次のような処理をしました。
どちらを変更してもコラージュする画像の幅が変わります。
従ってコラージュする画像の読み直しが必要になります。

  • 生成画像の幅の変更
    1. コラージュする画像の読み直し(プロパティを使うことで幅の変更を明記せず読み直しができます)
    2. 表示の更新
  • 境界性の幅の変更
    1. 画像の再配置
      境界線は画像を表示しているラベルウィジェットの padx と pady で指定しています
      これらは grid で指定しているので grid をやり直します
      ※変更することも可能ですが、やり直した方が再描画がカタカタしないという判断です
    2. 生成画像の幅の変更を実施
      ※境界の幅が変わると画像の幅も変える必要があるため

▽コード:画面での幅の変更

    def change_width(self, event=None):
        """
        生成画像の幅の変更による画像の読み直しと再表示
        """
        for i in range(4):                          # ラベルの画像の再設定
            self.lbl_imgs[i].img = self.load_image(self.image_paths[i])     # ラベルのimgに画像を読み直し
            self.lbl_imgs[i].config(image=self.lbl_imgs[i].img, borderwidth=0)  # ラベルのimageに再設定
        self.frm_images.update_idletasks()

    def change_border_width(self, event=None):
        """
        境界の幅変更による画像の再描画
        ラベルの外側の間隔を再設定し、画像の幅も変わるので読み直しと再描画
        """
        _bwidth = self.var_border.get()
        for i in range(4):
            self.lbl_imgs[i].grid_remove()  # grid 配置を削除
            self.lbl_imgs[i].grid(row=i//2, column=i%2, padx=(_bwidth, _bwidth * (i%2)), pady=(_bwidth, _bwidth * (i//2)))
            # print(self.lbl_imgs[i].grid_info())
        self.change_width()     # 境界が変わると画像の幅も変える必要がある

ドラッグアンドドロップ

▶説明は別記事を参照してください
 📄 ドラッグアンドドロップの実装(TkinterDnD2の使い方) - CSV viewerアプリの作り方(ドラッグアンドドロップ)【Python】 🔗

ここでは、4つの画像を4つのラベルにドラッグアンドドロップできるようにします。
そのため、dnd_bind() メソッドは、4つのラベルごとに設定します。
関数は同じものを設定します。
関数内では、event 引数で widget 属性が取得でき、それがドロップされたラベルのウィジェットになります。

▽コード:ドラッグアンドドロップの設定

        for i in range(4):
            # D&Dの設定
            self.lbl_imgs[i].dnd_bind(sequence='<<Drop>>', func=self.load_image_to_label)
            self.lbl_imgs[i].drop_target_register(DND_FILES)

from tkinterdnd2 import *
class App(TkinterDnD.Tk):

    def load_image_to_label(self, event=None):
        """
        ドロップされた画像をラベルに表示
        """
        if not event: return
        path = self.tk.splitlist(event.data)[0]         # ドロップされたパスの取得
        event.widget.img = self.load_image(path)        # 読み込んだ画像の保存
        event.widget.config(image=event.widget.img)     # 読み込んだ画像をラベルに設定
        self.image_paths[event.widget.pathx] = path     # GIMPに渡すパスの更新
        event.widget.update_idletasks()

Tkinterの使い方

Tkinterティーキンター)は Python が標準で提供している GUI パッケージです。
他のプログラムの開発ツールで提供されているようなルック&フィールな GUI ツールではありません。
従って、画面を構成するには、すべてコードを書く必要があります。

Tkinter を使用するにはインポートが必要です。

  • インポート:import tkinter as tk

慣例的に tk と別名を付けるようです。

◇使用ウィジェット

Tkinter がサポートする GUI の部品をウィジェット (widget) と呼びます。

ウィジェットは基本的にインスタンスを作成し、表示テキストや表示方法などを指定して使用します。

今回使用しているウィジェットです。

パッケージ ウィジェット 用途 見た目
tkinter Frame
フレーム
ウィジェットの受け皿
tkinter Label
ラベル
画像の表示
tkinter Button
ボタン
押すと処理が動くボタン
tkinter Combobox
コンボボックス
選択可能なテキストを表示するプルダウンボックス
tkinter Entry
エントリー
テキストを入力するボックス

◇画面構成 - フレーム構造

プログラムを作るにあたって、どのような画面にするか決めなくてはなりません。画面構成を考えるということですね。
画面を構成するというのは、GUI 部品(ウィジェット)を画面に配置することです。
ウィジェットは設定したテキストに合わせてサイズが決まるので、そのまま使用しても画面構成上問題ないものと、広げないと見栄えが良くないものが出てきます。
ウィジェットを広げる場合、他のウィジェットとの位置関係で思ったように配置できないことがあります。
そのようなときに Frame ウィジェットを使用して区分けをして希望する位置に配置します。
引き出しの整理に使うトレイのようなイメージでしょうか。

今回の画面構成です。

  • frm_buttons (Frame)
    • btn_create (Button)
    • btn_color (Button)
    • cmb_color (Checkbox)
    • frm_width (Frame)
      • lbl_width (Label)
      • ety_width (Entry)
    • frm_border (Frame)
      • lbl_border (Label)
      • ety_border (Entry)
  • frm_imgs (Frame)
    • lbl_imgs[4] (Label)

※「frm_buttons」などはプログラムで使用した変数名です。()内はクラス名です。

基本的に配置には pack() メソッドを使用しています。
今回、画像表示用のラベルは4つを格子状に並べるため grid() メソッドを使用しています。
grid() メソッドと pack() メソッドは共存できないのでフレームウィジェットで区切っています。

ウィジェットの配置 - pack

▶説明は別記事を参照してください
 📖 ◇ウィジェットの配置 - pack - SQLクライアントアプリの作り方(Tkinterで表)【Python】 🔗

◇動作をウィジェットに割り当てる - バインド

▶説明は別記事を参照してください
 📖 □動作をウィジェットに割り当てる - Excel viewerアプリの作り方(Tkinterでタブと表)【Python】 🔗

◆ソースの取得

全体のソースはこちらから取得できます。

◇依存関係

◆アプリの取得方法と使い方

アプリの取得方法と使い方を説明します。

◇取得と保存

exe ファイルと設定ファイルを含んだ zip ファイルを下記からダウンロードして取得します。
ダウンロードした zip ファイルを解凍すると次のファイルができます。(他にもreadmeなどが入っています)
任意のフォルダにファイルを保存してください。

  • アプリ   :collage_by_GIMP.exe
  • 設定ファイル:settings_collage.py

◇動作環境

◇使い方

  • インストール

    ダウンロードした zip ファイルを任意のフォルダで解凍します

  • 起動

  • 操作

    • ドラッグ&ドロップでの操作
      • アプリ画面上の4つの枠ににコラージュしたい画像をドラッグ&ドロップ
    • 生成画像の幅を指定
      • テキストボックスに生成画像の幅を入力しEnterを押します
        • 画面が指定した幅に変わります
    • 境界線の幅を指定
      • テキストボックスに境界線の幅を入力しEnterを押します
        • 画面が指定した幅に変わります
    • 色の選択
      • 色選択ボタンで選択
        • 色選択ボタンを押すと色選択ダイアログが表示されます
        • 希望の色を選択して OK ボタンを押します
      • 色選択コンボボックスで選択
        • コンボボックスの▼を押すと色名の選択肢が出るので選択します
        • コンボボックスに色名の一部を入れて↓キーを押すと入力した文字を持つ色名を出力します
    • 画像の生成

      • 作成ボタンを押します。処理が終わるとボタンが緑色になります。
        exe のあるフォルダに GIMPyymmddhhmm.jpg 画像が作成されます
    • 画面の説明

      • 作成ボタン     :コラージュした画像を作成します
      • 色選択ボタン    :境界線の色を指定します
      • 色選択コンボボックス:境界線の色を色名で指定します
      • 生成画像の幅    :生成画像の幅を px で指定します
      • 境界線の幅     :境界線の幅を px で指定します
      • 4つの四角     :コラージュする画像を表示します
  • アンインストール

    解凍したファイルをすべて削除します

◇設定方法

取得した settings_collage.py ファイルを修正することで次の項目を設定できます。

GIMP の exe のパス、GIMPPython-Fu スクリプトのパスを指定します。
アプリを使用する場合、スクリプトのパスはそのまま使用してください。
ソースを利用する場合、スクリプトのパスはソースを置いたフォルダの親フォルダからの相対パスを指定してください。

  • GIMP の exe のパス     :gimp1 = r"C:\Program Files\GIMP 2\bin\gimp-2.10.exe"
  • GIMP のコンソール exe のパス:gimp2 = r"C:\Program Files\GIMP 2\bin\gimp-console-2.10.exe"
  • GIMPスクリプトのパス   :scriptpath = r'scripts\fu_collage.py'

settings_collage.pyテキストエディタ(メモ帳でも大丈夫です)などで修正します。

▽コード:設定ファイル

"""
フォルダ設定
"""

gimp1 = r"C:\Program Files\GIMP 2\bin\gimp-2.10.exe"
gimp2 = r"C:\Program Files\GIMP 2\bin\gimp-console-2.10.exe"

scriptpath = r'scripts\fu_collage.py'

◇制限事項

GIMPのインストール

GIMPのインストールはこちらのサイトが詳しいです。

◆更新情報

  • 2023-10-06 1.1.1
    • コラージュ画像の縮小が縦に短い問題を修正
  • 2023-09-30 1.1.0:初期リリース

更新:2023-10-06

◆さいごに

ここで紹介したものと同じ機能のものを Python の pillow パッケージを使って Python だけで作りたいと思っています。
GUI はそのままで、GIMP Python-Fu スクリプトのバッチを起動していた部分を置き換えれば良いと考えています。

そうすれば、GIMP がなくても画像のコラージュができます。

上手くできるといいのですが・・・


あわせて読みたい GIMPでコラージュ:📖 画像(写真)を4分割レイアウトにコラージュ【GIMP】 🔗
バッチでコラージュ:📖 コラージュ処理をPython-Fuスクリプトでバッチ処理【GIMP】 🔗 GIMPスクリプト:📖 Python-Fuスクリプトでバッチ処理【GIMP】 🔗

◇ご注意

本記事は次のバージョンの下で動作した内容を基に記述しています。

◇免責事項

ご利用に際しては、『免責事項』をご確認ください。
お気づきの点がございましたら『お問い合わせ』からお問い合わせください。

投稿: 、更新:

  1. これを True にすると stdout と stderr が補足され、CompletedProcess インスタンスで取得できます。
    しかし、pyinstaller で作成した exe で実行するとデコードエラーが発生します。
    このエラーは VS Code では発生しませんでした。
    エラー回避のために False にすると GIMP コンソールの出力が VS Code 上で文字化けします。
  2. 別記事に解説があります📄ScrolledFrameとwrapped_gridで作る画像一覧の作り方【Python】- ◆Entryオブジェクトの入力を数字だけにする(検証)