Python使ったブロッコリーのジェネラティブアート

# 本記事について
Pythonのレトロゲームエンジン"pyxel"を使ったブロッコリーのジェネラティブアートについてまとめたもの。
2023.07.08開催の[Jackasson Fes 2023 in Summer](https://jackassons.connpass.com/event/284910/)にて発表したもの。

## Jackassonとは？
どんなにくだらないJackass!なものでも自分の表現したいものを作る！発表する！をコンセプトに 自身の発想の実現と技術への挑戦するためのLT大会。
当日はG's ACADEMYの卒業生である[VJ石井さん](https://twitter.com/horralic)が登場するなど、大変盛り上がりました！

## 誰のための記事？
- pyxcelに興味がある人
- pythonを使ってアートを作ってみたい人
- ブロッコリーが好きな自分のため

## Pyxcelについて
Pythonのレトロゲームエンジン。ドット絵スタイルのゲームが作れる。
画面サイズ 256 x 256 ドット、使える色は16色。
(参考:https://github.com/kitao/pyxel/blob/main/docs/README.ja.md)

## Pyxcelを使用した背景
ドットの散らばりがブロッコリーの花蕾に似ていたため。
単純な2次元のイラストではなく、座標を使ってブロッコリーを表現しようと感得たため。

## 作ったもの
不完全なブロッコリーっぽいものが出来上がった。
形状はこれから精度を上げてブロッコリーに近づけていく。
<div><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1695131/478cb25b-3e29-364e-ed41-a72151e42d76.png" width=33%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1695131/56b00fc8-68b6-fcb3-877e-9d8ca9f52511.png" width=33%><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1695131/cc0fe4d9-2085-de01-84ec-1e5f1fd3316f.png" width=33%></div>

## 手順
1.pyxelライブラリから必要な機能をインポート。
mathモジュールからsin、cos、sqrt、piの関数をインポートしてrandomモジュールも使用する。
```python
from pyxel import *
from math import sin, cos, sqrt, pi
import random
```
2.画面のサイズや背景色、点の色などの設定。
w = 256 部分で画面のサイズの設定を行う。wは画面の幅と高さ、background_colorは背景の色、dot_colorは描画する点の色を示す。ブロッコリーを表現したいので11の緑を使用。
dot_countは描画する点の数、p_minとp_maxは座標の範囲。
init(w, w, fps=1)はpyxelを初期化するための関数で、引数に画面の幅と高さ、フレームレートを指定。
```python
w = 256               
background_color = 0
dot_color = 11
dot_count = 20000
p_min, p_max = -3, 3
init(w, w, fps=1)
```
3.描画関数を作成
cls(background_color)によって背景を指定。
xとyは描画する点の座標を表す変数で、最初は0で初期化。
aからdまでの4つの変数に、p_minからp_maxの範囲でランダムな値を代入し、text()関数を使ってランダムな値を表示。
```python
def draw():
    cls(background_color)
    x, y = 0, 0
    a, b, c, d = [round(random.uniform(p_min, p_max), 2) for _ in range(4)]
    text(10, 10, "a="+str(a)+" b="+str(b)+" c="+str(c)+" d="+str(d), 7)
```
4.ブロッコリーの形状を描画
その後のforループでは、dot_count回の繰り返しで点を描画し、点の座標でsinとcosを使用し楕円の方程式を使って座標を変換。
ランダムに角度を選択し、最後に、座標にオフセットを適用して点を描画。    
Peter de Jong Attractor という、ランダム生成したa,b,c,d の値と下記の式を用いて色々な形を形成する。
　Xn+1 = sin(a * Yn) - cos(b * Xn)
　Yn+1 = sin(c * Xn) - cos(d * Yn)
引用：http://paulbourke.net/fractals/peterdejong/
```python
    for _ in range(dot_count):
        x, y = sin(a*y) - cos(b*x), sin(c*x) - cos(d*y)
        r = sqrt(x**2 + y**2) * 20  # 点の距離に基づいて半径を設定（大きめの値）
        
        # 楕円の方程式に基づいて座標を変換
        # ex = x * 0.8  # 楕円のx方向の拡大率
        ex = x * 0.8
        ey = y  # 楕円のy方向の拡大率
        theta = random.uniform(0, 2 * pi)  # ランダムな角度を選択
        px = ex * w / 5 + 128 + r * cos(theta)  # 円の中心からのオフセットを適用
        py = ey * w / 5 + 140 + r * sin(theta) + 60  # 上方向にオフセット
        pset(int(px), int(py), dot_color)
```
5.プログラムの実行とイベントループ
run()関数がpyxelのイベントループを開始し、更新関数と描画関数を繰り返し実行する。
```python
run(update, draw)
```
6.コード全体
```python
from pyxel import *
from math import sin, cos, sqrt, pi
import random

w = 256 
background_color = 0
dot_color = 11
dot_count = 20000
p_min, p_max = -3, 3
init(w, w, fps=1)

def draw():
    cls(background_color)
    x, y = 0, 0
    a, b, c, d = [round(random.uniform(p_min, p_max), 2) for _ in range(4)]
    text(10, 10, "a="+str(a)+" b="+str(b)+" c="+str(c)+" d="+str(d), 7)
    
    for _ in range(dot_count):
        x, y = sin(a*y) - cos(b*x), sin(c*x) - cos(d*y)
        r = sqrt(x**2 + y**2) * 20
        
        ex = x * 0.8
        ey = y 
        theta = random.uniform(0, 2 * pi) 
        px = ex * w / 5 + 128 + r * cos(theta)
        py = ey * w / 5 + 140 + r * sin(theta) + 60 
        pset(int(px), int(py), dot_color)

def update():
    pass

run(update, draw)
```
## 苦戦したところ
ブロッコリーらしさを追求するため、ランダムに生成されるモデルの中でも形状が最もブロッコリーに近しいものの値を、進化戦略を使ってa, b, c, dの値を最適化したが、逆に形状が崩れたり、過度に拡大され、うまくいかなかった。ただ、ブロッコリーを実物ではなく違った表現で見せたかったため、少しでも形状を本物に近づけられたところは良かったと思う。

## 参考にした記事
[Pyxelの環境構築@mac](https://qiita.com/yukiny/items/e5f65ed60e314362954c)

以上。
最後までお読みいただきありがとうございました。