fractale/source/main.py

512 lines
17 KiB
Python
Raw Permalink Normal View History

import cmath
2019-02-26 23:43:41 +01:00
import copy
from math import atan, cos, sin, pi
from PIL import Image, ImageDraw
"""
A lib to draw fractals on pillow image
>>> img = Image.new('RGB', (5000, 5000), (0, 0, 0))
>>> figures = Figures(im=img)
>>> figures.von_koch_curve_flake((2500, 2500), 2000,6)
2019-02-26 15:47:18 +01:00
>>> img.save("test.bmp")
"""
class State:
"""State of Lsystem"""
width: int
2019-02-26 23:43:41 +01:00
color: tuple
angle: int
y: int
x: int
def __init__(self):
"""Initialisation of state
>>> State().x
0
>>> State().y
0
>>> State().angle
0
>>> State().color
(255, 255, 255)
>>> State().width
0"""
self.x = 0
self.y = 0
self.angle = 0
self.color = (255, 255, 255)
self.width = 0
2019-02-26 23:43:41 +01:00
def __str__(self):
return str(self.x)
def __repr__(self):
return self.__str__()
class Lsystem(ImageDraw.ImageDraw):
"""Draw a L system"""
state: State
2019-02-26 23:43:41 +01:00
states: list
def dragon(self, size, recursions, color=None, width=0):
"""Trace Dragon curve
:param size: Lenght of a segment
:param recursions: number of recursions
:param color: color of drawing
:param width: width of drawing
:type size: float
:type recursions: int
:type color: tuple
:type width: int"""
self.draw_l("FX",
{"X": "X+YF+",
"Y": "-FX-Y"},
{"-": self.left(pi / 2),
'+': self.right(pi / 2),
"F": self.forward(size),
"Y": self.nothing(),
"X": self.nothing()},
recursions,
color, width)
def sierpinski_triangle(self, size, recursions, color=None, width=0):
"""Draw the sierpinski triangle
:param size: Lenght of a segment
:param recursions: number of recursions
:param color: color of drawing
:param width: width of drawing
:type size: float
:type recursions: int
:type color: tuple
:type width: int"""
self.draw_l("F-G-G",
{"F": "F-G+F+G-F",
"G": "GG"},
{"-": self.left(pi * 2 / 3),
'+': self.right(pi * 2 / 3),
"F": self.forward(size),
"G": self.forward(size)},
recursions,
color, width)
def fractal_plant(self, size, recursions, color=None, width=0):
"""Draw the fractal plant
:param size: Lenght of a segment
:param recursions: number of recursions
:param color: color of drawing
:param width: width of drawing
:type size: float
:type recursions: int
:type color: tuple
:type width: int"""
self.draw_l("X",
{"X": "F+[[X]-X]-F[-FX]+X",
"F": "FF"},
{"-": self.left(pi * 5 / 36),
'+': self.right(pi * 5 / 36),
"F": self.forward(size),
"X": self.nothing(),
"[": self.save(),
"]": self.restore()},
recursions,
color, width)
def koch_curve_right_angle(self, size, recursions, color=None, width=0):
"""Draw koch curve with right angle
:param size: Lenght of a segment
:param recursions: number of recursions
:param color: color of drawing
:param width: width of drawing
:type size: float
:type recursions: int
:type color: tuple
:type width: int"""
self.draw_l("F",
{"F": "F+F-F-F+F"},
{"-": self.left(pi / 2),
'+': self.right(pi / 2),
"F": self.forward(size),
"[": self.save(),
"]": self.restore()},
recursions,
color, width)
def fractal_binary_tree(self, size, recursions, color=None, width=0):
"""Draw fractal binary tree
:param size: Lenght of a segment
:param recursions: number of recursions
:param color: color of drawing
:param width: width of drawing
:type size: float
:type recursions: int
:type color: tuple
:type width: int"""
self.draw_l("0",
{"1": "11",
"0": "1[-0]+0"},
{"-": self.left(pi / 2),
'+': self.right(pi / 2),
"0": self.forward(size),
"1": self.forward(size),
"[": self.save(),
"]": self.restore()},
recursions,
color, width)
def __init__(self, *args, **kwargs):
"""Initialisation
Parameters are the same than ImageDraw.__init__"""
super().__init__(*args, **kwargs)
self.states = []
self.state = State()
def set_pos(self, x, y):
"""Set position of pen
:param x: x coordinate
:param y: y coordinate
:type x: float
:type y: float"""
self.state.x = x
self.state.y = y
def _right(self, angle):
"""Turn pen to right of angle
:param angle: Angle to rotate
:type angle: float
"""
self.state.angle -= angle
def _left(self, angle):
"""Turn pen to left of angle
:param angle: Angle to rotate
:type angle: float
"""
self.state.angle += angle
def _forward(self, distance):
"""Forward pen of distance
:param distance: Distance to forward
:type distance: float
"""
x_2: float = (distance * cos(self.state.angle)) + self.state.x
y_2: float = (distance * sin(self.state.angle)) + self.state.y
self.line(((self.state.x, self.state.y), (x_2, y_2)), self.state.color, self.state.width)
self.state.x, self.state.y = x_2, y_2
def _backward(self, distance):
"""Backward pen of distance
:param distance: Distance to backward
:type distance: float
"""
self._forward(-distance)
def _save(self):
"""Save state of pen"""
2019-02-26 23:43:41 +01:00
self.states.append(copy.deepcopy(self.state))
def _restore(self):
"""Restore last pen state"""
2019-02-26 23:43:41 +01:00
self.state = self.states.pop()
2019-02-26 15:47:18 +01:00
def draw_l(self, start, replacement, constants, nb_recursive, color=(255, 255, 255), width=0):
"""Draw a L system
:param start: Axiome
:param replacement: Dictionary which contain replacement values (F->F+F-F-F+F)
:param constants: Dictionary which contain all elements with there function
:param nb_recursive: Number of recursion
:param color: Color to use for the drawing
:param width: The line width, in pixels
:type start: str
:type replacement: dict
:type constants: dict
:type nb_recursive: int
2019-02-26 23:43:41 +01:00
:type color: tuple
:type width: int
"""
self.state.color = color
self.state.width = width
for i in range(nb_recursive):
2019-02-26 23:43:41 +01:00
newstart = ""
for carac in start:
if carac in replacement:
newstart += replacement[carac]
else:
newstart += carac
start = newstart
for item in start:
constants[item]()
def right(self, angle):
"""Return a lambda function which make pen turning of angle radians to right
:param angle: Angle to build function
:type angle: float
:return: lambda function to make pen turning right
:rtype: lambda"""
return lambda: self._right(angle)
def left(self, angle):
"""Return a lambda function which make pen turning of angle radians to left
:param angle: Angle to build function
:type angle: float
:return: lambda function to make pen turning left
:rtype: lambda"""
return lambda: self._left(angle)
def forward(self, distance):
"""Return a lambda function which make pen forward of distance
:param distance: Distance to build function
:type distance: float
:return: lambda function to make pen forward
:rtype: lambda"""
return lambda: self._forward(distance)
def backward(self, distance):
"""Return a lambda function which make pen backward of distance
:param distance: Distance to build function
:type distance: float
:return: lambda function to make pen backward
:rtype: lambda"""
return lambda: self._backward(distance)
def save(self):
"""Return a lambda function which save state of pen
:return: lambda function to save pen state
:rtype: lambda"""
return lambda: self._save()
def restore(self):
"""Return a lambda function which restore state of pen
:return: lambda function to restore pen state
:rtype: lambda"""
return lambda: self._restore()
2019-02-26 23:43:41 +01:00
def nothing(self):
return lambda: None
class Figures(ImageDraw.ImageDraw):
"""A lot of function to create some well-know shapes"""
@staticmethod
def point_to_complex(point):
"""Transform tuple to complex
:param point: Point to convert
2019-02-26 23:43:41 +01:00
:type point: tuple
:return: Complex representation of point
:rtype: complex"""
return complex(point[0], point[1])
@staticmethod
def complex_to_point(point):
"""Transform tuple to complex
:param point: Point to convert
:type point: complex
2019-02-26 19:41:10 +01:00
:return: tuple representation of point
2019-02-26 23:43:41 +01:00
:rtype: tuple"""
return point.real, point.imag
def rotation(self, point, center=0j, angle=0):
"""Rotate point in complex plane
:param point: point (or list of point) to rotate
:type point: tuple or complex
:param center: center of rotation
:type center: tuple or complex
:param angle: angle of rotation
:type angle: float
:return: Rotated point (or list of rotated points)
2019-02-26 23:43:41 +01:00
:rtype: tuple or list of tuples"""
if type(center) != complex:
center = self.point_to_complex(center)
if type(point) == list:
return [self.rotation(p, center, angle) for p in point]
if type(point) != complex:
point = self.point_to_complex(point)
return self.complex_to_point(cmath.exp(complex(0, 1) * angle) * (point - center) + center)
def homothety(self, point, center=0j, size=0):
"""Homothety of point in complex plane
:param point: point (or list of point) to make homothety
:type point: tuple or complex
:param center: center of homothety
:type center: tuple or complex
:param size: size of homothety
:type size: float
:return: Homothety of point (or list of homothety of points)
2019-02-26 23:43:41 +01:00
:rtype: tuple or list of tuples"""
if type(center) != complex:
center = self.point_to_complex(center)
if type(point) == list:
return [self.homothety(p, center, size) for p in point]
if type(point) != complex:
point = self.point_to_complex(point)
return self.complex_to_point(size * (point - center) + center)
def translation(self, point, vect):
"""Translate point in complex plane
:param point: point (or list of point) to translate
:type point: tuple or complex
:param vect: vector of translation
:type vect: tuple or complex
:return: Translated point (or list of translated points)
2019-02-26 23:43:41 +01:00
:rtype: tuple or list of tuples"""
if type(vect) != complex:
vect = self.point_to_complex(vect)
if type(point) == list:
return [self.translation(p, vect) for p in point]
if type(point) != complex:
point = self.point_to_complex(point)
return self.complex_to_point(point + vect)
def blanc_manger(self, origin, finish, iterations, color=None, width=0):
"""Trace blanc manger curve
:param origin: coordinate of the starting point
:param finish: coordinate of the ending point
:param iterations: iterations for the drawings
:param color: color to use for the lines
:param width: the line width, in pixels
:type origin: tuple
:type finish: tuple
:type iterations: int
:type color: tuple
:type width: int"""
2019-02-26 15:47:18 +01:00
lenght_theoric = 2 ** iterations
length = (((origin[0] - finish[0]) ** 2 + (origin[1] - finish[1]) ** 2) ** 0.5)
def sawtooth(x):
d = x - int(x)
if d <= 1 / 2:
return d
return 1 - d
def blanc_manger(x, iterations=1):
return sum([1 / (2 ** k) * sawtooth((2 ** k) * x) for k in range(iterations)])
points = [
((i / lenght_theoric) * length,
(blanc_manger(i / lenght_theoric, iterations)) * length)
for i in range(lenght_theoric + 1)]
if finish[0] == origin[0]:
2019-03-04 15:33:35 +01:00
angle = pi / 2
else:
angle = atan((finish[1] - origin[1]) / (finish[0] - origin[0]))
points = self.rotation(points, (0, 0), angle)
points = self.translation(points, origin)
self.line(points, color, width)
2019-02-26 15:47:18 +01:00
def von_koch_curve_flake(self, origin, radius, iterations, angle=0, color=None, width=0):
2019-02-27 02:15:36 +01:00
"""Draw the von koch flake on image.
:param origin: coordinate of the center of circumscribed circle of main triangle
:param radius: radius of circumscribed circle of main triangle
:param iterations: iterations for the drawings
:param angle: rotation of main triangle
:param color: color to use for the lines
:param width: the line width, in pixels
:type radius: float
:type origin: tuple
:type iterations: int
:type angle: float
:type color: tuple
:type width: int"""
angle = angle + pi / 2
summit_1 = (origin[0] + cos(angle) * radius, origin[1] + sin(angle) * radius)
summit_2 = (origin[0] + cos(angle + 2 / 3 * pi) * radius, origin[1] + sin(angle + 2 / 3 * pi) * radius)
summit_3 = (origin[0] + cos(angle - 2 / 3 * pi) * radius, origin[1] + sin(angle - 2 / 3 * pi) * radius)
self.von_koch_curve(summit_2, summit_1, iterations, color, width)
self.von_koch_curve(summit_3, summit_2, iterations, color, width)
self.von_koch_curve(summit_1, summit_3, iterations, color, width)
@staticmethod
def _int(value):
"""Make a tuple of float coordinate into tuple of int coordinate
:param value: Tuple to convert
2019-02-26 23:43:41 +01:00
:type value: tuple
:return: new tuple with int values
:rtype: tuple(int, int)"""
return int(value[0]), int(value[1])
def von_koch_curve(self, origin, finish, iterations=1, color=None, width=0):
2019-02-27 02:15:36 +01:00
"""Draw the von koch curve on image.
:param origin: coordinate of the starting point
:param finish: coordinate of the ending point
:param iterations: iterations for the drawings
:param color: color to use for the lines
:param width: the line width, in pixels
:type origin: tuple
:type finish: tuple
:type iterations: int
:type color: tuple
:type width: int"""
third = origin[0] + (finish[0] - origin[0]) * 1 / 3, origin[1] + (finish[1] - origin[1]) * 1 / 3
two_third = origin[0] + (finish[0] - origin[0]) * 2 / 3, origin[1] + (finish[1] - origin[1]) * 2 / 3
length = (((origin[0] - finish[0]) ** 2 + (origin[1] - finish[1]) ** 2) ** 0.5) / 3
2019-02-27 02:15:36 +01:00
if finish[0] == origin[0]:
2019-03-04 15:33:35 +01:00
angle = pi / 2
2019-02-27 02:15:36 +01:00
else:
angle = atan((finish[1] - origin[1]) / (finish[0] - origin[0]))
angle_total = angle + pi / 3
if origin[0] > finish[0]:
angle_total += pi
summit = (cos(angle_total) * length + third[0], sin(angle_total) * length + third[1])
if iterations <= 1:
self.line([self._int(origin), self._int(third), self._int(summit), self._int(two_third), self._int(finish)],
color, width)
else:
self.von_koch_curve(self._int(origin), self._int(third), iterations - 1, color, width)
self.von_koch_curve(self._int(third), self._int(summit), iterations - 1, color, width)
self.von_koch_curve(self._int(summit), self._int(two_third), iterations - 1, color, width)
self.von_koch_curve(self._int(two_third), self._int(finish), iterations - 1, color, width)
if __name__ == "__main__":
img = Image.new('RGB', (5000, 5000), (255, 255, 255))
2019-02-26 23:43:41 +01:00
"""figures = Figures(im=img)
figures.blanc_manger((2000, 2000), (3000, 3000), 7, color=(0, 0, 0), width=2)"""
2019-02-27 02:15:36 +01:00
figures = Figures(im=img)
figures.blanc_manger((1000, 2500), (4000, 2500), 5, color=(0, 0, 0), width=3)
img.save("D:\\Users\\louis chauvet\\Documents\\GitHub\\fractale\\test.bmp")