├── .gitignore ├── Basic_Example.py ├── Circular.py ├── Images ├── Basic_Example │ ├── 0-svg │ │ └── 20201809-223721-a67a133e.svg │ └── 20201809-223529-74e4570a.png ├── Circular │ ├── 0-svg │ │ └── a79bb544.svg │ └── 0bde255-sample.png ├── Line_Grid │ ├── 0-svg │ │ └── 0555b3ca.svg │ └── c314c507-sample.png ├── Line_Walker │ ├── 0-svg │ │ └── 8d309340.svg │ └── 857172e8-sample.png ├── Magnetic_Flow │ ├── 0-svg │ │ └── ff78ff1e.svg │ └── 9d6a69dd-sample.png ├── Mosaic_Circles │ ├── 0-svg │ │ └── 4a16feee.svg │ └── 1e299f47-sample.png ├── Parallel_Lines │ ├── 0-svg │ │ └── d13e7158.svg │ └── a5c334c2-sample.png └── Vertical_Lines │ ├── 0-svg │ └── d09bd68f.svg │ └── 125c3d4d-sample.png ├── Line_Grid.py ├── Line_Walker.py ├── Magnetic_Flow.py ├── Mosaic_Circles.py ├── Parallel_Lines.py ├── README.md ├── Vertical_Lines.py ├── generate ├── graphics ├── Config.py ├── Context.py ├── Generators.py ├── Geometry.py ├── Graphics.py ├── Helpers.py ├── Intersection.py ├── Twitter.py ├── Vector.py └── __init__.py ├── requirements.txt └── templates └── basic.py /.gitignore: -------------------------------------------------------------------------------- 1 | graphics/__pycache__ 2 | *.pyc 3 | *.swp 4 | Images/ 5 | venv/ 6 | -------------------------------------------------------------------------------- /Basic_Example.py: -------------------------------------------------------------------------------- 1 | import graphics.Config as config 2 | from graphics.Graphics import setup, export 3 | from graphics.Geometry import background, color, Circle, stroke 4 | from random import randint 5 | 6 | width = 2000 7 | height = 2000 8 | 9 | 10 | def draw(): 11 | background(0.95, 0.95, 0.95, 1.0) 12 | color(0, 0, 0, 0.01) 13 | 14 | for k in range(100): 15 | step = 100 / 4 16 | for j in range(0, width+100, 100): 17 | for i in range(0, height+100, 100): 18 | r = randint(-4, 4) 19 | offset_x = step / r if r != 0 else 0 20 | r = randint(-4, 4) 21 | offset_y = step / r if r != 0 else 0 22 | Circle(i+offset_x, j+offset_y, 10) 23 | stroke() 24 | 25 | 26 | def main(): 27 | # Pass optional overrides 28 | setup(width, height) 29 | draw() 30 | export() 31 | 32 | 33 | if __name__ == '__main__': 34 | main() 35 | -------------------------------------------------------------------------------- /Circular.py: -------------------------------------------------------------------------------- 1 | from graphics.Graphics import setup, export 2 | from graphics.Geometry import background, color, stroke 3 | from graphics.Geometry import Line as draw_line 4 | from graphics.Vector import Vector as vec2 5 | import math 6 | import random 7 | 8 | width, height = 1000, 1000 9 | 10 | 11 | # Line class 12 | class Line: 13 | def __init__(self, p0, d, _id): 14 | self.id = _id 15 | self.p0 = p0 16 | self.p1 = p0 17 | self.dir = d 18 | 19 | def draw(self): 20 | color(0.15, 0.15, 0.15, 1) 21 | draw_line(self.p0[0], self.p0[1], self.p1[0], self.p1[1]) 22 | stroke() 23 | 24 | def update(self): 25 | self.p1 = self.p1 + self.dir 26 | 27 | def intersect(self, p2, p3): 28 | A1 = self.p1[1] - self.p0[1] 29 | B1 = self.p0[0] - self.p1[0] 30 | C1 = A1 * self.p0[0] + B1 * self.p0[1] 31 | A2 = p3[1] - p2[1] 32 | B2 = p2[0] - p3[0] 33 | C2 = A2 * p2[0] + B2 * p2[1] 34 | denom = A1 * B2 - A2 * B1 35 | 36 | if denom == 0: 37 | return False 38 | 39 | intersect_x = (B2 * C1 - B1 * C2) / denom 40 | intersect_y = (A1 * C2 - A2 * C1) / denom 41 | 42 | rx0 = (intersect_x - self.p0[0]) / (self.p1[0] - self.p0[0]) 43 | ry0 = (intersect_y - self.p0[1]) / (self.p1[1] - self.p0[1]) 44 | rx1 = (intersect_x - p2[0]) / (p3[0] - p2[0]) 45 | ry1 = (intersect_y - p2[1]) / (p3[1] - p2[1]) 46 | 47 | if(((rx0 >= 0 and rx0 <= 1) or (ry0 >= 0 and ry0 <= 1)) and ((rx1 >= 0 and rx1 <= 1) or (ry1 >= 0 and ry1 <= 1))): 48 | return True 49 | else: 50 | return False 51 | 52 | def change_dir(self, a): 53 | current_angle = math.atan2(a[1], a[0]) * 180 / math.pi 54 | new_dir = random.randint(-15, 15) 55 | angle = math.radians(current_angle + new_dir) 56 | d = vec2([math.cos(angle), math.sin(angle)]) 57 | self.dir = d 58 | 59 | def get_length(self): 60 | dist = math.hypot(self.p0[0] - self.p1[0], self.p0[1] - self.p1[1]) 61 | return dist 62 | 63 | def get_direction(self): 64 | d = (self.p0 - self.p1) / self.get_length() 65 | return d * -1.0 66 | 67 | def get_dist_from_center(self, center): 68 | dist = math.hypot(self.p1[0] - center[0], self.p1[1] - center[1]) 69 | return dist 70 | 71 | 72 | def draw(): 73 | 74 | background(0.95, 0.95, 0.95, 1.0) 75 | 76 | grid_size = 3 77 | border = 40 78 | x_step = (width-(border*2)) / float(grid_size) 79 | y_step = (height-(border*2)) / float(grid_size) 80 | x_offset = x_step / 2.0 81 | y_offset = y_step / 2.0 82 | 83 | for y in range(grid_size): 84 | for x in range(grid_size): 85 | x_pos = x_step * x + x_offset + border 86 | y_pos = y_step * y + y_offset + border 87 | my_lines = [] 88 | num_lines = 180 89 | angle = (2*math.pi) / float(num_lines) 90 | center_x = width / 2.0 91 | center_y = height / 2.0 92 | center = vec2([x_pos, y_pos]) 93 | circle_size = (x_step / 2.0) - 10 94 | 95 | order = [] 96 | for i in range(num_lines): 97 | order.append(i) 98 | 99 | random.shuffle(order) 100 | 101 | for i in range(num_lines): 102 | index = order[i] 103 | pos = vec2([ 104 | (math.cos(angle*index) * (circle_size-0.1))+center[0], 105 | (math.sin(angle*index) * (circle_size-0.1))+center[1] 106 | ]) 107 | dir = (pos - vec2([center_x, center_y])) * -1.0 108 | my_lines.append(Line(pos, dir, i)) 109 | my_lines[i].change_dir(dir) 110 | 111 | while my_lines[i].get_dist_from_center(center) < circle_size: 112 | my_lines[i].update() 113 | 114 | stop_drawing = False 115 | for line in my_lines: 116 | if line.id != my_lines[i].id: 117 | if my_lines[i].intersect(line.p0, line.p1): 118 | stop_drawing = True 119 | break 120 | 121 | if stop_drawing: 122 | break 123 | 124 | for line in my_lines: 125 | if line.get_length() > 0: 126 | line.draw() 127 | 128 | 129 | def main(): 130 | setup(width, height) 131 | draw() 132 | export() 133 | 134 | 135 | if __name__ == '__main__': 136 | main() 137 | -------------------------------------------------------------------------------- /Images/Basic_Example/20201809-223529-74e4570a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakobGlock/Generative-Art/9104bb97795a4adb8ee5ea997d86d2fd9bfe1505/Images/Basic_Example/20201809-223529-74e4570a.png -------------------------------------------------------------------------------- /Images/Circular/0bde255-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakobGlock/Generative-Art/9104bb97795a4adb8ee5ea997d86d2fd9bfe1505/Images/Circular/0bde255-sample.png -------------------------------------------------------------------------------- /Images/Line_Grid/c314c507-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakobGlock/Generative-Art/9104bb97795a4adb8ee5ea997d86d2fd9bfe1505/Images/Line_Grid/c314c507-sample.png -------------------------------------------------------------------------------- /Images/Line_Walker/857172e8-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakobGlock/Generative-Art/9104bb97795a4adb8ee5ea997d86d2fd9bfe1505/Images/Line_Walker/857172e8-sample.png -------------------------------------------------------------------------------- /Images/Magnetic_Flow/9d6a69dd-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakobGlock/Generative-Art/9104bb97795a4adb8ee5ea997d86d2fd9bfe1505/Images/Magnetic_Flow/9d6a69dd-sample.png -------------------------------------------------------------------------------- /Images/Mosaic_Circles/1e299f47-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakobGlock/Generative-Art/9104bb97795a4adb8ee5ea997d86d2fd9bfe1505/Images/Mosaic_Circles/1e299f47-sample.png -------------------------------------------------------------------------------- /Images/Parallel_Lines/a5c334c2-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakobGlock/Generative-Art/9104bb97795a4adb8ee5ea997d86d2fd9bfe1505/Images/Parallel_Lines/a5c334c2-sample.png -------------------------------------------------------------------------------- /Images/Vertical_Lines/125c3d4d-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakobGlock/Generative-Art/9104bb97795a4adb8ee5ea997d86d2fd9bfe1505/Images/Vertical_Lines/125c3d4d-sample.png -------------------------------------------------------------------------------- /Line_Grid.py: -------------------------------------------------------------------------------- 1 | from graphics.Graphics import setup, export 2 | from graphics.Geometry import Line, background, color, stroke 3 | from graphics.Helpers import map 4 | from graphics.Vector import Vector as vec2 5 | import random 6 | import math 7 | 8 | # Some variables 9 | width, height = 1000, 1000 10 | 11 | 12 | # Main function 13 | def draw(): 14 | background(0.95, 0.95, 0.95, 1.0) 15 | color(0, 0, 0, 1) 16 | 17 | border = 50 18 | border_sQ = border*2 19 | num_lines = 18 20 | y_step = float((height-border_sQ)/num_lines) 21 | x_step = float((width-border_sQ)/num_lines) 22 | y_offset = y_step/2.0 23 | x_offset = x_step/2.0 24 | un_offset = 10 25 | my_lines = [] 26 | 27 | for i in range(num_lines): 28 | for j in range(num_lines-1): 29 | if j == 0: 30 | p1 = vec2([ 31 | x_step*j+x_offset + random.uniform(-x_offset, x_offset) + border, 32 | y_step*i+y_offset + random.uniform(-y_offset, y_offset) + border 33 | ]) 34 | p2 = vec2([ 35 | x_step*(j+1)+x_offset + random.uniform(-x_offset, x_offset) + border, 36 | y_step*i+y_offset + random.uniform(-y_offset, y_offset) + border 37 | ]) 38 | my_lines.append(Line(p1[0], p1[1], p2[0], p2[1])) 39 | else: 40 | p1 = vec2([ 41 | my_lines[(j+(num_lines-1)*i)-1].p1[0], 42 | my_lines[(j+(num_lines-1)*i)-1].p1[1] 43 | ]) 44 | p2 = vec2([ 45 | x_step*(j+1)+x_offset + random.uniform(-x_offset, x_offset) + border, 46 | y_step*i+y_offset + random.uniform(-y_offset, y_offset) + border 47 | ]) 48 | my_lines.append(Line(p1[0], p1[1], p2[0], p2[1])) 49 | 50 | index = 0 51 | for i in range(num_lines): 52 | index = index + 1 53 | for j in range(num_lines-1): 54 | if i != 0: 55 | lerp_lines = int(map(i, 0, num_lines, 1, 12))+1 56 | for k in range(lerp_lines): 57 | p0 = my_lines[(j+(num_lines-1)*(i-1))].get_lerp(math.pow(map(k, 0, lerp_lines-1, 0, 1), 1)) 58 | p1 = my_lines[(j+(num_lines-1)*i)].get_lerp(math.pow(map(k, 0, lerp_lines-1, 0, 1), 1)) 59 | Line(p0[0], p0[1], p1[0], p1[1]).draw() 60 | stroke() 61 | 62 | for line in my_lines: 63 | line.draw() 64 | stroke() 65 | 66 | 67 | def main(): 68 | setup(width, height) 69 | draw() 70 | export() 71 | 72 | 73 | # Call the main function 74 | if __name__ == '__main__': 75 | main() 76 | -------------------------------------------------------------------------------- /Line_Walker.py: -------------------------------------------------------------------------------- 1 | from graphics.Graphics import setup, export 2 | from graphics.Geometry import Line, background, color, stroke 3 | from graphics.Vector import Vector as vec2 4 | import math 5 | import random 6 | 7 | ############################# 8 | # This script is a bit slow # 9 | ############################# 10 | 11 | # Variables 12 | width, height = 1000, 1000 13 | center_x, center_y = width/2.0, height/2.0 14 | border = 20 15 | 16 | 17 | # Line Class 18 | class Line(Line): 19 | def __init__(self, x1, y1, x2, y2, id, dir, angle, connect): 20 | super().__init__(x1, y1, x2, y2) 21 | self.p1 = vec2([x1, y1]) 22 | self.id = id 23 | self.dir = dir 24 | self.angle = angle 25 | self.connect = connect 26 | 27 | def update(self): 28 | self.p1 = self.p1 + self.dir 29 | 30 | def change_dir(self, a): 31 | current_angle = math.atan2(a[1], a[0]) * 180 / math.pi 32 | new_dir = 90 33 | b = random.randint(0, 2) 34 | if b == 0: 35 | new_dir = 0 36 | elif b == 1: 37 | new_dir = 22.5 / 2.0 38 | else: 39 | new_dir = -(22.5 / 2.0) 40 | angle = math.radians(current_angle + new_dir) 41 | dir = vec2([math.cos(angle), math.sin(angle)]) 42 | self.dir = dir 43 | 44 | def edges(self): 45 | if self.p1[0] >= width-border or self.p1[0] < border or self.p1[1] < border or self.p1[1] >= height-border: 46 | return True 47 | 48 | def intersect(self, p2, p3): 49 | A1 = self.p1[1] - self.p0[1] 50 | B1 = self.p0[0] - self.p1[0] 51 | C1 = A1 * self.p0[0] + B1 * self.p0[1] 52 | A2 = p3[1] - p2[1] 53 | B2 = p2[0] - p3[0] 54 | C2 = A2 * p2[0] + B2 * p2[1] 55 | denom = A1 * B2 - A2 * B1 56 | 57 | if denom == 0: 58 | return False 59 | 60 | intersect_x = (B2 * C1 - B1 * C2) / denom 61 | intersect_y = (A1 * C2 - A2 * C1) / denom 62 | 63 | rx0 = (intersect_x - self.p0[0]) / (self.p1[0] - self.p0[0]) 64 | ry0 = (intersect_y - self.p0[1]) / (self.p1[1] - self.p0[1]) 65 | rx1 = (intersect_x - p2[0]) / (p3[0] - p2[0]) 66 | ry1 = (intersect_y - p2[1]) / (p3[1] - p2[1]) 67 | 68 | if(((rx0 >= 0 and rx0 <= 1) or (ry0 >= 0 and ry0 <= 1)) and ((rx1 >= 0 and rx1 <= 1) or (ry1 >= 0 and ry1 <= 1))): 69 | return True 70 | else: 71 | return False 72 | 73 | 74 | # Main function 75 | def draw(): 76 | background(0.95, 0.95, 0.95, 1.0) 77 | color(0, 0, 0, 1) 78 | 79 | walkers = [] 80 | index = 0 81 | angle = (math.pi*2) / 90.0 82 | pos = vec2([100, 100]) 83 | dir = vec2([1, 1]) 84 | x, y = pos[0], pos[1] 85 | walkers.append(Line(x, y, x, y, 0, dir, angle * 180.0 / math.pi, -1)) 86 | line_length = 20 87 | depth = -1 88 | 89 | for i in range(3000): 90 | if i != 0: 91 | if stop_drawing is True: 92 | connect = random.randint(0, i-1) 93 | pos = walkers[connect].get_lerp(random.uniform(0.25, 0.75)) 94 | dist = math.hypot( 95 | walkers[i-1].p0[0] - center_x, 96 | walkers[i-1].p0[1] - center_y 97 | ) 98 | c = vec2([center_x, center_y]) 99 | dir = ((walkers[i-1].p0 - c) / dist) * -1.0 100 | x, y = pos[0], pos[1] 101 | walkers.append(Line(x, y, x, y, i, dir, angle * 180.0 / math.pi, connect)) 102 | walkers[i].change_dir(walkers[i].dir) 103 | line_length = 10 104 | index = index + 1 105 | else: 106 | pos = walkers[i-1].p1 107 | dir = walkers[i-1].dir 108 | x, y = pos[0], pos[1] 109 | walkers.append(Line(x, y, x, y, i, dir, angle * 180.0 / math.pi, -1)) 110 | walkers[i].change_dir(walkers[i-1].dir) 111 | 112 | if index > 400: 113 | break 114 | 115 | stop_drawing = False 116 | 117 | while walkers[i].get_length() < line_length: 118 | walkers[i].update() 119 | 120 | for w in walkers: 121 | if walkers[i].id != w.id and walkers[i-1].id != w.id and walkers[i].connect != w.id: 122 | if walkers[i].intersect(w.p0, w.p1) or walkers[i].edges(): 123 | stop_drawing = True 124 | break 125 | 126 | if stop_drawing: 127 | break 128 | 129 | walkers[i].draw() 130 | stroke() 131 | 132 | 133 | def main(): 134 | setup(width, height) 135 | draw() 136 | export() 137 | 138 | 139 | if __name__ == '__main__': 140 | main() 141 | -------------------------------------------------------------------------------- /Magnetic_Flow.py: -------------------------------------------------------------------------------- 1 | from graphics.Graphics import setup, export 2 | from graphics.Geometry import background, color, stroke, Line, line_width 3 | import math 4 | import random 5 | 6 | # Some variables 7 | height, width = 1000, 1000 8 | grid_size = 100 9 | border, mag_border = 50, 450 10 | step_x, step_y = (width//grid_size), (height//grid_size) 11 | 12 | 13 | # Particle class 14 | class Particle: 15 | def __init__(self, x, y, vel_x, vel_y): 16 | self.x = x 17 | self.y = y 18 | self.vel_x = vel_x 19 | self.vel_y = vel_y 20 | self.frc_x = 0 21 | self.frc_y = 0 22 | self.lx, self.ly = self.x, self.y 23 | self.draw_stroke = True 24 | 25 | def update(self): 26 | 27 | self.x = self.x + self.frc_x 28 | self.y = self.y + self.frc_y 29 | 30 | self.vel_x = self.vel_x * 0.9 31 | self.vel_y = self.vel_y * 0.9 32 | 33 | self.x = self.x + self.vel_x 34 | self.y = self.y + self.vel_y 35 | 36 | def edges(self): 37 | if self.x <= 50 or self.x >= width-50 or self.y <= 50 or self.y >= height-50: 38 | self.draw_stroke = False 39 | else: 40 | self.draw_stroke = True 41 | 42 | def reset_force(self): 43 | self.frc_x = 0 44 | self.frc_y = 0 45 | 46 | def set_force(self, fx, fy): 47 | self.frc_x = self.frc_x + fx 48 | self.frc_y = self.frc_y + fy 49 | 50 | def set_last_pos(self): 51 | self.lx, self.ly = self.x, self.y 52 | 53 | def calculate_force(self, mx, my, mp): 54 | dy = mx - self.x 55 | dx = my - self.y 56 | angle = math.atan2(dy, dx) * mp 57 | sx = math.sin(angle) 58 | sy = math.cos(angle) 59 | return [sx, sy] 60 | 61 | def draw(self): 62 | if self.draw_stroke is not False: 63 | line_width(0.9) 64 | Line(self.lx, self.ly, self.x, self.y) 65 | stroke() 66 | 67 | 68 | # Magnet Class 69 | class magnet: 70 | def __init__(self, x, y, pole): 71 | self.x = x 72 | self.y = y 73 | self.p = pole 74 | 75 | 76 | def draw(): 77 | 78 | background(0.95, 0.95, 0.95, 1.0) 79 | color(0.0, 0.0, 0.0, 1.0) 80 | 81 | magnets = [] 82 | my_particles = [] 83 | num_magnets = random.randint(2, 8) 84 | sum_x, sum_y = 0, 0 85 | sums = 0 86 | 87 | print("Number of Magnets: " + str(num_magnets)) 88 | 89 | for m in range(num_magnets): 90 | pole = 1 91 | if random.uniform(0, 1) < 0.5: 92 | pole = -1 93 | 94 | magnets.append(magnet( 95 | random.randint(100, width-100), 96 | random.randint(100, height-100), 97 | pole 98 | )) 99 | 100 | start_num = 360 101 | a = (math.pi*2)/start_num 102 | 103 | for x in range(100, width-100, (width-200)//1): 104 | for y in range(100, height-100, (height-200)//1): 105 | for i in range(start_num): 106 | xx = x + (math.sin(a*i)*250) + ((width-200)//2) 107 | yy = y + (math.cos(a*i)*250) + ((height-200)//2) 108 | vx = random.uniform(-1, 1)*0.5 109 | vy = random.uniform(-1, 1)*0.5 110 | my_particles.append(Particle(xx, yy, vx, vy)) 111 | 112 | for p in my_particles: 113 | for t in range(1000): 114 | for m in magnets: 115 | sums = p.calculate_force(m.x, m.y, m.p*4) 116 | sum_x = sum_x + sums[0] 117 | sum_y = sum_y + sums[1] 118 | 119 | sum_x = sum_x / len(magnets) 120 | sum_y = sum_y / len(magnets) 121 | 122 | p.reset_force() 123 | p.set_force(sum_x, sum_y) 124 | p.update() 125 | p.edges() 126 | if t % 8 == 0: 127 | p.draw() 128 | p.set_last_pos() 129 | 130 | 131 | def main(): 132 | setup(width, height) 133 | draw() 134 | export() 135 | 136 | 137 | if __name__ == '__main__': 138 | main() 139 | -------------------------------------------------------------------------------- /Mosaic_Circles.py: -------------------------------------------------------------------------------- 1 | import graphics.Config as config 2 | from graphics.Graphics import setup, export 3 | from graphics.Geometry import Line, background, color, stroke 4 | from graphics.Helpers import map 5 | from graphics.Generators import NoiseLoop 6 | import math 7 | import random 8 | 9 | width, height = 1000, 1000 10 | 11 | 12 | def draw(): 13 | background(0.95, 0.95, 0.95, 1.0) 14 | color(0, 0, 0, 1) 15 | 16 | grid_x, grid_y = 1, 1 17 | x_step, y_step = width//grid_x, height//grid_y 18 | x_offset, y_offset = x_step//2, y_step//2 19 | 20 | n_loop = [] 21 | offset = [] 22 | num_layers = 40 23 | s = (x_offset // num_layers) 24 | 25 | for xxx in range(grid_x): 26 | for yyy in range(grid_y): 27 | center_x = xxx * x_step + x_offset 28 | center_y = yyy * y_step + y_offset 29 | for layer in range(num_layers): 30 | offset = random.randint(0, 360) 31 | n_loop.append(NoiseLoop(map(layer, 0, num_layers, 1, 4), s*layer, s*layer+s)) 32 | num_points = 360 33 | for i in range(num_points): 34 | r = n_loop[layer].get_value(i) 35 | x = r * math.cos(math.radians(i)) + center_x 36 | y = r * math.sin(math.radians(i)) + center_y 37 | # Think of a better way to do this 38 | config.Context.line_to(x, y) 39 | config.Context.close_path() 40 | stroke() 41 | 42 | if layer != 0: 43 | num_lines = map(layer, 0, num_layers, 16, 2) 44 | num_points = 360 / num_lines 45 | for i in range(int(num_points)): 46 | r = n_loop[layer].get_value(i*num_lines+offset) 47 | x = r * math.cos(math.radians(i*num_lines+offset)) + center_x 48 | y = r * math.sin(math.radians(i*num_lines+offset)) + center_y 49 | 50 | r = n_loop[layer-1].get_value(i*num_lines+offset) 51 | xx = r * math.cos(math.radians(i*num_lines+offset)) + center_x 52 | yy = r * math.sin(math.radians(i*num_lines+offset)) + center_y 53 | Line(xx, yy, x, y) 54 | stroke() 55 | 56 | 57 | def main(): 58 | setup(width, height) 59 | draw() 60 | export() 61 | 62 | 63 | if __name__ == '__main__': 64 | main() 65 | -------------------------------------------------------------------------------- /Parallel_Lines.py: -------------------------------------------------------------------------------- 1 | from graphics.Graphics import setup, export 2 | from graphics.Geometry import Line, background, color, stroke 3 | from graphics.Helpers import map 4 | from graphics.Vector import Vector as vec2 5 | import random 6 | import math 7 | 8 | 9 | # Some variables 10 | width, height = 1000, 1000 11 | 12 | 13 | # Main function 14 | def draw(): 15 | background(0.95, 0.95, 0.95, 1.0) 16 | color(0, 0, 0, 1) 17 | 18 | connector_lines = [] 19 | 20 | num_lines_x = 20 21 | num_lines_y = 3 22 | x_step = float((width-100) // num_lines_x) 23 | y_step = float(height // num_lines_y) 24 | x_offset = (x_step // 2) + 50 25 | y_offset = y_step // 2 26 | 27 | for h in range(num_lines_y): 28 | for i in range(num_lines_x): 29 | x = x_step * i + x_offset 30 | y = y_step * h + y_offset 31 | yy = random.randint(25, 100) 32 | p0 = vec2([ 33 | x + random.uniform(-(x_step//2)+10, (x_step//2)+10), 34 | random.uniform(y-50, y+50) + yy 35 | ]) 36 | p1 = vec2([ 37 | x + random.uniform(-(x_step//2)+10, (x_step//2)+10), 38 | random.uniform(y-50, y+50) - yy 39 | ]) 40 | connector_lines.append(Line(p0[0], p0[1], p1[0], p1[1])) 41 | 42 | if i != 0: 43 | num_lines = 21 44 | l1 = i+num_lines_x*h 45 | l2 = (i+num_lines_x*h)+1 46 | 47 | while l2 == l1: 48 | l2 = random.randint(0, len(connector_lines)) 49 | 50 | for j in range(num_lines+1): 51 | p0 = connector_lines[l1-1].get_lerp(math.pow(map(j, 0, num_lines, 0, 1), 1)) 52 | p1 = connector_lines[l2-1].get_lerp(math.pow(map(j, 0, num_lines, 0, 1), 1)) 53 | Line(p0[0], p0[1], p1[0], p1[1]) 54 | stroke() 55 | 56 | 57 | def main(): 58 | setup(width, height) 59 | draw() 60 | export() 61 | 62 | 63 | # Call the main function 64 | if __name__ == '__main__': 65 | main() 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Generative-Art 2 | 3 | I have only tested this on Ubuntu 20.04. 4 | 5 | A selection of generative art scripts written in Python with the intention of using my AxiDraw to plot them onto paper. 6 | 7 | This project has evolved into a small python package which has functions and classes, which I reuse in other scripts and saves me writing the same code over and over again. It also includes basic tool for generating scripts and doing basic tasks that I do on a regular basis. 8 | 9 | This is really built for my own personal use and is a constantly evolving project, but I thought people may enjoy / be interested in it, so that is why I am making it public. 10 | 11 | You can get these scripts without the python module integration by cloning the 0.1.1 tag, go here: https://github.com/JakobGlock/Generative-Art/tree/0.1.1 12 | 13 | 14 | ### Setup 15 | 16 | These scripts have been made using Python 3.8.2 17 | 18 | Install some dependencies, on Linux this very simple you just run the following command in a terminal window: 19 | 20 | `sudo apt install python-cairo libcairo2-dev` 21 | 22 | Create a Python virtual environment and activate it: 23 | 24 | ``` 25 | cd /to/the/repo/directory/on/your/computer/Generative-art 26 | python3 -m venv venv 27 | source venv/bin/activate 28 | ``` 29 | 30 | You will also need to install some packages for Python which can be done using the following command: 31 | 32 | `pip3 install -r requirements.txt` 33 | 34 | 35 | ### Running Scripts 36 | 37 | You can use the generate tool to run the scripts, to get more information run the following command: 38 | 39 | `./generate --help` 40 | 41 | 42 | To generate an artwork: 43 | 44 | `./generate artwork new Line_Grid.py` 45 | 46 | To generate the same artwork, but 10 of them and in SVG format: 47 | 48 | `./generate artwork new Line_Grid.py -n 10 --svg` 49 | 50 | This will save 10 files in SVG format into the `Images/Line_Grid` folder, see `./generate --help` for more options and information. 51 | 52 | 53 | To create a new script/project, run the following command: 54 | 55 | `./generate project new my_cool_script.py` 56 | 57 | This will create a basic script from a template file to get you started. 58 | 59 | 60 | ### Sample Images 61 | 62 | #### Circular 63 | ![Circular](/Images/Circular/0bde255-sample.png) 64 | 65 | #### Line_Grid 66 | ![Line_Grid](/Images/Line_Grid/c314c507-sample.png) 67 | 68 | #### Line_Walker 69 | ![Line_Walker](/Images/Line_Walker/857172e8-sample.png) 70 | 71 | #### Magnetic_Flow 72 | ![Magnetic_Flow](/Images/Magnetic_Flow/9d6a69dd-sample.png) 73 | 74 | #### Mosaic_Circles 75 | ![Mosaic_Circles](/Images/Mosaic_Circles/1e299f47-sample.png) 76 | 77 | #### Parallel_Lines 78 | ![Parallel_Lines](/Images/Parallel_Lines/a5c334c2-sample.png) 79 | 80 | #### Vertical_Lines 81 | ![Vertical_Lines](/Images/Vertical_Lines/125c3d4d-sample.png) 82 | -------------------------------------------------------------------------------- /Vertical_Lines.py: -------------------------------------------------------------------------------- 1 | from graphics.Graphics import setup, export 2 | from graphics.Geometry import background, color, stroke 3 | from graphics.Geometry import Line as draw_line 4 | from graphics.Vector import Vector as vec2 5 | import math 6 | import random 7 | import numpy as np 8 | 9 | # Some variables 10 | width, height = 1000, 1000 11 | 12 | 13 | class Line: 14 | def __init__(self, p0, dir, id): 15 | self.id = id 16 | self.p0 = p0 17 | self.p1 = p0 18 | self.dir = dir 19 | self.intersect = vec2([0.0, 0.0]) 20 | self.count = 0 21 | 22 | def draw(self): 23 | color(0.0, 0.0, 0.0, 1.0) 24 | draw_line(self.p0[0], self.p0[1], self.p1[0], self.p1[1]) 25 | stroke() 26 | 27 | def extend_line(self): 28 | self.p1 = self.p1 + self.dir 29 | 30 | def change_dir(self): 31 | self.count = self.count + 1 32 | 33 | if self.count % 40 == 0: 34 | angle = math.radians(random.randint(45, 135)) 35 | dir = vec2([math.sin(angle), math.cos(angle)]) 36 | self.dir = dir 37 | return True 38 | else: 39 | return False 40 | 41 | def edges(self): 42 | if self.p1[0] >= width or self.p1[0] < 0 or \ 43 | self.p1[1] < 0 or self.p1[1] >= height-50: 44 | return True 45 | 46 | def line_intersect(self, p2, p3): 47 | A1 = self.p1[1] - self.p0[1] 48 | B1 = self.p0[0] - self.p1[0] 49 | C1 = A1 * self.p0[0] + B1 * self.p0[1] 50 | A2 = p3[1] - p2[1] 51 | B2 = p2[0] - p3[0] 52 | C2 = A2 * p2[0] + B2 * p2[1] 53 | denom = A1 * B2 - A2 * B1 54 | 55 | if denom == 0: 56 | return False 57 | 58 | intersect_x = (B2 * C1 - B1 * C2) / denom 59 | intersect_y = (A1 * C2 - A2 * C1) / denom 60 | 61 | rx0 = (intersect_x - self.p0[0]) / (self.p1[0] - self.p0[0]) 62 | ry0 = (intersect_y - self.p0[1]) / (self.p1[1] - self.p0[1]) 63 | rx1 = (intersect_x - p2[0]) / (p3[0] - p2[0]) 64 | ry1 = (intersect_y - p2[1]) / (p3[1] - p2[1]) 65 | 66 | if(((rx0 >= 0 and rx0 <= 1) or (ry0 >= 0 and ry0 <= 1)) and \ 67 | ((rx1 >= 0 and rx1 <= 1) or (ry1 >= 0 and ry1 <= 1))): 68 | self.intersect = vec2([intersect_x, intersect_y]) 69 | return True 70 | else: 71 | return False 72 | 73 | def get_intersect(self): 74 | return self.intersect 75 | 76 | def get_closest_point(self, p): 77 | a = np.linalg.norm(self.p0-p) 78 | b = np.linalg.norm(self.p1-p) 79 | 80 | if a <= b: 81 | self.p0 = p 82 | else: 83 | self.p1 = p 84 | 85 | 86 | def get_direction(): 87 | num_angles = 2 88 | r = int(random.uniform(0, num_angles)) 89 | angle_step = [0, 45] 90 | 91 | for i in range(num_angles): 92 | if i == r: 93 | angle = math.radians(angle_step[i]) 94 | 95 | dirs = vec2([math.sin(angle), math.cos(angle)]) 96 | return dirs 97 | 98 | 99 | def draw(): 100 | background(0.95, 0.95, 0.95, 1.0) 101 | 102 | num_walkers = random.randint(100, 200) 103 | walkers = [] 104 | x_step = float((width-100)) / num_walkers 105 | amt = random.uniform(0.075, 0.15) 106 | amt_step = random.randint(15, 80) 107 | start_dist = random.randint(10, 150) 108 | 109 | index = 0 110 | count = 0 111 | for i in range(num_walkers): 112 | x = float((x_step * i) + 50.0) 113 | pos = vec2([float(x), float(50.0)]) 114 | angle = math.radians(0) 115 | dirs = vec2([math.sin(angle), math.cos(angle)]) 116 | walkers.append(Line(pos, dirs, i)) 117 | 118 | walk = True 119 | while walk: 120 | if count % amt_step == 0: 121 | r = random.uniform(0, 1) 122 | if r < amt and walkers[index].p0[1] > start_dist: 123 | dirs = get_direction() 124 | walkers.append(Line(walkers[index].p1, dirs, i)) 125 | index = index + 1 126 | else: 127 | angle = math.radians(0) 128 | dirs = vec2([math.sin(angle), math.cos(angle)]) 129 | walkers.append(Line(walkers[index].p1, dirs, i)) 130 | index = index + 1 131 | 132 | walkers[index].extend_line() 133 | 134 | hit_line = False 135 | for w in walkers: 136 | if walkers[index].id != w.id: 137 | intersect = walkers[index].line_intersect(w.p0, w.p1) 138 | if intersect: 139 | hit_line = True 140 | 141 | hit_edge = walkers[index].edges() 142 | if hit_edge or hit_line: 143 | walk = False 144 | 145 | count = count + 1 146 | index = index + 1 147 | 148 | for walker in walkers: 149 | walker.draw() 150 | 151 | 152 | def main(): 153 | setup(width, height) 154 | draw() 155 | export() 156 | 157 | 158 | # Call the main function 159 | if __name__ == '__main__': 160 | main() 161 | -------------------------------------------------------------------------------- /generate: -------------------------------------------------------------------------------- 1 | #! venv/bin/python 2 | 3 | """ 4 | Generate - Tool for running scripts 5 | 6 | Usage: 7 | generate project (new | delete) 8 | generate artwork new [--number=] [--open] [--svg] [--twitter] 9 | generate artwork random [--number=] [--open] [--svg] [--twitter] 10 | generate artwork all [--open] [--svg] 11 | generate list 12 | 13 | Options: 14 | -h --help Show this screen. 15 | -n --number= Number of images to generate [default: 1]. 16 | -o --open Open the file, in an image viewer (Linux only) [default: False]. 17 | --svg Create an SVG file [default: False]. 18 | --twitter Post to output image to Twitter [default: False]. 19 | """ 20 | 21 | from docopt import docopt 22 | import subprocess 23 | import glob 24 | import random 25 | import os 26 | import shutil 27 | 28 | python_path = 'venv/bin/python' 29 | dashes = "".join(["-" for x in range(0, 81)]) 30 | 31 | 32 | def print_msg(typ, msg): 33 | print(dashes) 34 | print(" ".join([typ + ":", msg])) 35 | print(dashes) 36 | 37 | 38 | def run_script(filename, open_file, file_format, twitter_post): 39 | subprocess.call([python_path, filename, open_file, file_format, twitter_post]) 40 | 41 | 42 | def check_filename(args): 43 | filename = args[''] 44 | if not filename.endswith(".py"): 45 | print_msg("ERROR", "Filename must end with .py") 46 | exit() 47 | 48 | elif os.path.isfile(filename) and not args['delete'] and not args['artwork']: 49 | print_msg("ERROR", "File already exists, pick another name") 50 | exit() 51 | 52 | elif not os.path.isfile(filename) and args['delete']: 53 | print_msg("ERROR", "File {} does not exist, pick a valid name".format(filename)) 54 | exit() 55 | 56 | return filename 57 | 58 | 59 | def projects(args): 60 | filename = check_filename(args) 61 | if args['new']: 62 | shutil.copy2("templates/basic.py", filename) 63 | print_msg("INFO", "Generated new project {}".format(filename)) 64 | 65 | elif args['delete']: 66 | print("") 67 | x = input("WARNING: Are you REALLY sure you want to delete {} and all images (e.g. Y|N or y|n)? ".format(filename)) 68 | if x == "Y" or x == "y": 69 | print_msg("INFO", "Deleting script and related image folder") 70 | os.remove(filename) 71 | 72 | f = os.path.splitext(filename)[0] 73 | path = "Images/{}".format(f) 74 | if os.path.isdir(path): 75 | shutil.rmtree(path) 76 | else: 77 | print_msg("ERROR", "Directory {} does not exist, skipping".format(path)) 78 | else: 79 | exit() 80 | 81 | 82 | def artworks(args): 83 | filename = None 84 | if args['']: 85 | filename = check_filename(args) 86 | number = int(args['--number']) 87 | open_file = str(args['--open']) 88 | file_format = str(args['--svg']) 89 | twitter_post = str(args['--twitter']) 90 | 91 | if filename and args['new']: 92 | for i in range(0, number): 93 | run_script(filename, open_file, file_format, twitter_post) 94 | 95 | elif args['random']: 96 | for i in range(0, number): 97 | files = glob.glob('*.py') 98 | filename = random.choice(files) 99 | run_script(filename, open_file, file_format, twitter_post) 100 | 101 | elif args['all']: 102 | files = glob.glob('*.py') 103 | for filename in files: 104 | run_script(filename, open_file, file_format, twitter_post) 105 | 106 | 107 | def list_projects(args): 108 | files = glob.glob('*.py') 109 | print_msg("INFO", "List of Projects") 110 | for filename in files: 111 | print(filename) 112 | print(dashes) 113 | 114 | 115 | def main(args): 116 | if args['project']: 117 | projects(args) 118 | 119 | elif args['artwork']: 120 | artworks(args) 121 | 122 | elif args['list']: 123 | list_projects(args) 124 | 125 | 126 | if __name__ == '__main__': 127 | args = docopt(__doc__, version='Generate 0.0.1') 128 | main(args) 129 | -------------------------------------------------------------------------------- /graphics/Config.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | args = sys.argv 5 | 6 | # This is our drawing Context 7 | Context = None 8 | 9 | # Some default variables 10 | file_format = 'SVG' if eval(args[2]) else 'PNG' 11 | open_file = True if eval(args[1]) else False 12 | image_folder = "/Images" 13 | script_name = args[0] 14 | 15 | # Variables for posting to Twitter 16 | twitter_post = True if eval(args[3]) else False 17 | consumer_key = os.environ.get('consumer_key') 18 | consumer_secret = os.environ.get('consumer_secret') 19 | access_token = os.environ.get('access_token') 20 | access_token_secret = os.environ.get('access_token_secret') 21 | tweet_status = '#ADD #YOUR #TWEET #HERE' 22 | -------------------------------------------------------------------------------- /graphics/Context.py: -------------------------------------------------------------------------------- 1 | import cairo 2 | from uuid import uuid4 3 | from .Helpers import does_path_exist, open_file 4 | from os import path 5 | from datetime import datetime 6 | 7 | 8 | class DrawContext: 9 | def __init__(self, width, height, file_format, filepath, project_folder, open_bool): 10 | self.open_bool = open_bool 11 | self.width = width 12 | self.height = height 13 | self.file_format = file_format 14 | self.filename = self.generate_filename() 15 | self.filepath = filepath 16 | self.project_folder = "/" + project_folder[:-3] 17 | self.cwd = self.set_cwd() 18 | self.fullpath = self.cwd + "/" + self.filename + "." + self.file_format.lower() 19 | self.init() 20 | 21 | def init(self): 22 | does_path_exist(self.cwd) 23 | 24 | if self.file_format == 'PNG': 25 | self.cairo_context = self.setup_png() 26 | elif self.file_format == 'SVG': 27 | self.cairo_context = self.setup_svg() 28 | 29 | def setup_png(self): 30 | self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width, self.height) 31 | return cairo.Context(self.surface) 32 | 33 | def export_png(self): 34 | self.surface.write_to_png(self.fullpath) 35 | print("INFO: Saving file to {}".format(self.fullpath)) 36 | if self.open_bool: 37 | print("INFO: Opening file {}".format(self.fullpath)) 38 | open_file(self.fullpath) 39 | 40 | def setup_svg(self): 41 | self.surface = cairo.SVGSurface(self.fullpath, self.width, self.height) 42 | return cairo.Context(self.surface) 43 | 44 | def export_svg(self): 45 | self.surface.finish() 46 | print("INFO: Saving file to {}".format(self.fullpath)) 47 | if self.open_bool: 48 | print("INFO: Opening file {}".format(self.fullpath)) 49 | open_file(self.fullpath) 50 | 51 | def export(self): 52 | if self.file_format == "PNG": 53 | self.export_png() 54 | elif self.file_format == "SVG": 55 | self.export_svg() 56 | 57 | @property 58 | def context(self): 59 | return self.cairo_context 60 | 61 | @context.setter 62 | def context(self, context): 63 | self.context = context 64 | 65 | def get_file_name(self): 66 | return self.filename 67 | 68 | def get_fullpath(self): 69 | return self.fullpath 70 | 71 | def generate_filename(self): 72 | now = datetime.now() 73 | timestamp = now.strftime("%Y%d%m-%H%M%S") 74 | unique_id = uuid4().hex[:8] 75 | return str(timestamp + "-" + unique_id) 76 | 77 | def set_cwd(self): 78 | if self.file_format == "PNG": 79 | return path.dirname(path.realpath(__file__))[:-9] + self.filepath + self.project_folder 80 | elif self.file_format == "SVG": 81 | return path.dirname(path.realpath(__file__))[:-9] + self.filepath + self.project_folder + "/0-svg" 82 | else: 83 | print("ERROR: Choose a valid file format: PNG|SVG") 84 | exit(0) 85 | -------------------------------------------------------------------------------- /graphics/Generators.py: -------------------------------------------------------------------------------- 1 | from .Vector import Vector as vec2 2 | from noise import pnoise2 3 | from random import randint, choices 4 | from math import cos 5 | from math import sin 6 | from math import radians 7 | 8 | 9 | # Noise loop generator 10 | class NoiseLoop: 11 | def __init__(self, diameter, _min, _max): 12 | self.diameter = diameter 13 | self.min = _min 14 | self.max = _max 15 | self.noise_seed = randint(0, 10000) 16 | 17 | def get_value(self, a): 18 | x = self.map(cos(radians(a)), -1, 1, 0, self.diameter) 19 | y = self.map(sin(radians(a)), -1, 1, 0, self.diameter) 20 | r = pnoise2(x+self.noise_seed, y+self.noise_seed) 21 | return self.map(r, -1, 1, self.min, self.max) 22 | 23 | def map(self, value, left_min, left_max, right_min, right_max): 24 | left_span = left_max - left_min 25 | right_span = right_max - right_min 26 | value_scaled = float(value - left_min) / float(left_span) 27 | return right_min + (value_scaled * right_span) 28 | 29 | def set_noise_seed(self, _offset): 30 | self.noise_seed = _offset 31 | 32 | 33 | # Random walker 34 | class RandomWalker: 35 | def __init__(self, x, y, size): 36 | self.pos = vec2([x, y]) 37 | self.prev_pos = vec2([x, y]) 38 | self.size = size 39 | self.direction = vec2([0, 0]) 40 | 41 | def walk(self, d, w): 42 | self.set_direction(d, w) 43 | self.pos.x += self.direction.x * self.size 44 | self.pos.y += self.direction.y * self.size 45 | 46 | def set_direction(self, d, w): 47 | directions = d 48 | weights = w 49 | self.direction = choices(directions, weights=weights, k=1)[0] 50 | 51 | def step_size(self, s, r=None): 52 | if r is None: 53 | self.size = s 54 | else: 55 | self.size = randint(s, r) 56 | -------------------------------------------------------------------------------- /graphics/Geometry.py: -------------------------------------------------------------------------------- 1 | import graphics.Config as config 2 | from numpy import clip 3 | from math import hypot, sqrt 4 | from .Helpers import TWO_PI 5 | from .Vector import Vector as vec2 6 | 7 | 8 | # Circle class 9 | class Circle: 10 | def __init__(self, x, y, r): 11 | self.context = config.Context 12 | self.pos = vec2([x, y]) 13 | self.radius = r 14 | self.draw() 15 | 16 | def draw(self): 17 | self.context.arc(self.pos.x, self.pos.y, self.radius, 0, TWO_PI) 18 | 19 | def get_radius(self): 20 | return self.radius 21 | 22 | def get_position(self): 23 | return self.pos 24 | 25 | def set_position(self, _position): 26 | self.pos = _position 27 | 28 | 29 | # Line class 30 | class Line: 31 | def __init__(self, x1, y1, x2, y2, draw=True): 32 | self.context = config.Context 33 | self.p0 = vec2([x1, y1]) 34 | self.p1 = vec2([x2, y2]) 35 | self.id = None 36 | if draw: 37 | self.draw() 38 | 39 | def draw(self): 40 | self.context.move_to(self.p0.x, self.p0.y) 41 | self.context.line_to(self.p1.x, self.p1.y) 42 | 43 | def get_length(self): 44 | return sqrt((self.p1.x - self.p0.x)**2 + (self.p1.y - self.p0.y)**2) 45 | 46 | def set_id(self, id): 47 | self.id = id 48 | 49 | def get_lerp(self, _t): 50 | x = self.p0.x+(self.p1.x-self.p0.x)*_t 51 | y = self.p0.y+(self.p1.y-self.p0.y)*_t 52 | p = vec2([x, y]) 53 | return p 54 | 55 | def get_direction(self): 56 | d = (self.p0 - self.p1) / self.get_length() 57 | return d * -1.0 58 | 59 | 60 | # Particle class built on the Circle class 61 | class Particle(Circle): 62 | def __init__(self, _x, _y, _r): 63 | Circle.__init__(self, _x, _y, _r) 64 | self.vel = vec2([0.0, 0.0]) 65 | self.frc = vec2([0.0, 0.0]) 66 | self.width = 1000 67 | self.height = 1000 68 | 69 | def update(self): 70 | self.vel += self.frc 71 | self.pos += self.vel 72 | 73 | def edges(self): 74 | if self.pos.x <= 0 or self.pos.x >= self.width: 75 | self.vel.x = self.vel.x * -1 76 | 77 | if self.pos.y <= 0 or self.pos.y >= self.height: 78 | self.vel.y = self.vel.y * -1 79 | 80 | def add_force(self, _x, _y, _amt, _limit): 81 | fx, fy = _x * _amt, _y * _amt 82 | fx, fy = clip(fx - self.vel.x, -_limit, _limit), clip(fy - self.vel.y, -_limit, _limit) 83 | self.frc.x += fx 84 | self.frc.y += fy 85 | 86 | 87 | def circle_three_points(A, B, C): 88 | 89 | y_delta_a = B.y - A.y 90 | y_delta_a = B.x - A.x 91 | y_delta_b = C.y - B.y 92 | x_delta_b = C.x - B.x 93 | center = vec2([0.0, 0.0]) 94 | 95 | a_slope = float(y_delta_a / y_delta_a) 96 | b_slope = float(y_delta_b / x_delta_b) 97 | center.x = (a_slope * b_slope * (A.y - C.y) + b_slope * (A.x + B.x) - a_slope * (B.x + C.x)) / (2.0 * (b_slope - a_slope)) 98 | center.y = -1 * (center.x - (A.x + B.x) / 2.0) / a_slope + (A.y + B.y) / 2.0 99 | 100 | radius = hypot(center.x - A.x, center.y - A.y) 101 | return {'center': center, 'radius': radius} 102 | 103 | 104 | def background(r, g, b, a): 105 | config.Context.set_source_rgba(r, g, b, a) 106 | config.Context.paint() 107 | 108 | 109 | def color(r, g, b, a): 110 | config.Context.set_source_rgba(r, g, b, a) 111 | 112 | 113 | def stroke(): 114 | config.Context.stroke() 115 | 116 | 117 | def fill(): 118 | config.Context.fill() 119 | 120 | 121 | def line_width(value): 122 | config.Context.set_line_width(value) 123 | 124 | 125 | def set_line_cap(value): 126 | if value == "LINE_CAP_BUTT": 127 | config.Context.set_line_cap(0) 128 | elif value == "LINE_CAP_ROUND": 129 | config.Context.set_line_cap(1) 130 | elif value == "LINE_CAP_SQUARE": 131 | config.Context.set_line_cap(2) 132 | else: 133 | config.Context.set_line_cap(2) 134 | -------------------------------------------------------------------------------- /graphics/Graphics.py: -------------------------------------------------------------------------------- 1 | from os import environ as env 2 | import graphics.Config as config 3 | from graphics.Context import DrawContext 4 | from graphics.Twitter import twitter_post 5 | 6 | 7 | def setup(width, height, **kwargs): 8 | if config.twitter_post: 9 | check_env_vars() 10 | 11 | config.DrawContext = DrawContext( 12 | width, 13 | height, 14 | kwargs.get('file_format', config.file_format), 15 | kwargs.get('image_folder', config.image_folder), 16 | kwargs.get('script_name', config.script_name), 17 | kwargs.get('open_file', config.open_file) 18 | ) 19 | config.Context = config.DrawContext.context 20 | log_info() 21 | 22 | 23 | def export(): 24 | config.DrawContext.export() 25 | 26 | if config.twitter_post: 27 | filepath = config.DrawContext.get_fullpath() 28 | twitter_post(filepath) 29 | print("INFO: Posted image to twitter") 30 | 31 | 32 | def log_info(): 33 | print("INFO: Generating image for {}".format(config.script_name)) 34 | print("INFO: Images being saved to directory {}".format(config.image_folder)) 35 | 36 | 37 | def check_env_vars(): 38 | if (not env.get('consumer_key') and 39 | not env.get('consumer_secret') and 40 | not env.get('access_token') and 41 | not env.get('access_token_secret')): 42 | print("ERROR: You must export consumer_key, consumer_secret, access_token and access_token_secret environment variables") 43 | exit() 44 | -------------------------------------------------------------------------------- /graphics/Helpers.py: -------------------------------------------------------------------------------- 1 | from os import path, makedirs 2 | from math import pi, hypot 3 | from numpy import where, cross 4 | from subprocess import call 5 | from sys import platform 6 | from .Vector import Vector as vec2 7 | from .Vector import sub_vec, add_vec 8 | 9 | # Useful constants 10 | PI = pi 11 | HALF_PI = PI/2 12 | TWO_PI = pi*2 13 | 14 | 15 | # Useful functions 16 | # Map a value to a new range 17 | def map(value, left_min, left_max, right_min, right_max): 18 | left_span = left_max - left_min 19 | right_span = right_max - right_min 20 | value_scaled = float(value - left_min) / float(left_span) 21 | return right_min + (value_scaled * right_span) 22 | 23 | 24 | # Lerp between two points 25 | def lerp(x1, y1, x2, y2, _amt): 26 | x = x1+(x2-x1)*_amt 27 | y = y1+(y2-y1)*_amt 28 | return vec2([x, y]) 29 | 30 | 31 | # Create a folder if it does not exist 32 | def does_path_exist(folder_path): 33 | if not path.exists(folder_path): 34 | makedirs(folder_path) 35 | 36 | 37 | # Same as range(), but uses floats, requires all arguments! 38 | def frange(start, stop, step): 39 | i = start 40 | while i < stop: 41 | yield i 42 | i += step 43 | 44 | 45 | # Open a file or image 46 | def open_file(f): 47 | opener = "open" if platform == "darwin" else "xdg-open" 48 | call([opener, f]) 49 | 50 | 51 | # Get distance between two points 52 | def dist(x1, y1, x2, y2): 53 | d = hypot(x1 - x2, y1 - y2) 54 | return d 55 | 56 | 57 | # Get length of a vector 58 | def length(p): 59 | return hypot(p[0], p[1]) 60 | 61 | 62 | # Scalar projection on a point 63 | def scalar_projection(p, a, b): 64 | ap = sub_vec(p, a) 65 | ab = sub_vec(b, a) 66 | ab.normalize() 67 | ab *= ap.dot(ab) 68 | point = add_vec(a, ab) 69 | return point 70 | 71 | 72 | # Find the closest point, points should be an array of vec2 73 | def find_closest_point(a, points): 74 | current = None 75 | shortest = None 76 | for p in points: 77 | d = dist(a.x, a.y, p.x, p.y) 78 | if current is None: 79 | current = d 80 | shortest = d 81 | 82 | if d < current: 83 | current = d 84 | shortest = p 85 | return shortest 86 | -------------------------------------------------------------------------------- /graphics/Intersection.py: -------------------------------------------------------------------------------- 1 | from numpy import cross 2 | 3 | 4 | # Is a point on a line segment 5 | def point_on_segment(p, a, b, EPSILON=10.0): 6 | ap = a - p 7 | ab = a - b 8 | c = cross(ap, ab) 9 | if c < -EPSILON and c > EPSILON: 10 | return False 11 | 12 | KAP = ap.dot(ab) 13 | if KAP < 0: 14 | return False 15 | if KAP == 0: 16 | return True 17 | 18 | KAB = ab.dot(ab) 19 | if KAP > KAB: 20 | return False 21 | if KAP == KAB: 22 | return True 23 | 24 | return True 25 | 26 | 27 | # Intersection method stolen from: 28 | # https://algorithmtutor.com/Computational-Geometry/Check-if-two-line-segment-intersect/ 29 | def direction(p1, p2, p3): 30 | return cross(p3 - p1, p2 - p1) 31 | 32 | 33 | def on_segment(p1, p2, p): 34 | return min(p1.x, p2.x) <= p.x <= max(p1.x, p2.x) and \ 35 | min(p1.y, p2.y) <= p.y <= max(p1.y, p2.y) 36 | 37 | 38 | def intersect(a, b): 39 | p1, p2, p3, p4 = a.p0, a.p1, b.p0, b.p1 40 | d1 = direction(p3, p4, p1) 41 | d2 = direction(p3, p4, p2) 42 | d3 = direction(p1, p2, p3) 43 | d4 = direction(p1, p2, p4) 44 | 45 | if ((d1 > 0 and d2 < 0) or (d1 < 0 and d2 > 0)) and \ 46 | ((d3 > 0 and d4 < 0) or (d3 < 0 and d4 > 0)): 47 | return True 48 | 49 | elif d1 == 0 and on_segment(p3, p4, p1): 50 | return True 51 | elif d2 == 0 and on_segment(p3, p4, p2): 52 | return True 53 | elif d3 == 0 and on_segment(p1, p2, p3): 54 | return True 55 | elif d4 == 0 and on_segment(p1, p2, p4): 56 | return True 57 | else: 58 | return False 59 | -------------------------------------------------------------------------------- /graphics/Twitter.py: -------------------------------------------------------------------------------- 1 | import tweepy 2 | import graphics.Config as config 3 | 4 | 5 | def twitter_post(image_location): 6 | auth = tweepy.OAuthHandler(config.consumer_key, config.consumer_secret) 7 | auth.set_access_token(config.access_token, config.access_token_secret) 8 | api = tweepy.API(auth) 9 | 10 | # Post to twitter 11 | img = image_location 12 | api.update_with_media(img, status=config.tweet_status) 13 | -------------------------------------------------------------------------------- /graphics/Vector.py: -------------------------------------------------------------------------------- 1 | from numpy import ndarray, asarray, multiply, add, divide, subtract 2 | from math import sqrt 3 | 4 | 5 | class Vector(ndarray): 6 | def __new__(cls, input_array, info=None): 7 | obj = asarray(input_array).view(cls) 8 | return obj 9 | 10 | @property 11 | def x(self): 12 | return self[0] 13 | 14 | @property 15 | def y(self): 16 | return self[1] 17 | 18 | @x.setter 19 | def x(self, value): 20 | self[0] = value 21 | 22 | @y.setter 23 | def y(self, value): 24 | self[1] = value 25 | 26 | def mag(self): 27 | return sqrt(self.x*self.x + self.y*self.y) 28 | 29 | def normalize(self): 30 | m = self.mag() 31 | if m > 0: 32 | self.x /= m 33 | self.y /= m 34 | 35 | 36 | # Basic vector transformations, they return numpy arrays 37 | def mult_vec(a, b): 38 | return multiply(a, b) 39 | 40 | 41 | def div_vec(a, b): 42 | return divide(where(a != 0, a, 1), where(b != 0, b, 1)) 43 | 44 | 45 | def add_vec(a, b): 46 | return add(a, b) 47 | 48 | 49 | def sub_vec(a, b): 50 | return subtract(a, b) 51 | -------------------------------------------------------------------------------- /graphics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakobGlock/Generative-Art/9104bb97795a4adb8ee5ea997d86d2fd9bfe1505/graphics/__init__.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | noise==1.2.2 2 | numpy==1.17.3 3 | pycairo==1.19.1 4 | tweepy==3.8.0 5 | docopt==0.6.2 6 | -------------------------------------------------------------------------------- /templates/basic.py: -------------------------------------------------------------------------------- 1 | import graphics.Config as config 2 | from graphics.Graphics import setup, export 3 | from graphics.Geometry import Circle, background, color, stroke 4 | 5 | width, height = 1000, 1000 6 | 7 | 8 | def draw(): 9 | # Draw stuff here 10 | background(0.95, 0.95, 0.95, 1.0) 11 | color(0, 0, 0, 1.0) 12 | Circle(width*0.5, height*0.5, 250) 13 | stroke() 14 | 15 | 16 | def main(): 17 | setup(width, height) 18 | draw() 19 | export() 20 | 21 | 22 | if __name__ == '__main__': 23 | main() 24 | --------------------------------------------------------------------------------