"BOKU"のITな日常

BOKUが勉強したり、考えたことを頭の整理を兼ねてまとめてます。

Pythonでグラフィックス:有名な「バーンスタインのシダの葉」を描くクラス

f:id:arakan_no_boku:20210912183619p:plain

目次

バーンスタインのシダの葉

今回は、Pythonで「バーンスタインのシダの葉」を実装します。

すごい有名なやつです。

描いた図形はこうなります。

f:id:arakan_no_boku:20210918202800p:plain

描画クラスのソースコード

クラスのソースコードです。

グラフの描画機能を使って描くので、matplotlibが必要です。

タイトルを日本語にしているので、日本語フォントの導入も必要です。

arakan-pgm-ai.hatenablog.com

class_graphics.py

from matplotlib import pyplot as plt
import random


class Fern():
    def __init__(self):
        self.__tx = [0]
        self.__ty = [0]
        self.__title = "バーンスタインのシダの葉を描く"

    def __tran_1(self, p):
        x = p[0]
        y = p[1]
        x1 = 0.85 * x + 0.04 * y
        y1 = -0.04 * x + 0.85 * y + 1.6
        return x1, y1

    def __tran_2(self, p):
        x = p[0]
        y = p[1]
        x1 = 0.2 * x - 0.26 * y
        y1 = 0.23 * x + 0.22 * y + 1.6
        return x1, y1

    def __tran_3(self, p):
        x = p[0]
        y = p[1]
        x1 = -0.15 * x + 0.28 * y
        y1 = 0.26 * x + 0.24 * y + 0.44
        return x1, y1

    def __tran_4(self, p):
        # x = p[0]
        y = p[1]
        x1 = 0
        y1 = 0.16 * y
        return x1, y1

    def __get_index(self):
        prob = [0.85, 0.07, 0.07, 0.01]
        r = random.random()
        c = 0
        sump = []
        for p in prob:
            c += p
            sump.append(c)
        for item, sp in enumerate(sump):
            if(r <= sp):
                return item
        return len(prob) - 1

    def __tran(self, p):
        trans = [self.__tran_1, self.__tran_2, self.__tran_3, self.__tran_4]
        tindex = self.__get_index()
        t = trans[tindex]
        x, y = t(p)
        return x, y

    def __draw(self, n):
        x1 = 0
        y1 = 0
        for i in range(n):
            x1, y1 = self.__tran((x1, y1))
            self.__tx.append(x1)
            self.__ty.append(y1)
        return self.__tx, self.__ty

    def draw(self, n=10000):
        x, y = self.__draw(n)
        plt.plot(x, y, '.')
        plt.title(self.__title)
        plt.show()

 

ソースコードの補足説明

メインの処理は「__draw()」です。

 def __draw(selfn):
        x1 = 0
        y1 = 0
        for i in range(n):
            x1, y1 = self.__tran*1
            self.__tx.append(x1)
            self.__ty.append(y1)
        return self.__tx, self.__ty

座標を示す「x1、y1」を更新しながら、「n」で指定した回数繰り返します。

nは、デフォルトで10000回にしてます。

ここの数字を変えると、図形の密度が変わります。

 

x1,y1を更新する処理は「__tran()」です。

    def __tran(selfp):
        trans = [self.__tran_1, self.__tran_2, self.__tran_3, self.__tran_4]
        tindex = self.__get_index()
        t = trans[tindex]
        x, y = t(p)
        return x, y

pというタプルで(x1,y1)を受け取り,それを引数にして「tran_1」「tran_2」「tran_3」「tran_4」のいずれかのメソッドをよびだします。

 

どのメソッドを呼び出すかを決めるのは「__get_index()」です。

    def __get_index(self):
        prob = [0.850.070.070.01]
        r = random.random()
        c = 0
        sump = []
        for p in prob:
            c += p
            sump.append(c)
        for item, sp in enumerate(sump):
            if(r <= sp):
                return item
        return len(prob) - 1

prob=[0.85,0.07,0.07,0.01]のリストを順番に加算した値と、乱数を比較して、乱数が合計値よりも小さかったら、そのインデックスを返します。

この数字を変更すると、できあがる図形の形が変わります。

シダの葉っぱのような図形にするには、この値が適切といわれてます。

 

入力された「x1とy1」の変換の仕方が4パターンあります。

1パターン目

x1 = 0.85*x + 0.04*y

y1 = -0.04*x + 0.85*y + 1.6

2パターン目

x1 = 0.2*x - 0.26*y

y1 = 0.23*x + 0.22*y + 1.6

3パターン目

x1 = -0.15*x + 0.28*y

y1 = 0.26*x + 0.24*y + 0.44

4パターン目

x1 = 0

y1 = 0.16*y

このパターンをランダムに繰り返していくことで、図形を描き出していくわけです。 

実行方法です

ソースはこんな感じです。

from class_graphics import Fern

Fern().draw()

デフォルトで実行したのが冒頭にのせた

f:id:arakan_no_boku:20210918202800p:plain

です。

これを1000回くらいにすると。

from class_graphics import Fern

Fern().draw(1000)

f:id:arakan_no_boku:20210918204810p:plain

スカスカになります(笑)

どのくらいが最適なのか?は、好みですかね。

おまけの情報

こちらの記事と見比べてもらうとわかると思うのですが.

arakan-pgm-ai.hatenablog.com

今回の「シダの葉」を描くクラスと、「シェルピンスキーのギャスケット」を描くクラスは、x1・y1の変換する部分の比率と、__get_index()のリストの数値が違うだけで、ほぼ、構造は同じです。

なので。

この構造のままで、いろいろと数値を変えrると他の図形も描けそうではあります。

いちおう、参考情報です。

ではでは。

*1:x1, y1