【Python3】フラクタルを描く方法
NumPy
Python3
Pillow
【Python3】フラクタルを描く方法

【Python3】フラクタルを描く方法

Python3でNumPyとPillowを用いてフラクタルを描く方法を紹介します。

目次
  1. 1.フラクタルとは
  2. 2.環境
  3. 3.ソースコード
  4. 4.解説
  5. 4-1.Pixelクラスの定義
  6. 4-2.IMGクラスの定義
  7. 4-3.Fractalクラスの定義
  8. 5.実例
  9. 5-1.ソースコード2
  10. 5-2.結果
  11. 6.まとめ

1.フラクタルとは

どれだけ拡大しても同じ模様が繰り返される図形のことです。一定の法則にしたがって図形を描くことでこのような図形を生成することが可能になります。

2.環境

  • Python 3.10.6
  • numpy 1.23.5
  • Pillow 9.3.0

3.ソースコード

fractal.py
#!/usr/bin/python3

import numpy as np
from PIL import Image

class Pixel:
    def __init__(self, r = 0, g = 0, b = 0) -> None:
        self.r = r
        self.g = g
        self.b = b
    
    def add(self, r = 0, g = 0, b = 0, a = 1.0):
        self.r = int(self.r * (1 - a) + r * a)
        self.g = int(self.g * (1 - a) + g * a)
        self.b = int(self.b * (1 - a) + b * a)


class IMG:
    def __init__(self, w = 1000, h = 1000) -> None:
        self.width = w
        self.height = h
        self.image = [[Pixel() for _ in range(w)] for _ in range(h)]
    
    def write(self, name="out.png"):
        arr = np.array([
            [[p.r, p.g, p.b] for p in pixels] for pixels in self.image
        ], dtype=np.uint8)
        img = Image.fromarray(arr)
        img.save(f"./{name}")


class Fractal(IMG):
    def __init__(self) -> None:
        super().__init__()
    
    def drawCircle(self, x=500, y=500, r=250, w=240):
        x0 = max(x - r, 0)
        x1 = min(x + r + 1, self.width)
        y0 = max(y - r, 0)
        y1 = min(y + r + 1, self.height)
        for i in range(x0, x1):
            for j in range(y0, y1):
                if w ** 2 < (i - x) ** 2 + (j - y) ** 2 <= r ** 2:
                    self.image[j][i].add(255, 255, 255, 0.5)

    def fractal(self, x=500, y=500, r=250, w=5):
        if r < 10 and w < 0:
            return
        self.drawCircle(x, y, r, r - w)
        self.fractal(x - r, y - r, int(r / 2), int(r / 2) - w)
        self.fractal(x + r, y - r, int(r / 2), int(r / 2) - w)
        self.fractal(x - r, y + r, int(r / 2), int(r / 2) - w)
        self.fractal(x + r, y + r, int(r / 2), int(r / 2) - w)


if __name__ == "__main__":
    img = Fractal()
    img.fractal()
    img.write()

4.解説

4-1.Pixelクラスの定義

fractal.py
class Pixel:
    def __init__(self, r = 0, g = 0, b = 0) -> None:
        self.r = r
        self.g = g
        self.b = b
    
    def add(self, r = 0, g = 0, b = 0, a = 1.0):
        self.r = int(self.r * (1 - a) + r * a)
        self.g = int(self.g * (1 - a) + g * a)
        self.b = int(self.b * (1 - a) + b * a)

RGB画像の画素はRGBの3値を持っているので、それを表現するためのクラスです。 addメソッドは新しい色を合成するためのメソッドです。

4-2.IMGクラスの定義

fractal.py
class IMG:
    def __init__(self, w = 1000, h = 1000) -> None:
        self.width = w
        self.height = h
        self.image = [[Pixel() for _ in range(w)] for _ in range(h)]
    
    def write(self, name="out.png"):
        arr = np.array([
            [[p.r, p.g, p.b] for p in pixels] for pixels in self.image
        ], dtype=np.uint8)
        img = Image.fromarray(arr)
        img.save(f"./{name}")

高さhピクセル、横wピクセルの画像を表現するためのクラスです。 imageメンバでPixelをh*wの二次元配列として保存しています。

writeメソッドは表現している画像をファイルに出力するためのメソッドです。 まずnp.arrayを使用して画像をndarrayに変換し、次にImage.fromarrayを使用して画像に保存できるようにしています。

4-3.Fractalクラスの定義

fractal.py
class Fractal(IMG):
    def __init__(self) -> None:
        super().__init__()
    
    def drawCircle(self, x=500, y=500, r=250, w=240):
        x0 = max(x - r, 0)
        x1 = min(x + r + 1, self.width)
        y0 = max(y - r, 0)
        y1 = min(y + r + 1, self.height)
        for i in range(x0, x1):
            for j in range(y0, y1):
                if (r - w) ** 2 < (i - x) ** 2 + (j - y) ** 2 <= r ** 2:
                    self.image[j][i].add(255, 255, 255, 0.5)

    def fractal(self, x=500, y=500, r=250, w=5):
        if r < 10:
            return
        self.drawCircle(x, y, r, r - w)
        self.fractal(x - r, y - r, int(r / 2), w)
        self.fractal(x + r, y - r, int(r / 2), w)
        self.fractal(x - r, y + r, int(r / 2), w)
        self.fractal(x + r, y + r, int(r / 2), w)

Fractalを描くためのクラスで、IMGクラスを継承しています。

drawCircleメソッドは(x,y)を中心とした半径rの円を、wの幅で描画するためのメソッドです。 まず円形がぴったり収まる辺の長さが2rの正方形を計算します。 次にその範囲の画素を順にみていき、中心からの距離が描画範囲に入っていれば白色を加えます。

fractalメソッドは(x,y)を中心とした半径rの円をwの幅で描画したあと、上下左右に半径r/2の円をwの幅で描画するメソッドです。 半径が10未満になるまで自身を再帰的に呼び出す再帰関数となっているので、同様の模様が繰り返されるフラクタルとなります。

5.実例

5-1.ソースコード2

fractal.py
if __name__ == "__main__":
    img = Fractal()
    img.fractal()
    img.write()

5-2.結果

fractal_result_01

6.まとめ

簡単なコードでフラクタルを描画することができました。

今回は円形を使ったので、中心からの距離を比較するだけで簡単に描画できましたが、多角形などは少し複雑な処理が必要となります。

本格的な画像生成を行うには自作クラスでは遠回りになってしまうので、opencvやmatplotlibなどのライブラリを利用すると効率よく行うことができます。

機会があればそちらの方法も解説したいと思います。

最後までご覧いただきありがとうございました。