Pillowで図形のアニメーション作成

[Pillow](https://pypi.org/project/pillow
)と[Matplotlib](https://matplotlib.org)を使ってアニメーションをやってみました。
作成する図形はマンデルブロ集合というフラクタル図形にしました。

VSCodeのJupyter Notebookでアニメーション表示することができます。
適当なコードですみませんが、多少コメントを付けて貼ります。

:::note warn
アニメーション＆フラクタル図形の計算で処理が重めです。N（画素数）とframes（フレーム数）を減らすと軽くなります。
:::


```python
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
from IPython import display
from matplotlib import rcParams
from matplotlib.animation import ArtistAnimation

rcParams['animation.embed_limit'] = 2**128 # アニメーションのサイズ上限を変更

# マンデルブロ集合のパラメータ
N = 1000
epsilon = 0.1
center = [-0.53, 0.613]

frames = 100 # フレーム数を100に制限

def fn(z, c):
    return z*z + c

def convergence(c):
    z = 0.0
    i = 0
    while i < 100:
        z = fn(z, c)
        if abs(z) > 2:
            return i
        i += 1
    return i

def display_mandelbrot_set():
    
    grid = np.zeros((N, N), dtype=np.uint8)
    points_x = np.linspace(center[0]-epsilon, center[0]+epsilon, N)
    points_y = np.linspace(center[1]-epsilon, center[1]+epsilon, N)
    
    image = Image.new("RGBA", (N, N), tuple([0, 0, 0, 0]))
    plt.axis("off")
    plt.tick_params(labelbottom=False, labelleft=False, labelright=False, labeltop=False)
    fig = plt.figure(figsize=(15, 15))
    im = plt.imshow(np.array(image), vmin=0, vmax=100, cmap="Blues_r") # 初期値
    ims = [[im]] # 画像(アニメーションのフレーム)のリスト
    frate = N * N / frames
    i = 0
    for n, rec in enumerate(points_x):
        for m, imc in enumerate(points_y):
            c = rec + imc*1J
            grid[m, n] = convergence(c) # 画像（フレーム）に値を入れる
            if i % frate == 0: # アニメーションのフレーム数を100に制限
                im = plt.imshow(np.array(grid), vmin=0, 
                                vmax=100, cmap="Blues_r",
                                animated=True) # 画像の作成
                ims.append([im]) # 画像を追加
            i += 1
    
    ani = ArtistAnimation(fig, ims, interval=0.1, blit=True, repeat_delay=100)
    html = display.HTML(ani.to_jshtml())
    display.display(html) # Jupyter NotebookのためにHTML形式で表示
    ani.save("mandelbrot_set.gif", writer="pillow") # GIF形式で保存
    plt.close(fig)
   
display_mandelbrot_set()
```

![mandelbrot_set.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490241/5cefbfb7-094a-36d3-0970-fb3d34879e6a.gif)

アニメーション作成処理が重くなってしまったのでフレーム数は100に絞りました。
1ピクセルずつ計算する過程をアニメーションにしている感じになりました。

図形が動くようなアニメーションも作ってみます。

```python
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
from IPython import display
from matplotlib import rcParams
from matplotlib.animation import ArtistAnimation

rcParams['animation.embed_limit'] = 2**128
solutions = np.array([2.7693, 0.11535-0.58974J, 0.11535+0.58974J])
N = 300
epsilon = 0.1
center = [-0.530, 0.613]

frames = 100

def fn(z, c):
    return z*z + c

def convergence(c):
    z = 0.0
    i = 0
    while i < 100:
        z = fn(z, c)
        if abs(z) > 2:
            return i
        i += 1
    return i

def display_mandelbrot_set(center:list) -> np.array:
    grid = np.zeros((N, N), dtype=np.uint8)
    points_x = np.linspace(center[0]-epsilon, center[0]+epsilon, N)
    points_y = np.linspace(center[1]-epsilon, center[1]+epsilon, N)
    for n, rec in enumerate(points_x):
        for m, imc in enumerate(points_y):
            c = rec + imc*1J
            grid[m, n] = convergence(c)
    return grid

if __name__ == '__main__':
    image = Image.new("RGBA", (N, N), tuple([0, 0, 0, 0]))
    plt.axis("off")
    plt.tick_params(labelbottom=False, labelleft=False, labelright=False, labeltop=False)
    fig = plt.figure(figsize=(15, 15))
    im = plt.imshow(np.array(image), vmin=0, vmax=100, cmap="Blues_r")
    ims = [[im]]
    for frame in range(frames):
        grid = display_mandelbrot_set(center) # 画像（フレーム）を取得
        im = plt.imshow(np.array(grid), vmin=0, vmax=100, cmap="Blues_r", animated=True)
        ims.append([im]) # 画像を追加
        center = [x - 0.001 for x in center] # 中心を少しだけずらす
    ani = ArtistAnimation(fig, ims, interval=100, blit=True, repeat_delay=1000)
    html = display.HTML(ani.to_jshtml())
    display.display(html)
    ani.save("mandelbrot_set.gif", writer="pillow")
    plt.close(fig)
```

![mandelbrot_set.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490241/6df7e1ee-d4a1-0fcf-fe30-821cdb8c0ece.gif)


これは中心`center`を少しずつ動かしているだけですが、アニメーションらしくなりました。

matplotlib                    3.9.1
pillow                        10.4.0

---

参考）マンデルブロ集合のコードをお借りました

https://qiita.com/physics303/items/5d4fb4708fe72ba48fb0