Pythonならクリスマスツリーを簡単に描けるはず

こんにちは！

12月は季節柄、クリスマスツリーを飾る家庭が多いと思います。
我が家でもツリーを飾っているのですが、片付けが面倒、と思うこともしばしば。

そこで今回は、Pythonの豊富なライブラリを利用して、クリスマスツリーを描いてみることにしました。
ツリーを出さなくていいレベルになるか分かりませんが、やってみます。

# やりたいこと
- クリスマスツリーを描く
- ライトをチカチカ光らせる
- 雪を降らせる
- GIFアニメーションとして保存する

# 環境構築
Pythonの仮想環境を作ります。
```bash
python -m venv tree-art
cd tree-art
```
ディレクトリに移動した直後の状態
```bash
$ ls
bin/		include/	lib/		pyvenv.cfg
```
仮想環境の有効化
```bash
source bin/activate
```
必要なライブラリのインストール
```bash
pip install Pillow
```
# Pillowとは
Pythonに画像処理機能を追加するライブラリです。
様々な画像形式に対応しており、読み込んだ画像の編集もできます。

# コード
```python:main.py
import random
import math
from PIL import Image, ImageDraw

frames = []
number_of_frames = 20

# RGBカラーコードで明るい色を定義
led_colors = [
    (255, 128, 128),    # 赤
    (0, 255, 128),      # 緑
    (0, 128, 255),      # 青
    (128, 128, 255),    # 紫
    (255, 255, 64),     # 黄
]

# 星の描画
def draw_star(draw, center, size, color):
    x, y = center
    outer = size
    inner = size / 2.5
    points = []
    angle = 0
    for i in range(5):
        outer_x = x + outer * math.cos(math.radians(angle))
        outer_y = y - outer * math.sin(math.radians(angle))
        points.append((outer_x, outer_y))
        angle += 36
        inner_x = x + inner * math.cos(math.radians(angle))
        inner_y = y - inner * math.sin(math.radians(angle))
        points.append((inner_x, inner_y))
        angle += 36
    draw.polygon(points, fill=color)

# 画像の設定
width, height = 500, 700

for frame_number in range(number_of_frames):
    # 画像と描画オブジェクトの生成
    image = Image.new('RGB', (width, height), color='RGB(0, 0, 110)')
    draw = ImageDraw.Draw(image)

    # 背景に雪を降らせる
    for _ in range(200):  # 雪の数
        snow_x = random.randint(0, width)
        snow_y = random.randint(0, height)
        snow_size = random.randint(2, 5)
        draw.ellipse([snow_x - snow_size, snow_y - snow_size,
                      snow_x + snow_size, snow_y + snow_size], fill="RGB(256, 256, 256)")

    # ツリーの描画
    tree_color = 'RGB(0, 88, 88)'
    tree_positions = [(250, 450), (250, 350), (250, 250)]
    # ツリーの各層を描画
    for i, (x, y) in enumerate(tree_positions):
        size = 250 - i * 50
        draw.polygon([x - size, y, x, y - size * 1.5, x + size, y], fill=tree_color)

    # 雪を積もらせる
    draw.rectangle([0, 550, width, height], fill='RGB(256, 256, 256)')

    # ツリーの幹の描画
    draw.rectangle([230, 450, 270, 550], fill='RGB(101, 67, 33)')

    total_ornaments = 0
    for i, (x, y) in enumerate(tree_positions):
        size = 250 - i * 50
        ornaments_count = 50 - i * 3 * 5
        total_ornaments += ornaments_count
        for _ in range(ornaments_count):
            # ツリーの層の形に基づいて装飾を配置するための計算
            rand_y = random.uniform(y - size * 1.5, y)
            # Y座標に基づいてX座標の最大オフセットを計算
            max_x_offset = (size * 1.5 - (y - rand_y)) / 1.5
            rand_x = random.uniform(x - max_x_offset, x + max_x_offset)
            ornament_color = random.choice(led_colors)
            ornament_size = random.randint(3, 8)
            draw.ellipse((rand_x - ornament_size, rand_y - ornament_size,
                          rand_x + ornament_size, rand_y + ornament_size),
                          fill=ornament_color)

    # 星の描画
    draw_star(draw, (250, 50), 40, 'gold')

    # 生成したフレームをリストに追加
    frames.append(image)

# GIFアニメーションとして保存
frames[0].save('christmas_tree_animation.gif',
               save_all=True,
               append_images=frames[1:],
               duration=1500,  # フレームごとの表示時間(ミリ秒)
               loop=0)  # アニメーションを無限ループ
```

# 実行結果
Pythonで描いたツリーはこちらです！
今回はこれが限界でございます🎅
![christmas_tree_animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407770/fe217dc7-db90-007b-5815-ab4da43674f4.gif)
これはPythonではなく、作成者のデザインセンスの問題ですね。

# まとめ
今回は、Pythonでクリスマスツリーを描いてみました🎄
自宅では今年もツリーを出すことになりそうですが、いつの日か、本物のツリーがいらなくなるくらいの精度に仕上げようと思います🎅

みなさんも一緒に頑張りましょう！