1.フラクタルとは
どれだけ拡大しても同じ模様が繰り返される図形のことです。一定の法則にしたがって図形を描くことでこのような図形を生成することが可能になります。
2.環境
- Python 3.10.6
- numpy 1.23.5
- Pillow 9.3.0
3.ソースコード
#!/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クラスの定義
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クラスの定義
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クラスの定義
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
if __name__ == "__main__":
img = Fractal()
img.fractal()
img.write()
5-2.結果
6.まとめ
簡単なコードでフラクタルを描画することができました。
今回は円形を使ったので、中心からの距離を比較するだけで簡単に描画できましたが、多角形などは少し複雑な処理が必要となります。
本格的な画像生成を行うには自作クラスでは遠回りになってしまうので、opencvやmatplotlibなどのライブラリを利用すると効率よく行うことができます。
機会があればそちらの方法も解説したいと思います。
最後までご覧いただきありがとうございました。