趣味の開発ノート

ITの学習やプログラミング・ノーコードアプリ開発のことなど。

【Python】画像入りフォルダをドラッグ&ドロップでまとめて余白削除するツール

技術文章の執筆用に、画像の余白部分を削除するツールをPythonで作りました。

以前の記事で、Pillowというライブラリを使用したスクリプトを紹介しています。

nouka-it.hatenablog.com

今回の記事では、これを元に「画像の入ったフォルダをターミナル上にドラッグ&ドロップすることで画像をまとめて処理できるツール」を作成したので、その内容を残しておくことにしました。

スクリプトがやや長く、この記事の中では細かく解説ができませんでした。「Pythonでこんなことができるよー」という参考になればよいなと思います。

もし何か知りたいことがあればコメントいただけたらと思います!

やりたいこと

今回のツールでやりたいことは、複数の画像の余白処理をまとめて簡単にしたい、というところでした(PCはMacを使用しています)。

実際にこのツールを使って行う手順は、以下の通りです。

  1. Pythonスクリプトを実行すると、ターミナルでパスの入力を求められる
  2. その状態でターミナルに画像の入ったフォルダをドラッグ&ドロップすると、絶対パスが入力される
  3. Enterキーを押すと、フォルダ内の全ての画像について余白の削除が適用される
  4. 新しい画像は、作成された「cropped」フォルダに保存される

動画にしてみました。ここではVSCodeのターミナルを使っています。

ちなみにこの「ドラッグ&ドロップで処理する」というのは、ノンプロ研メンバーのこはたさんの記事を読んで良いなと思って、アイデアをいただきました(実際に処理する内容は違うのですが)。

note.com

pyファイルの実行に関して、Windowsだとドラッグ&ドロップでpyファイルの実行をすることが可能なようなのですが、Macだとどうもそれができません。

どうすれば簡単にできるのかなーと考えたところ、このようにターミナルを使ってやるのが一番簡単そうでした。

スクリプト

こちらが今回のスクリプトです。

from pathlib import Path
from PIL import Image, ImageChops

def input_path():
    """入力を受け付けてパスを返す関数

    Returns:
        path(Path): 入力されたパス
    """
    data = input('パスを入力 > ')
    path = Path(data.replace("\'", ''))

    return path

  
def create_files_tuple(path):
    """パスを元に画像ファイルのパスが格納されたタプルを返す関数

    パスがfileならそのパスを、folderならその中身のパスをタプルに格納

    Args:
        path(Path): 入力されたパス
    Returns:
        files(tuple): 画像ファイルのパスが格納されたタプル
    """
    if path.is_dir():
        _files = path.iterdir()
        files = tuple(_files)
    if path.is_file():
        files = (path,)

    return files


def crop_margin(path, bg_color=(255, 255, 255), override=False):
    """画像の余白を削除する関数

    Args:
        path(Path): 元画像ファイルのパス
        bg_color(tuple): 削除したい余白カラー(デフォルトは白)
        override(boolean): 処理した画像を上書き保存するかどうか(Trueで画像を上書き保存、Falseでcroppedフォルダを作成し保存)
    """
    # 保存先パスの決定
    if override:
        dest_path = path
    else:
        dir_path = Path(f'{path.parent}/cropped')
        dir_path.mkdir(exist_ok=True)
        dest_path = Path(f'{dir_path}/{path.name}')

    # 余白削除のための差分画像の作成
    img = Image.open(path)
    bg_img = Image.new('RGBA', img.size, bg_color)
    diff_img = ImageChops.difference(img, bg_img)

    # クロップ範囲の取得と実行
    crop_range = diff_img.convert('RGB').getbbox()
    crop_img = img.crop(crop_range)
    crop_img.save(dest_path)


if __name__ == '__main__':
    path = input_path()
    files = create_files_tuple(path)
    for file in files:
        crop_margin(file)

スクリプトの補足

ツールを作っていて詰まったところ、工夫したところなどなど補足をしておきます。

ドラッグ&ドロップでパスを入力するときにシングルクォートがつく問題

ターミナルにドラッグ&ドロップしてパスを取得すると、自動でシングルクォートがついてしまいます。

最初このことに気づかず、input_path()関数でこのパスの取得を行う際に、このシングルクォートが邪魔でその後の処理が正常に作動してくれませんでした。

そこで、文字列型のreplace()メソッドを使ってシングルクォートを空文字列に置き換えることで、正しいパスを取得できるようにしています。

def input_path():
 〜省略〜
    data = input('パスを入力 > ')
    path = Path(data.replace("\'", ''))
 〜省略〜

入力するパスはファイルでもフォルダでも対応可能

パスの入力時にフォルダをドラッグ&ドロップすることで、フォルダ内の画像すべてに同じ処理をします。

それをするために、create_files_tuple()関数の中で、フォルダの中の画像ファイルのパスを一度タプルに格納しています。このときに、画像の入フォルダだけでなく、画像ファイル単体をドラッグ&ドロップしても対応できるようにしています。

def create_files_tuple(path):
 〜省略〜
    if path.is_dir():
        _files = path.iterdir()
        files = tuple(_files)
    if path.is_file():
        files = (path,)

    return files

画像を上書き保存するようにもできるよ

実際の画像の処理は、crop_margin()関数で行っています。この中でオプション引数としてoverrideを用意して、出力する画像を上書き保存するかどうかを指定できるようにしてみました。

def crop_margin(path, bg_color=(255, 255, 255), override=False):
 〜省略〜
    # 保存先パスの決定
    if override:
        dest_path = path
    else:
        dir_path = Path(f'{path.parent}/cropped')
        dir_path.mkdir(exist_ok=True)
        dest_path = Path(f'{dir_path}/{path.name}')
 〜省略〜

このcrop_margin関数を実行する際に、override=Trueを引数に記述してあげれば、余白を削除した画像を元画像に上書き保存するようになります。

if __name__ == '__main__':
 〜省略〜
    for file in files:
        crop_margin(file, override=True)

スクリプトの問題点

フォルダの中に画像ファイル以外が入っていたらどうなるの?という問題がありますが、そこはちゃんと作っていません。汗

運用でなんとでもなるので、あまりスクリプトを複雑にしなくても良いのかなと思っています。

おわりに

このままでも使えますが、実際にはこのpyファイルをダブルクリックで実行できるようにツール化して活用しています。

個人的にはなかなか便利なツールができたので満足です。