Pythonで画像圧縮とwebp画像生成をする

## 経緯
Webサイトを制作していく過程で画像の圧縮、webp画像を作る際に

- gulpで環境を作成して実行
- [Googleの変換ツール](https://developers.google.com/speed/webp)を使用してwebp画像を生成

という手順で作業を行った。

小規模なサイトならいいのですが、大規模なサイトや環境を組んでも共有できない事情があるなどを考えツールを作成してみた。

## 開発環境

- Windows10
- Python 3.9.5

## 目標

1. 複数のJPG, PNG画像を圧縮してくれる
2. 複数のJPG, PNG画像からWebp画像を生成してくれる
3. エンジニア以外の環境構築ができない人でも作業を行うことができる

3を考えたときにWebブラウザで動作するかGUIを作ってアプリケーションとするか悩ましいが
今回はPythonを勉強中ということもあり、デスクトップで使用できるアプリケーションを作成する。

## 完成品

https://github.com/reini02/image_controller/tree/main/dist

`/dist/image_controller.exe`に完成品が入っている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267610/4948906f-c12e-180d-9891-355e85e43918.png)

- パスに圧縮・変換する画像が格納されているディレクトリを指定する
- webp画像に変換したい場合はチェックボックスにチェックをいれる


## 使用するライブラリ

- [Pillow](https://pillow.readthedocs.io/en/stable/)
    - リサイズや圧縮など画像の処理を行う
- [PySimpleGUI](https://pysimplegui.readthedocs.io/en/latest/)
    - 簡単にGUIを実装する
- glob
    - Pythonインストール時にデフォルトでインストールされる
- os
    - Pythonインストール時にデフォルトでインストールされる

## 事前準備

```
$ pip install Pillow
$ pip install pysimplegui
```

## 実装

### 必要なライブラリを読み込む

```
# 画像圧縮のライブラリをImportする
from PIL import Image, ImageFilter

# ファイルを取得するためのライブラリをImportする
import glob
import os

#GUI生成するライブラリをImportする
import PySimpleGUI as sg
```

### 画像の圧縮をするソース

```
# 圧縮のクオリティ
quality = 70

# 画像が格納されているディレクトリを指定する
path = 'path/to/dir'
files = glob.glob(path + '/*')

# ディレクトリ内の画像をループで処理する
for file in files :
  # 画像の情報を取得
  image = Image.open(file)
  
  # 画像を圧縮してセーブする
  image.save(path  + '/' + os.path.split(file)[1], optimize=True, quality=quality)
```

### JPG, PNGをwebp画像として保存する

```
# 画像が格納されているディレクトリを指定する
path = 'path/to/dir'
files = glob.glob(path + '/*')

# ディレクトリ内の画像をループで処理する
for file in files :
  image.save(path + '/' + os.path.split(file)[1].split('.')[0] + '.webp', 'webp')
```

これで圧縮、webp画像の生成ができる

### GUIを実装する

```
# GUIのテーマを選択する
sg.theme('Reddit')

# GUIに必要な要素を配列で格納する
layout = [
            [sg.Text('　　パス'), sg.InputText(key='path', size=(40,8))],
            [sg.Text('webp画像を生成する'), sg.Checkbox('',key='is_webp', default=False)],
            [sg.Text('実行結果'),sg.MLine(key='output'+sg.WRITE_ONLY_KEY, size=(40,8))],
            [sg.Button('実行')]
          ]

# Windowを生成する
window = sg.Window(title='Image controller', layout=layout)

# Window内の処理を記載する
while True:
    event, values = window.read()

    # Windowが閉じられたときの処理
    if event == sg.WINDOW_CLOSED or event == 'Quit':
        break

    # ここに実行をクリックしたときの処理を記載する
    # syori();
```

GUIの実装を行っただけでPythonファイルを実行すると完成品のような見た目のGUIができる

### 組み合わせて一つのPythonファイルにする

```
# ------------------------------ Settings ------------------------------ #

# 画像圧縮のライブラリ
from PIL import Image, ImageFilter

# ファイルを取得するためのライブラリ
import glob
import os

#GUI
import PySimpleGUI as sg

# ------------------------------ Config ------------------------------ #

# 圧縮のクオリティ
quality = 70

# GUI
sg.theme('Reddit')

layout = [
            [sg.Text('　　パス'), sg.InputText(key='path', size=(40,8))],
            [sg.Text('webp画像を生成する'), sg.Checkbox('',key='is_webp', default=False)],
            [sg.Text('実行結果'),sg.MLine(key='output'+sg.WRITE_ONLY_KEY, size=(40,8))],
            [sg.Button('実行')]
          ]
window = sg.Window(title='Image controller', layout=layout)

# テーマの確認
# sg.preview_all_look_and_feel_themes()

# ------------------------------ 画像圧縮の処理 ------------------------------ #

def compression(file) :
  # 画像の情報を取得
  # フォーマット　image.format
  # サイズ　image.size
  # カラーモード image.mode
  image = Image.open(file)
  
  # 画像を圧縮してセーブする (values['path']でGUIで入力したパスが取得できる)
  image.save(values['path'] + '/' + os.path.split(file)[1],optimize=True,quality=quality)

  # メッセージをGUIに表示する
  window['output'+sg.WRITE_ONLY_KEY].print(os.path.split(file)[1] + ' is compressed.')

def initalizeWebp(file) :
  image = Image.open(file)

  # webpに変換してセーブする  (values['path']でGUIで入力したパスが取得できる)
  image.save(values['path'] + '/' + os.path.split(file)[1].split('.')[0] + '.webp', 'webp')

  # メッセージをGUIに表示する
  window['output'+sg.WRITE_ONLY_KEY].print(os.path.split(file)[1] + ' is initalize webp image.')

# ------------------------------ GUIの処理 ------------------------------ #

# Windowの処理
while True:
    event, values = window.read()

    # See if user wants to quit or window was closed
    if event == sg.WINDOW_CLOSED or event == 'Quit':
        break
    
    files = glob.glob(values['path'] + '/*')

    for file in files :
      compression(file)
      
      # チェックボックスがチェックされていたら実行
      if (values['is_webp'] == True) :
        initalizeWebp(file)
```

## 参考サイト

https://note.com/kohaku935/n/n721e5e620839
https://qiita.com/dario_okazaki/items/656de21cab5c81cabe59

PySimpleGUIは公式のドキュメントがわかりやすく書いてあるので参考になります。
https://pysimplegui.readthedocs.io/en/latest/

## 補足

Pythonファイルのexe化は以下のツールを使用しています。

- pyinstaller

以下サイトを参考にしています。
https://techacademy.jp/magazine/18963

結構導入～実行まで簡単だったのでおすすめです。