Pillow への機能追加

# はじめに
大学の「大規模ソフトウェアを手探る」という授業で、Pillow への機能追加を選んだので、その際に自分が追加した機能について紹介する。

:::note info
Pillow 本家のリポジトリへの Contribute はまだしていません。
:::

## 環境
- Python3.11.1
- Pillow==10.1.0
- venv



# 追加した機能
## 画像を中央で切り取る
Image オブジェクトに、画像を指定した大きさで中央から切り取る `Image.crop_center(size)` メソッドを実装した。

- `size`: (width, height)-tuple. 切り取る範囲の大きさ。小数値を入れると親要素に対する割合で幅・高さを自動で求める。

これまでは Image.crop(box) で左上・右下の座標を指定しないといけなず、計算を自分でしないといけなかったが、それを割合指定だけで実現できるようにした。

### 実行例
```python
im = Image.new("RGB", (500, 500), "#2b26ad")
draw = ImageDraw.Draw(im)
draw.ellipse((125,125,375,375), "white")
cropped = im.crop_center((0.5, 0.5))
```

![before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601515/094dbe95-dc00-38d3-dbc2-2319333d18f8.png "切り抜き前")
切り抜き前の画像


![after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601515/7d2bcd90-2e88-06b6-b751-d84ec8049944.png "切り抜き後")
切り抜き後の画像



## 線形グラデーション画像
Image モジュール内に、指定した大きさ・方向・色の線形グラデーションの画像を作れる `linear_gradient_RGB(size, colors, start)` 関数を実装した。

- `size`: (width, height)-tuple. 画像サイズ
- `colors`: (start, finish)-tuple. 最初の色と最後の色
- `start`: "left", "top", "bottom" or "right". どの方向からグラデーションが始まるか

これまでは numpy など外部ライブラリが必要だったものを、 Pillow だけで完結できるようにした。
for文などを使わず、リスト内包表記などを用いてなるべく高速に実行できるように心がけた。

### 実行例
```python
image = Image.linear_gradient_RGB((500,500), ((43,38,173), (255,145,77)), "left")
image.save("gradation.png")
```

![グラデーション画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601515/3060438a-b0e5-4e29-b9d8-6143860ae8c3.png "グラデーション画像")
グラデーション画像



## グラデーションGIF
Image モジュール内に、
`gradation_gif(path, mode, size, colors, steps=30, optimize=True, duration=30, loop=1)` 関数を実装した。

- `path`: ファイルパス
- `mode`: 画像のモード（今はRGBのみ対応）
- `size`: (width, height)-tuple. 画像サイズ
- `colors`: tuple of RGB tuples. 変化する色。大量の指定もできる。
- `steps`: 色を何段階で変化させていくか。（全部で何フレームか）
- `optimize`: 最適化するかどうか。 デフォルトは True.
- `duration`: 各フレームを何ms表示するか。 デフォルトは30.
- `loop`: 何回ループするか。 デフォルトは1(ループなし)。 0だと無限ループ。

元々の `Image.save()` の引数工夫で gif を保存できたので、その引数を全て使えるようにした。

### 実行例
```python
Image.gradation_gif("gradation.gif", "RGB", (100, 100), 
    ((43,38,173), (255,145,77), (43,38,173)), 
    steps=45, optimize=False, duration=30, loop=0)
```

![グラデーションGIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2601515/f2595e78-b34c-577a-243d-2d7b5bf128c9.gif "グラデーションGIF")
グラデーションGIF



## JSON から layoutItem のクラスを作成するエンコーダー

JSONで指定したフォーマットで、画像や図形とその大きさ、色、配置などを指定すると列方向や行方向で等間隔になるように配置する機能を共同で製作した。
その際に、以下のようなクラスに変換する機能を実装した。

```python:LayoutItem.py
def new_layout_item(width, height, layout_type, items, color=None, path=None, shape=None, mode=None):
    """
    Create LayoutItem Object.
    """
    return LayoutItem(width, height, layout_type, items, color, path, shape, mode)

class LayoutItem:
    def __init__(self, width, height, layout_type, items, color=None, path=None, shape=None, mode=None):
        self.width = width
        self.height = height
        self.layout_type = layout_type # image | shape | row | column
        self.items = items # List of LayoutItem
        self.color = color
        self.path = path # path to image
        self.shape = shape # "rectangle" | "ellipse"
        self.mode = mode
```
`LayoutItem` 自身が `items` というプロパティに `LayoutItem` の配列を持つので、それを再起的に処理できるようにする必要があった。

例えば以下のようなjsonを処理できるようにした。
```json:layout1.json
{
    "layout_type": "column",
    "width": 900,
    "height": 900,
    "color": "#ffffff",
    "mode": "RGBA",
    "items": [
        {
            "layout_type": "row",
            "width": 900,
            "height": 280,
            "items": [
                {
                    "layout_type": "image",
                    "width": 260,
                    "height": 260,
                    "path": "Tests/images/argb-32bpp_MipMaps-1.png",
                    "items": []
                },
                {
                    "layout_type": "shape",
                    "width": 260,
                    "height": 260,
                    "color": "red",
                    "shape": "rectangle",
                    "items": []
                }
            ]
        }
    ]
}
```

このクラスを用いた詳細な処理についての説明は、別のメンバーに任せようと思う。


# おわりに
追加した主な機能は以上である。
これらの機能が実際にあると便利だなという声があったら、 Pillow の本家にも Pull Request を出してみようかなと考えている。