9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-07-08

本記事について

Pythonのレトロゲームエンジン"pyxel"を使ったブロッコリーのジェネラティブアートについてまとめたもの。
2023.07.08開催のJackasson Fes 2023 in Summerにて発表したもの。

Jackassonとは?

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

誰のための記事?

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

Pyxcelについて

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

Pyxcelを使用した背景

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

作ったもの

不完全なブロッコリーっぽいものが出来上がった。
形状はこれから精度を上げてブロッコリーに近づけていく。

手順

1.pyxelライブラリから必要な機能をインポート。
mathモジュールからsin、cos、sqrt、piの関数をインポートしてrandomモジュールも使用する。

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を初期化するための関数で、引数に画面の幅と高さ、フレームレートを指定。

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()関数を使ってランダムな値を表示。

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/

    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のイベントループを開始し、更新関数と描画関数を繰り返し実行する。

run(update, draw)

6.コード全体

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

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

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?