diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..656a42f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +graphics/__pycache__ +*.pyc +*.swp +Images/ +venv/ diff --git a/Basic_Example.py b/Basic_Example.py new file mode 100644 index 0000000..d875d6e --- /dev/null +++ b/Basic_Example.py @@ -0,0 +1,34 @@ +import graphics.Config as config +from graphics.Graphics import setup, export +from graphics.Geometry import background, color, Circle, stroke +from random import randint + +width = 2000 +height = 2000 + + +def draw(): + background(0.95, 0.95, 0.95, 1.0) + color(0, 0, 0, 0.01) + + for k in range(100): + step = 100 / 4 + for j in range(0, width+100, 100): + for i in range(0, height+100, 100): + r = randint(-4, 4) + offset_x = step / r if r != 0 else 0 + r = randint(-4, 4) + offset_y = step / r if r != 0 else 0 + Circle(i+offset_x, j+offset_y, 10) + stroke() + + +def main(): + # Pass optional overrides + setup(width, height) + draw() + export() + + +if __name__ == '__main__': + main() diff --git a/Circular.py b/Circular.py index 202a0c4..f4a7197 100644 --- a/Circular.py +++ b/Circular.py @@ -1,27 +1,25 @@ -import cairo +from graphics.Graphics import setup, export +from graphics.Geometry import background, color, stroke +from graphics.Geometry import Line as draw_line +from graphics.Vector import Vector as vec2 import math -import uuid import random -import numpy as np -# Some variables -fileFormat = 'PNG' width, height = 1000, 1000 # Line class class Line: - def __init__(self, p0, dir, id): - self.id = id + def __init__(self, p0, d, _id): + self.id = _id self.p0 = p0 self.p1 = p0 - self.dir = dir + self.dir = d - def draw(self, context): - context.set_source_rgba(0.15, 0.15, 0.15, 1) - context.move_to(self.p0[0], self.p0[1]) - context.line_to(self.p1[0], self.p1[1]) - context.stroke() + def draw(self): + color(0.15, 0.15, 0.15, 1) + draw_line(self.p0[0], self.p0[1], self.p1[0], self.p1[1]) + stroke() def update(self): self.p1 = self.p1 + self.dir @@ -38,113 +36,101 @@ def intersect(self, p2, p3): if denom == 0: return False - intersectX = (B2 * C1 - B1 * C2) / denom - intersectY = (A1 * C2 - A2 * C1) / denom + intersect_x = (B2 * C1 - B1 * C2) / denom + intersect_y = (A1 * C2 - A2 * C1) / denom - rx0 = (intersectX - self.p0[0]) / (self.p1[0] - self.p0[0]) - ry0 = (intersectY - self.p0[1]) / (self.p1[1] - self.p0[1]) - rx1 = (intersectX - p2[0]) / (p3[0] - p2[0]) - ry1 = (intersectY - p2[1]) / (p3[1] - p2[1]) + rx0 = (intersect_x - self.p0[0]) / (self.p1[0] - self.p0[0]) + ry0 = (intersect_y - self.p0[1]) / (self.p1[1] - self.p0[1]) + rx1 = (intersect_x - p2[0]) / (p3[0] - p2[0]) + ry1 = (intersect_y - p2[1]) / (p3[1] - p2[1]) if(((rx0 >= 0 and rx0 <= 1) or (ry0 >= 0 and ry0 <= 1)) and ((rx1 >= 0 and rx1 <= 1) or (ry1 >= 0 and ry1 <= 1))): return True else: return False - def changeDir(self, a): - currentAngle = math.atan2(a[1], a[0]) * 180 / math.pi - newDir = random.randint(-15, 15) - angle = math.radians(currentAngle + newDir) - dir = np.array([math.cos(angle), math.sin(angle)]) - self.dir = dir + def change_dir(self, a): + current_angle = math.atan2(a[1], a[0]) * 180 / math.pi + new_dir = random.randint(-15, 15) + angle = math.radians(current_angle + new_dir) + d = vec2([math.cos(angle), math.sin(angle)]) + self.dir = d - def getLength(self): + def get_length(self): dist = math.hypot(self.p0[0] - self.p1[0], self.p0[1] - self.p1[1]) return dist - def getDirection(self): - dir = (self.p0 - self.p1) / self.getLength() - return dir * -1.0 + def get_direction(self): + d = (self.p0 - self.p1) / self.get_length() + return d * -1.0 - def getDistFromCenter(self, center): + def get_dist_from_center(self, center): dist = math.hypot(self.p1[0] - center[0], self.p1[1] - center[1]) return dist -# Main function -def main(): +def draw(): - context.set_source_rgba(0.95, 0.95, 0.95, 1.0) - context.paint() + background(0.95, 0.95, 0.95, 1.0) - gridSize = 3 + grid_size = 3 border = 40 - xStep = (width-(border*2)) / float(gridSize) - yStep = (height-(border*2)) / float(gridSize) - xOffset = xStep / 2.0 - yOffset = yStep / 2.0 - - for y in range(gridSize): - for x in range(gridSize): - xPos = xStep * x + xOffset + border - yPos = yStep * y + yOffset + border - myLines = [] - numLines = 180 - angle = (2*math.pi) / float(numLines) - centerX = width / 2.0 - centerY = height / 2.0 - center = np.array([xPos, yPos]) - circleSize = (xStep / 2.0) - 10 + x_step = (width-(border*2)) / float(grid_size) + y_step = (height-(border*2)) / float(grid_size) + x_offset = x_step / 2.0 + y_offset = y_step / 2.0 + + for y in range(grid_size): + for x in range(grid_size): + x_pos = x_step * x + x_offset + border + y_pos = y_step * y + y_offset + border + my_lines = [] + num_lines = 180 + angle = (2*math.pi) / float(num_lines) + center_x = width / 2.0 + center_y = height / 2.0 + center = vec2([x_pos, y_pos]) + circle_size = (x_step / 2.0) - 10 order = [] - for i in range(numLines): + for i in range(num_lines): order.append(i) random.shuffle(order) - for i in range(numLines): + for i in range(num_lines): index = order[i] - pos = np.array([ - (math.cos(angle*index) * (circleSize-0.1))+center[0], - (math.sin(angle*index) * (circleSize-0.1))+center[1] + pos = vec2([ + (math.cos(angle*index) * (circle_size-0.1))+center[0], + (math.sin(angle*index) * (circle_size-0.1))+center[1] ]) - dir = (pos - np.array([centerX, centerY])) * -1.0 - myLines.append(Line(pos, dir, i)) - myLines[i].changeDir(dir) - - while myLines[i].getDistFromCenter(center) < circleSize: - myLines[i].update() - - stopDrawing = False - for line in myLines: - if line.id != myLines[i].id: - if myLines[i].intersect(line.p0, line.p1): - stopDrawing = True + dir = (pos - vec2([center_x, center_y])) * -1.0 + my_lines.append(Line(pos, dir, i)) + my_lines[i].change_dir(dir) + + while my_lines[i].get_dist_from_center(center) < circle_size: + my_lines[i].update() + + stop_drawing = False + for line in my_lines: + if line.id != my_lines[i].id: + if my_lines[i].intersect(line.p0, line.p1): + stop_drawing = True break - if stopDrawing: + if stop_drawing: break - for line in myLines: - if line.getLength() > 0: - line.draw(context) + for line in my_lines: + if line.get_length() > 0: + line.draw() + + +def main(): + setup(width, height) + draw() + export() -# Call the main function and save image if __name__ == '__main__': - if fileFormat == 'PNG': - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) - context = cairo.Context(surface) - main() - filename = str(uuid.uuid4().hex[:8]) - surface.write_to_png("Images/Circular/" + filename + ".png") - else: - filename = str(uuid.uuid4().hex[:8]) - surface = cairo.SVGSurface( - "Images/Circular/0-svg/" + filename + ".svg", - width, - height - ) - context = cairo.Context(surface) - main() - surface.finish() + main() diff --git a/Line_Grid.py b/Line_Grid.py index 2befb5a..e53190e 100644 --- a/Line_Grid.py +++ b/Line_Grid.py @@ -1,113 +1,75 @@ -import cairo -import uuid +from graphics.Graphics import setup, export +from graphics.Geometry import Line, background, color, stroke +from graphics.Helpers import map +from graphics.Vector import Vector as vec2 import random import math -import numpy as np # Some variables -fileFormat = 'PNG' width, height = 1000, 1000 -# Line class -class Line: - def __init__(self, p0, p1): - self.p0 = p0 - self.p1 = p1 - - def draw(self, context): - context.set_source_rgba(0, 0, 0, 1) - context.move_to(self.p0[0], self.p0[1]) - context.line_to(self.p1[0], self.p1[1]) - context.stroke() - - def getLerp(self, _t): - x = self.p0[0]+(self.p1[0]-self.p0[0])*_t - y = self.p0[1]+(self.p1[1]-self.p0[1])*_t - p = np.array([x, y]) - return p - - -# Map a value to a new range -def map(value, leftMin, leftMax, rightMin, rightMax): - leftSpan = leftMax - leftMin - rightSpan = rightMax - rightMin - valueScaled = float(value - leftMin) / float(leftSpan) - return rightMin + (valueScaled * rightSpan) - - # Main function -def main(): - - context.set_source_rgba(0.95, 0.95, 0.95, 1.0) - context.paint() - - context.set_source_rgba(0, 0, 0, 1) +def draw(): + background(0.95, 0.95, 0.95, 1.0) + color(0, 0, 0, 1) border = 50 - borderSQ = border*2 - numLines = 18 - yStep = float((height-borderSQ)/numLines) - xStep = float((width-borderSQ)/numLines) - yOffset = yStep/2.0 - xOffset = xStep/2.0 - unOffset = 10 - myLines = [] - - for i in range(numLines): - for j in range(numLines-1): + border_sQ = border*2 + num_lines = 18 + y_step = float((height-border_sQ)/num_lines) + x_step = float((width-border_sQ)/num_lines) + y_offset = y_step/2.0 + x_offset = x_step/2.0 + un_offset = 10 + my_lines = [] + + for i in range(num_lines): + for j in range(num_lines-1): if j == 0: - p1 = np.array([ - xStep*j+xOffset + random.uniform(-xOffset, xOffset) + border, - yStep*i+yOffset + random.uniform(-yOffset, yOffset) + border + p1 = vec2([ + x_step*j+x_offset + random.uniform(-x_offset, x_offset) + border, + y_step*i+y_offset + random.uniform(-y_offset, y_offset) + border ]) - p2 = np.array([ - xStep*(j+1)+xOffset + random.uniform(-xOffset, xOffset) + border, - yStep*i+yOffset + random.uniform(-yOffset, yOffset) + border + p2 = vec2([ + x_step*(j+1)+x_offset + random.uniform(-x_offset, x_offset) + border, + y_step*i+y_offset + random.uniform(-y_offset, y_offset) + border ]) - myLines.append(Line(p1, p2)) + my_lines.append(Line(p1[0], p1[1], p2[0], p2[1])) else: - p1 = np.array([ - myLines[(j+(numLines-1)*i)-1].p1[0], - myLines[(j+(numLines-1)*i)-1].p1[1] + p1 = vec2([ + my_lines[(j+(num_lines-1)*i)-1].p1[0], + my_lines[(j+(num_lines-1)*i)-1].p1[1] ]) - p2 = np.array([ - xStep*(j+1)+xOffset + random.uniform(-xOffset, xOffset) + border, - yStep*i+yOffset + random.uniform(-yOffset, yOffset) + border + p2 = vec2([ + x_step*(j+1)+x_offset + random.uniform(-x_offset, x_offset) + border, + y_step*i+y_offset + random.uniform(-y_offset, y_offset) + border ]) - myLines.append(Line(p1, p2)) + my_lines.append(Line(p1[0], p1[1], p2[0], p2[1])) index = 0 - for i in range(numLines): + for i in range(num_lines): index = index + 1 - for j in range(numLines-1): + for j in range(num_lines-1): if i != 0: - lerpLines = int(map(i, 0, numLines, 1, 12))+1 - for k in range(lerpLines): - p0 = myLines[(j+(numLines-1)*(i-1))].getLerp(math.pow(map(k, 0, lerpLines-1, 0, 1), 1)) - p1 = myLines[(j+(numLines-1)*i)].getLerp(math.pow(map(k, 0, lerpLines-1, 0, 1), 1)) - line = Line(p0, p1) - line.draw(context) + lerp_lines = int(map(i, 0, num_lines, 1, 12))+1 + for k in range(lerp_lines): + p0 = my_lines[(j+(num_lines-1)*(i-1))].get_lerp(math.pow(map(k, 0, lerp_lines-1, 0, 1), 1)) + p1 = my_lines[(j+(num_lines-1)*i)].get_lerp(math.pow(map(k, 0, lerp_lines-1, 0, 1), 1)) + Line(p0[0], p0[1], p1[0], p1[1]).draw() + stroke() - for line in myLines: - line.draw(context) + for line in my_lines: + line.draw() + stroke() + + +def main(): + setup(width, height) + draw() + export() # Call the main function if __name__ == '__main__': - if fileFormat == 'PNG': - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) - context = cairo.Context(surface) - main() - filename = uuid.uuid4().hex[:8] - surface.write_to_png("Images/Line_Grid/" + str(filename) + ".png") - elif fileFormat == 'SVG': - filename = uuid.uuid4().hex[:8] - surface = cairo.SVGSurface( - "Images/Line_Grid/0-svg/" + str(filename) + ".svg", - width, - height - ) - context = cairo.Context(surface) - main() - surface.finish() + main() diff --git a/Line_Walker.py b/Line_Walker.py index f6dbbb7..fd24beb 100644 --- a/Line_Walker.py +++ b/Line_Walker.py @@ -1,51 +1,44 @@ -import cairo +from graphics.Graphics import setup, export +from graphics.Geometry import Line, background, color, stroke +from graphics.Vector import Vector as vec2 import math import random -import uuid -import numpy as np ############################# # This script is a bit slow # ############################# # Variables -fileFormat = 'PNG' width, height = 1000, 1000 -border = 50 -centerX, centerY = width/2.0, height/2.0 +center_x, center_y = width/2.0, height/2.0 +border = 20 # Line Class -class Line: - def __init__(self, p0, dir, id, angle, connect): +class Line(Line): + def __init__(self, x1, y1, x2, y2, id, dir, angle, connect): + super().__init__(x1, y1, x2, y2) + self.p1 = vec2([x1, y1]) self.id = id - self.p0 = p0 - self.p1 = p0 self.dir = dir self.angle = angle self.connect = connect - def draw(self, context): - context.set_source_rgba(0, 0, 0, 1) - context.move_to(self.p0[0], self.p0[1]) - context.line_to(self.p1[0], self.p1[1]) - context.stroke() - def update(self): self.p1 = self.p1 + self.dir - def changeDir(self, a): - currentAngle = math.atan2(a[1], a[0]) * 180 / math.pi - newDir = 90 + def change_dir(self, a): + current_angle = math.atan2(a[1], a[0]) * 180 / math.pi + new_dir = 90 b = random.randint(0, 2) if b == 0: - newDir = 0 + new_dir = 0 elif b == 1: - newDir = 22.5 / 2.0 + new_dir = 22.5 / 2.0 else: - newDir = -(22.5 / 2.0) - angle = math.radians(currentAngle + newDir) - dir = np.array([math.cos(angle), math.sin(angle)]) + new_dir = -(22.5 / 2.0) + angle = math.radians(current_angle + new_dir) + dir = vec2([math.cos(angle), math.sin(angle)]) self.dir = dir def edges(self): @@ -64,104 +57,84 @@ def intersect(self, p2, p3): if denom == 0: return False - intersectX = (B2 * C1 - B1 * C2) / denom - intersectY = (A1 * C2 - A2 * C1) / denom + intersect_x = (B2 * C1 - B1 * C2) / denom + intersect_y = (A1 * C2 - A2 * C1) / denom - rx0 = (intersectX - self.p0[0]) / (self.p1[0] - self.p0[0]) - ry0 = (intersectY - self.p0[1]) / (self.p1[1] - self.p0[1]) - rx1 = (intersectX - p2[0]) / (p3[0] - p2[0]) - ry1 = (intersectY - p2[1]) / (p3[1] - p2[1]) + rx0 = (intersect_x - self.p0[0]) / (self.p1[0] - self.p0[0]) + ry0 = (intersect_y - self.p0[1]) / (self.p1[1] - self.p0[1]) + rx1 = (intersect_x - p2[0]) / (p3[0] - p2[0]) + ry1 = (intersect_y - p2[1]) / (p3[1] - p2[1]) if(((rx0 >= 0 and rx0 <= 1) or (ry0 >= 0 and ry0 <= 1)) and ((rx1 >= 0 and rx1 <= 1) or (ry1 >= 0 and ry1 <= 1))): return True else: return False - def getLength(self): - dist = math.hypot(self.p0[0] - self.p1[0], self.p0[1] - self.p1[1]) - return dist - - def getLerp(self, _t): - x = self.p0[0]+(self.p1[0]-self.p0[0])*_t - y = self.p0[1]+(self.p1[1]-self.p0[1])*_t - p = np.array([x, y]) - return p - - def getDirection(self): - dir = (self.p0 - self.p1) / self.getLength() - return dir * -1.0 - # Main function -def main(): - context.set_source_rgba(0.95, 0.95, 0.95, 1.0) - context.paint() +def draw(): + background(0.95, 0.95, 0.95, 1.0) + color(0, 0, 0, 1) walkers = [] index = 0 angle = (math.pi*2) / 90.0 - pos = np.array([100, 100]) - dir = np.array([1, 1]) - walkers.append(Line(pos, dir, 0, angle * 180.0 / math.pi, -1)) - lineLength = 20 + pos = vec2([100, 100]) + dir = vec2([1, 1]) + x, y = pos[0], pos[1] + walkers.append(Line(x, y, x, y, 0, dir, angle * 180.0 / math.pi, -1)) + line_length = 20 depth = -1 for i in range(3000): if i != 0: - if stopDrawing is True: + if stop_drawing is True: connect = random.randint(0, i-1) - pos = walkers[connect].getLerp(random.uniform(0.25, 0.75)) + pos = walkers[connect].get_lerp(random.uniform(0.25, 0.75)) dist = math.hypot( - walkers[i-1].p0[0] - centerX, - walkers[i-1].p0[1] - centerY + walkers[i-1].p0[0] - center_x, + walkers[i-1].p0[1] - center_y ) - c = np.array([centerX, centerY]) + c = vec2([center_x, center_y]) dir = ((walkers[i-1].p0 - c) / dist) * -1.0 - walkers.append(Line(pos, dir, i, angle * 180.0 / math.pi, connect)) - walkers[i].changeDir(walkers[i].dir) - lineLength = 10 + x, y = pos[0], pos[1] + walkers.append(Line(x, y, x, y, i, dir, angle * 180.0 / math.pi, connect)) + walkers[i].change_dir(walkers[i].dir) + line_length = 10 index = index + 1 else: pos = walkers[i-1].p1 dir = walkers[i-1].dir - walkers.append(Line(pos, dir, i, angle * 180 / math.pi, -1)) - walkers[i].changeDir(walkers[i-1].dir) + x, y = pos[0], pos[1] + walkers.append(Line(x, y, x, y, i, dir, angle * 180.0 / math.pi, -1)) + walkers[i].change_dir(walkers[i-1].dir) if index > 400: break - stopDrawing = False + stop_drawing = False - while walkers[i].getLength() < lineLength: + while walkers[i].get_length() < line_length: walkers[i].update() for w in walkers: if walkers[i].id != w.id and walkers[i-1].id != w.id and walkers[i].connect != w.id: if walkers[i].intersect(w.p0, w.p1) or walkers[i].edges(): - stopDrawing = True + stop_drawing = True break - if stopDrawing: + if stop_drawing: break - walkers[i].draw(context) + walkers[i].draw() + stroke() + + +def main(): + setup(width, height) + draw() + export() -# Call the main function and save an image if __name__ == '__main__': - if fileFormat == 'PNG': - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) - context = cairo.Context(surface) - main() - fileName = uuid.uuid4().hex[:8] - surface.write_to_png("Images/Line_Walker/"+str(fileName)+".png") - elif fileFormat == 'SVG': - fileName = uuid.uuid4().hex[:8] - surface = cairo.SVGSurface( - "Images/Line_Walker/0-svg/"+str(fileName)+".svg", - width, - height - ) - context = cairo.Context(surface) - main() - context.finish() + main() diff --git a/Magnetic_Flow.py b/Magnetic_Flow.py index b12e99f..ad9108b 100644 --- a/Magnetic_Flow.py +++ b/Magnetic_Flow.py @@ -1,60 +1,56 @@ -import cairo +from graphics.Graphics import setup, export +from graphics.Geometry import background, color, stroke, Line, line_width import math import random -import uuid - -# Set to SVG to export an SVG file -fileFormat = 'PNG' # Some variables -height = 1000 -width = 1000 -gridSize = 100 -border, magBorder = 50, 450 -stepX, stepY = (width//gridSize), (height//gridSize) +height, width = 1000, 1000 +grid_size = 100 +border, mag_border = 50, 450 +step_x, step_y = (width//grid_size), (height//grid_size) # Particle class class Particle: - def __init__(self, x, y, velX, velY): + def __init__(self, x, y, vel_x, vel_y): self.x = x self.y = y - self.velX = velX - self.velY = velY - self.frcX = 0 - self.frcY = 0 + self.vel_x = vel_x + self.vel_y = vel_y + self.frc_x = 0 + self.frc_y = 0 self.lx, self.ly = self.x, self.y - self.drawStroke = True + self.draw_stroke = True def update(self): - self.x = self.x + self.frcX - self.y = self.y + self.frcY + self.x = self.x + self.frc_x + self.y = self.y + self.frc_y - self.velX = self.velX * 0.9 - self.velY = self.velY * 0.9 + self.vel_x = self.vel_x * 0.9 + self.vel_y = self.vel_y * 0.9 - self.x = self.x + self.velX - self.y = self.y + self.velY + self.x = self.x + self.vel_x + self.y = self.y + self.vel_y def edges(self): if self.x <= 50 or self.x >= width-50 or self.y <= 50 or self.y >= height-50: - self.drawStroke = False + self.draw_stroke = False else: - self.drawStroke = True + self.draw_stroke = True - def resetForce(self): - self.frcX = 0 - self.frcY = 0 + def reset_force(self): + self.frc_x = 0 + self.frc_y = 0 - def setForce(self, fx, fy): - self.frcX = self.frcX + fx - self.frcY = self.frcY + fy + def set_force(self, fx, fy): + self.frc_x = self.frc_x + fx + self.frc_y = self.frc_y + fy - def setLastPos(self): + def set_last_pos(self): self.lx, self.ly = self.x, self.y - def calculateForce(self, mx, my, mp): + def calculate_force(self, mx, my, mp): dy = mx - self.x dx = my - self.y angle = math.atan2(dy, dx) * mp @@ -62,13 +58,11 @@ def calculateForce(self, mx, my, mp): sy = math.cos(angle) return [sx, sy] - def draw(self, context): - if self.drawStroke is not False: - context.set_line_width(0.9) - context.set_source_rgba(0, 0, 0, 1) - context.move_to(self.lx, self.ly) - context.line_to(self.x, self.y) - context.stroke() + def draw(self): + if self.draw_stroke is not False: + line_width(0.9) + Line(self.lx, self.ly, self.x, self.y) + stroke() # Magnet Class @@ -79,21 +73,20 @@ def __init__(self, x, y, pole): self.p = pole -# Main function -def main(): +def draw(): - context.set_source_rgba(0.95, 0.95, 0.95, 1) - context.paint() + background(0.95, 0.95, 0.95, 1.0) + color(0.0, 0.0, 0.0, 1.0) magnets = [] - myParticles = [] - numMagnets = random.randint(2, 8) - sumX, sumY = 0, 0 + my_particles = [] + num_magnets = random.randint(2, 8) + sum_x, sum_y = 0, 0 sums = 0 - print("Number of Magnets: " + str(numMagnets)) + print("Number of Magnets: " + str(num_magnets)) - for m in range(numMagnets): + for m in range(num_magnets): pole = 1 if random.uniform(0, 1) < 0.5: pole = -1 @@ -104,52 +97,42 @@ def main(): pole )) - startNum = 360 - a = (math.pi*2)/startNum + start_num = 360 + a = (math.pi*2)/start_num for x in range(100, width-100, (width-200)//1): for y in range(100, height-100, (height-200)//1): - for i in range(startNum): + for i in range(start_num): xx = x + (math.sin(a*i)*250) + ((width-200)//2) yy = y + (math.cos(a*i)*250) + ((height-200)//2) vx = random.uniform(-1, 1)*0.5 vy = random.uniform(-1, 1)*0.5 - myParticles.append(Particle(xx, yy, vx, vy)) + my_particles.append(Particle(xx, yy, vx, vy)) - for p in myParticles: + for p in my_particles: for t in range(1000): for m in magnets: - sums = p.calculateForce(m.x, m.y, m.p*4) - sumX = sumX + sums[0] - sumY = sumY + sums[1] + sums = p.calculate_force(m.x, m.y, m.p*4) + sum_x = sum_x + sums[0] + sum_y = sum_y + sums[1] - sumX = sumX / len(magnets) - sumY = sumY / len(magnets) + sum_x = sum_x / len(magnets) + sum_y = sum_y / len(magnets) - p.resetForce() - p.setForce(sumX, sumY) + p.reset_force() + p.set_force(sum_x, sum_y) p.update() p.edges() if t % 8 == 0: - p.draw(context) - p.setLastPos() + p.draw() + p.set_last_pos() + + +def main(): + setup(width, height) + draw() + export() -# Call the main function if __name__ == '__main__': - if fileFormat == 'PNG': - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) - context = cairo.Context(surface) - main() - filename = uuid.uuid4().hex[:8] - surface.write_to_png('Images/Magnetic_Flow/' + str(filename) + '.png') - elif fileFormat == 'SVG': - filename = uuid.uuid4().hex[:8] - surface = cairo.SVGSurface( - 'Images/Magnetic_Flow/0-svg/' + str(filename) + '.svg', - width, - height - ) - context = cairo.Context(surface) - main() - surface.finish() + main() diff --git a/Mosaic_Circles.py b/Mosaic_Circles.py index 288698a..6fa9d68 100644 --- a/Mosaic_Circles.py +++ b/Mosaic_Circles.py @@ -1,130 +1,64 @@ -import cairo +import graphics.Config as config +from graphics.Graphics import setup, export +from graphics.Geometry import Line, background, color, stroke +from graphics.Helpers import map +from graphics.Generators import NoiseLoop import math import random -import uuid -import numpy as np -from noise import pnoise2 -# Some variables -fileFormat = 'PNG' width, height = 1000, 1000 -centerX, centerY = width/2, height/2 -TWO_PI = math.pi*2.0 -# Noise generator class -class NoiseLoop: - def __init__(self, diameter, min, max): - self.diameter = diameter - self.min = min - self.max = max - self.noiseSeed = random.randint(0, 10000) +def draw(): + background(0.95, 0.95, 0.95, 1.0) + color(0, 0, 0, 1) - def getValue(self, a): - x = self.map(math.cos(math.radians(a)), -1, 1, 0, self.diameter) - y = self.map(math.sin(math.radians(a)), -1, 1, 0, self.diameter) - r = pnoise2(x+self.noiseSeed, y+self.noiseSeed) - return self.map(r, -1, 1, self.min, self.max) + grid_x, grid_y = 1, 1 + x_step, y_step = width//grid_x, height//grid_y + x_offset, y_offset = x_step//2, y_step//2 - def map(self, value, leftMin, leftMax, rightMin, rightMax): - leftSpan = leftMax - leftMin - rightSpan = rightMax - rightMin - valueScaled = float(value - leftMin) / float(leftSpan) - return rightMin + (valueScaled * rightSpan) - - def setNoiseSeed(self, _offset): - self.noiseSeed = _offset - - -# Line class -class Line: - def __init__(self, context, x1, y1, x2, y2): - self.context = context - self.p0 = np.array([x1, y1]) - self.p1 = np.array([x2, y2]) - self.id = None - - def draw(self): - self.context.move_to(self.p0[0], self.p0[1]) - self.context.line_to(self.p1[0], self.p1[1]) - - def getLength(self): - return sqrt((self.p1[0] - self.p0[0])**2 + (self.p1[1] - self.p0[1])**2) - - def setId(self, id): - self.id = id - - -# Map a value to a new range -def map(value, leftMin, leftMax, rightMin, rightMax): - leftSpan = leftMax - leftMin - rightSpan = rightMax - rightMin - valueScaled = float(value - leftMin) / float(leftSpan) - return rightMin + (valueScaled * rightSpan) - - -def main(): - - context.set_source_rgba(0.95, 0.95, 0.95, 1.0) - context.paint() - - context.set_source_rgba(0.0, 0.0, 0.0, 1.0) - gridX, gridY = 1, 1 - xStep, yStep = width//gridX, height//gridY - xOffset, yOffset = xStep//2, yStep//2 - - nLoop = [] + n_loop = [] offset = [] - numLayers = 40 - s = (xOffset // numLayers) - - for xxx in range(gridX): - for yyy in range(gridY): - centerX = xxx * xStep + xOffset - centerY = yyy * yStep + yOffset - for l in range(numLayers): + num_layers = 40 + s = (x_offset // num_layers) + + for xxx in range(grid_x): + for yyy in range(grid_y): + center_x = xxx * x_step + x_offset + center_y = yyy * y_step + y_offset + for layer in range(num_layers): offset = random.randint(0, 360) - nLoop.append(NoiseLoop(map(l, 0, numLayers, 1, 4), s*l, s*l+s)) - numPoints = 360 - for i in range(numPoints): - r = nLoop[l].getValue(i) - x = r * math.cos(math.radians(i)) + centerX - y = r * math.sin(math.radians(i)) + centerY - context.line_to(x, y) - context.close_path() - context.stroke() + n_loop.append(NoiseLoop(map(layer, 0, num_layers, 1, 4), s*layer, s*layer+s)) + num_points = 360 + for i in range(num_points): + r = n_loop[layer].get_value(i) + x = r * math.cos(math.radians(i)) + center_x + y = r * math.sin(math.radians(i)) + center_y + # Think of a better way to do this + config.Context.line_to(x, y) + config.Context.close_path() + stroke() + + if layer != 0: + num_lines = map(layer, 0, num_layers, 16, 2) + num_points = 360 / num_lines + for i in range(int(num_points)): + r = n_loop[layer].get_value(i*num_lines+offset) + x = r * math.cos(math.radians(i*num_lines+offset)) + center_x + y = r * math.sin(math.radians(i*num_lines+offset)) + center_y + + r = n_loop[layer-1].get_value(i*num_lines+offset) + xx = r * math.cos(math.radians(i*num_lines+offset)) + center_x + yy = r * math.sin(math.radians(i*num_lines+offset)) + center_y + Line(xx, yy, x, y) + stroke() - if l != 0: - numLines = map(l, 0, numLayers, 16, 2) - numPoints = 360 / numLines - for i in range(int(numPoints)): - r = nLoop[l].getValue(i*numLines+offset) - x = r * math.cos(math.radians(i*numLines+offset)) + centerX - y = r * math.sin(math.radians(i*numLines+offset)) + centerY - r = nLoop[l-1].getValue(i*numLines+offset) - xx = r * math.cos(math.radians(i*numLines+offset)) + centerX - yy = r * math.sin(math.radians(i*numLines+offset)) + centerY - line = Line(context, xx, yy, x, y) - line.draw() - context.stroke() +def main(): + setup(width, height) + draw() + export() -# Call the main function if __name__ == '__main__': - if fileFormat == 'PNG': - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) - context = cairo.Context(surface) - main() - fileName = uuid.uuid4().hex[:8] - surface.write_to_png("Images/Mosaic_Circles/" + str(fileName) + ".png") - elif fileFormat == 'SVG': - fileName = uuid.uuid4().hex[:8] - surface = cairo.SVGSurface( - "Images/Mosaic_Circles/0-svg/" + str(fileName) + ".svg", - width, - height - ) - context = cairo.Context(surface) - main() - surface.finish() + main() diff --git a/Parallel_Lines.py b/Parallel_Lines.py index c84a444..99cb015 100644 --- a/Parallel_Lines.py +++ b/Parallel_Lines.py @@ -1,102 +1,65 @@ -import cairo -import numpy as np +from graphics.Graphics import setup, export +from graphics.Geometry import Line, background, color, stroke +from graphics.Helpers import map +from graphics.Vector import Vector as vec2 import random import math -import uuid + # Some variables -fileFormat = 'PNG' width, height = 1000, 1000 -# Line class -class Line: - def __init__(self, p0, p1): - self.p0 = p0 - self.p1 = p1 - - def draw(self, context): - context.set_source_rgba(0, 0, 0, 1) - context.move_to(self.p0[0], self.p0[1]) - context.line_to(self.p1[0], self.p1[1]) - context.stroke() - - def getLerp(self, _t): - x = self.p0[0]+(self.p1[0]-self.p0[0])*_t - y = self.p0[1]+(self.p1[1]-self.p0[1])*_t - p = np.array([x, y]) - return p - - -# Map values to a new range -def map(value, leftMin, leftMax, rightMin, rightMax): - leftSpan = leftMax - leftMin - rightSpan = rightMax - rightMin - valueScaled = float(value - leftMin) / float(leftSpan) - return rightMin + (valueScaled * rightSpan) - - # Main function -def main(): - context.set_source_rgba(0.95, 0.95, 0.95, 1.0) - context.paint() - - context.set_source_rgba(0, 0, 0, 1) - - connectorLines = [] - - numLinesX = 20 - numLinesY = 3 - xStep = float((width-100) // numLinesX) - yStep = float(height // numLinesY) - xOffset = (xStep // 2) + 50 - yOffset = yStep // 2 - - for h in range(numLinesY): - for i in range(numLinesX): - x = xStep * i + xOffset - y = yStep * h + yOffset +def draw(): + background(0.95, 0.95, 0.95, 1.0) + color(0, 0, 0, 1) + + connector_lines = [] + + num_lines_x = 20 + num_lines_y = 3 + x_step = float((width-100) // num_lines_x) + y_step = float(height // num_lines_y) + x_offset = (x_step // 2) + 50 + y_offset = y_step // 2 + + for h in range(num_lines_y): + for i in range(num_lines_x): + x = x_step * i + x_offset + y = y_step * h + y_offset yy = random.randint(25, 100) - p0 = np.array([ - x + random.uniform(-(xStep//2)+10, (xStep//2)+10), + p0 = vec2([ + x + random.uniform(-(x_step//2)+10, (x_step//2)+10), random.uniform(y-50, y+50) + yy ]) - p1 = np.array([ - x + random.uniform(-(xStep//2)+10, (xStep//2)+10), + p1 = vec2([ + x + random.uniform(-(x_step//2)+10, (x_step//2)+10), random.uniform(y-50, y+50) - yy ]) - connectorLines.append(Line(p0, p1)) + connector_lines.append(Line(p0[0], p0[1], p1[0], p1[1])) if i != 0: - numLines = 21 - l1 = i+numLinesX*h - l2 = (i+numLinesX*h)+1 + num_lines = 21 + l1 = i+num_lines_x*h + l2 = (i+num_lines_x*h)+1 while l2 == l1: - l2 = random.randint(0, len(connectorLines)) + l2 = random.randint(0, len(connector_lines)) - for j in range(numLines): - p0 = connectorLines[l1-1].getLerp(math.pow(map(j, 0, numLines, 0, 1), 1)) - p1 = connectorLines[l2-1].getLerp(math.pow(map(j, 0, numLines, 0, 1), 1)) - line = Line(p0, p1) - line.draw(context) + for j in range(num_lines+1): + p0 = connector_lines[l1-1].get_lerp(math.pow(map(j, 0, num_lines, 0, 1), 1)) + p1 = connector_lines[l2-1].get_lerp(math.pow(map(j, 0, num_lines, 0, 1), 1)) + Line(p0[0], p0[1], p1[0], p1[1]) + stroke() + + +def main(): + setup(width, height) + draw() + export() -# Call the main function and save an image +# Call the main function if __name__ == '__main__': - if fileFormat == 'PNG': - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) - context = cairo.Context(surface) - main() - filename = uuid.uuid4().hex[:8] - surface.write_to_png("Images/Parallel_Lines/" + str(filename) + ".png") - elif fileFormat == 'SVG': - filename = uuid.uuid4().hex[:8] - surface = cairo.SVGSurface( - "Images/Parallel_Lines/0-svg/" + str(filename) + ".svg", - width, - height - ) - context = cairo.Context(surface) - main() - surface.finish() + main() diff --git a/Vertical_Lines.py b/Vertical_Lines.py index 9f824d6..3873013 100644 --- a/Vertical_Lines.py +++ b/Vertical_Lines.py @@ -1,49 +1,49 @@ -import cairo +from graphics.Graphics import setup, export +from graphics.Geometry import background, color, stroke +from graphics.Geometry import Line as draw_line +from graphics.Vector import Vector as vec2 import math -import uuid import random import numpy as np -# Variables -fileFormat = 'PNG' +# Some variables width, height = 1000, 1000 -# Line class -class lineSegment: +class Line: def __init__(self, p0, dir, id): self.id = id self.p0 = p0 self.p1 = p0 self.dir = dir - self.intersect = np.array([0.0, 0.0]) + self.intersect = vec2([0.0, 0.0]) self.count = 0 - def draw(self, context): - context.set_source_rgba(0, 0, 0, 1) - context.move_to(self.p0[0], self.p0[1]) - context.line_to(self.p1[0], self.p1[1]) - context.stroke() + def draw(self): + color(0.0, 0.0, 0.0, 1.0) + draw_line(self.p0[0], self.p0[1], self.p1[0], self.p1[1]) + stroke() - def extendLine(self): + def extend_line(self): self.p1 = self.p1 + self.dir - def changeDir(self): + def change_dir(self): self.count = self.count + 1 if self.count % 40 == 0: angle = math.radians(random.randint(45, 135)) - dir = np.array([math.sin(angle), math.cos(angle)]) + dir = vec2([math.sin(angle), math.cos(angle)]) self.dir = dir return True else: return False def edges(self): - if self.p1[0] >= width or self.p1[0] < 0 or self.p1[1] < 0 or self.p1[1] >= height-50: + if self.p1[0] >= width or self.p1[0] < 0 or \ + self.p1[1] < 0 or self.p1[1] >= height-50: return True - def lineIntersection(self, p2, p3): + def line_intersect(self, p2, p3): A1 = self.p1[1] - self.p0[1] B1 = self.p0[0] - self.p1[0] C1 = A1 * self.p0[0] + B1 * self.p0[1] @@ -55,24 +55,25 @@ def lineIntersection(self, p2, p3): if denom == 0: return False - intersectX = (B2 * C1 - B1 * C2) / denom - intersectY = (A1 * C2 - A2 * C1) / denom + intersect_x = (B2 * C1 - B1 * C2) / denom + intersect_y = (A1 * C2 - A2 * C1) / denom - rx0 = (intersectX - self.p0[0]) / (self.p1[0] - self.p0[0]) - ry0 = (intersectY - self.p0[1]) / (self.p1[1] - self.p0[1]) - rx1 = (intersectX - p2[0]) / (p3[0] - p2[0]) - ry1 = (intersectY - p2[1]) / (p3[1] - p2[1]) + rx0 = (intersect_x - self.p0[0]) / (self.p1[0] - self.p0[0]) + ry0 = (intersect_y - self.p0[1]) / (self.p1[1] - self.p0[1]) + rx1 = (intersect_x - p2[0]) / (p3[0] - p2[0]) + ry1 = (intersect_y - p2[1]) / (p3[1] - p2[1]) - if(((rx0 >= 0 and rx0 <= 1) or (ry0 >= 0 and ry0 <= 1)) and ((rx1 >= 0 and rx1 <= 1) or (ry1 >= 0 and ry1 <= 1))): - self.intersect = np.array([intersectX, intersectY]) + if(((rx0 >= 0 and rx0 <= 1) or (ry0 >= 0 and ry0 <= 1)) and \ + ((rx1 >= 0 and rx1 <= 1) or (ry1 >= 0 and ry1 <= 1))): + self.intersect = vec2([intersect_x, intersect_y]) return True else: return False - def getIntersect(self): + def get_intersect(self): return self.intersect - def getClosetPoint(self, p): + def get_closest_point(self, p): a = np.linalg.norm(self.p0-p) b = np.linalg.norm(self.p1-p) @@ -82,90 +83,78 @@ def getClosetPoint(self, p): self.p1 = p -# Helper function -def getDirection(): - numAngles = 2 - r = int(random.uniform(0, numAngles)) - angleStep = [0, 45] +def get_direction(): + num_angles = 2 + r = int(random.uniform(0, num_angles)) + angle_step = [0, 45] - for i in range(numAngles): + for i in range(num_angles): if i == r: - angle = math.radians(angleStep[i]) + angle = math.radians(angle_step[i]) - dir = np.array([math.sin(angle), math.cos(angle)]) - return dir + dirs = vec2([math.sin(angle), math.cos(angle)]) + return dirs -# Main function -def main(): - context.set_source_rgba(0.95, 0.95, 0.95, 1) - context.paint() +def draw(): + background(0.95, 0.95, 0.95, 1.0) - numWalkers = random.randint(100, 200) + num_walkers = random.randint(100, 200) walkers = [] - xStep = float((width-100)) / numWalkers + x_step = float((width-100)) / num_walkers amt = random.uniform(0.075, 0.15) - amtStep = random.randint(15, 80) - startDist = random.randint(10, 150) + amt_step = random.randint(15, 80) + start_dist = random.randint(10, 150) index = 0 count = 0 - for i in range(numWalkers): - x = float((xStep * i) + 50.0) - pos = np.array([float(x), float(50.0)]) + for i in range(num_walkers): + x = float((x_step * i) + 50.0) + pos = vec2([float(x), float(50.0)]) angle = math.radians(0) - dir = np.array([math.sin(angle), math.cos(angle)]) - walkers.append(lineSegment(pos, dir, i)) + dirs = vec2([math.sin(angle), math.cos(angle)]) + walkers.append(Line(pos, dirs, i)) walk = True while walk: - if count % amtStep == 0: + if count % amt_step == 0: r = random.uniform(0, 1) - if r < amt and walkers[index].p0[1] > startDist: - dir = getDirection() - walkers.append(lineSegment(walkers[index].p1, dir, i)) + if r < amt and walkers[index].p0[1] > start_dist: + dirs = get_direction() + walkers.append(Line(walkers[index].p1, dirs, i)) index = index + 1 else: angle = math.radians(0) - dir = np.array([math.sin(angle), math.cos(angle)]) - walkers.append(lineSegment(walkers[index].p1, dir, i)) + dirs = vec2([math.sin(angle), math.cos(angle)]) + walkers.append(Line(walkers[index].p1, dirs, i)) index = index + 1 - walkers[index].extendLine() + walkers[index].extend_line() - hitLine = False + hit_line = False for w in walkers: if walkers[index].id != w.id: - intersect = walkers[index].lineIntersection(w.p0, w.p1) + intersect = walkers[index].line_intersect(w.p0, w.p1) if intersect: - hitLine = True + hit_line = True - hitEdge = walkers[index].edges() - if hitEdge or hitLine: + hit_edge = walkers[index].edges() + if hit_edge or hit_line: walk = False count = count + 1 index = index + 1 for walker in walkers: - walker.draw(context) + walker.draw() + + +def main(): + setup(width, height) + draw() + export() -# Call the main function and save an image +# Call the main function if __name__ == '__main__': - if fileFormat == 'PNG': - surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) - context = cairo.Context(surface) - main() - fileName = uuid.uuid4().hex[:8] - surface.write_to_png('Images/Vertical_Lines/' + str(fileName) + '.png') - elif fileFormat == 'SVG': - fileName = uuid.uuid4().hex[:8] - surface = cairo.SVGSurface( - 'Images/Vertical_Lines/0-svg/' + str(fileName) + '.svg', - width, - height - ) - context = cairo.Context(surface) - main() - context.finish() + main() diff --git a/generate b/generate new file mode 100755 index 0000000..8ece1fd --- /dev/null +++ b/generate @@ -0,0 +1,128 @@ +#! venv/bin/python + +""" +Generate - Tool for running scripts + +Usage: + generate project (new | delete) + generate artwork new [--number=] [--open] [--svg] [--twitter] + generate artwork random [--number=] [--open] [--svg] [--twitter] + generate artwork all [--open] [--svg] + generate list + +Options: + -h --help Show this screen. + -n --number= Number of images to generate [default: 1]. + -o --open Open the file, in an image viewer (Linux only) [default: False]. + --svg Create an SVG file [default: False]. + --twitter Post to output image to Twitter [default: False]. +""" + +from docopt import docopt +import subprocess +import glob +import random +import os +import shutil + +python_path = 'venv/bin/python' +dashes = "".join(["-" for x in range(0, 81)]) + + +def print_msg(typ, msg): + print(dashes) + print(" ".join([typ + ":", msg])) + print(dashes) + + +def run_script(filename, open_file, file_format, twitter_post): + subprocess.call([python_path, filename, open_file, file_format, twitter_post]) + + +def check_filename(args): + filename = args[''] + if not filename.endswith(".py"): + print_msg("ERROR", "Filename must end with .py") + exit() + + elif os.path.isfile(filename) and not args['delete'] and not args['artwork']: + print_msg("ERROR", "File already exists, pick another name") + exit() + + elif not os.path.isfile(filename) and args['delete']: + print_msg("ERROR", "File {} does not exist, pick a valid name".format(filename)) + exit() + + return filename + + +def projects(args): + filename = check_filename(args) + if args['new']: + shutil.copy2("templates/basic.py", filename) + print_msg("INFO", "Generated new project {}".format(filename)) + + elif args['delete']: + print("") + x = input("WARNING: Are you REALLY sure you want to delete {} and all images (e.g. Y|N or y|n)? ".format(filename)) + if x == "Y" or x == "y": + print_msg("INFO", "Deleting script and related image folder") + os.remove(filename) + + f = os.path.splitext(filename)[0] + path = "Images/{}".format(f) + if os.path.isdir(path): + shutil.rmtree(path) + else: + print_msg("ERROR", "Directory {} does not exist, skipping".format(path)) + else: + exit() + + +def artworks(args): + filename = None + if args['']: + filename = check_filename(args) + number = int(args['--number']) + open_file = str(args['--open']) + file_format = str(args['--svg']) + twitter_post = str(args['--twitter']) + + if filename and args['new']: + for i in range(0, number): + run_script(filename, open_file, file_format, twitter_post) + + elif args['random']: + for i in range(0, number): + files = glob.glob('*.py') + filename = random.choice(files) + run_script(filename, open_file, file_format, twitter_post) + + elif args['all']: + files = glob.glob('*.py') + for filename in files: + run_script(filename, open_file, file_format, twitter_post) + + +def list_projects(args): + files = glob.glob('*.py') + print_msg("INFO", "List of Projects") + for filename in files: + print(filename) + print(dashes) + + +def main(args): + if args['project']: + projects(args) + + elif args['artwork']: + artworks(args) + + elif args['list']: + list_projects(args) + + +if __name__ == '__main__': + args = docopt(__doc__, version='Generate 0.0.1') + main(args) diff --git a/graphics/Config.py b/graphics/Config.py new file mode 100644 index 0000000..38d584c --- /dev/null +++ b/graphics/Config.py @@ -0,0 +1,21 @@ +import sys +import os + +args = sys.argv + +# This is our drawing Context +Context = None + +# Some default variables +file_format = 'SVG' if eval(args[2]) else 'PNG' +open_file = True if eval(args[1]) else False +image_folder = "/Images" +script_name = args[0] + +# Variables for posting to Twitter +twitter_post = True if eval(args[3]) else False +consumer_key = os.environ.get('consumer_key') +consumer_secret = os.environ.get('consumer_secret') +access_token = os.environ.get('access_token') +access_token_secret = os.environ.get('access_token_secret') +tweet_status = '#Generative #Art #CreativeCoding #Python #Cornwall #Bot' diff --git a/graphics/Context.py b/graphics/Context.py new file mode 100644 index 0000000..bb78320 --- /dev/null +++ b/graphics/Context.py @@ -0,0 +1,84 @@ +import cairo +from uuid import uuid4 +from .Helpers import does_path_exist, open_file +from os import path +from datetime import datetime + + +class DrawContext: + def __init__(self, width, height, file_format, filepath, project_folder, open_bool): + self.open_bool = open_bool + self.width = width + self.height = height + self.file_format = file_format + self.filename = self.generate_filename() + self.filepath = filepath + self.project_folder = "/" + project_folder[:-3] + self.cwd = self.set_cwd() + self.fullpath = self.cwd + "/" + self.filename + "." + self.file_format.lower() + self.init() + + def init(self): + does_path_exist(self.cwd) + + if self.file_format == 'PNG': + self.cairo_context = self.setup_png() + elif self.file_format == 'SVG': + self.cairo_context = self.setup_svg() + + def setup_png(self): + self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.width, self.height) + return cairo.Context(self.surface) + + def export_png(self): + self.surface.write_to_png(self.fullpath) + print("INFO: Saving file to {}".format(self.fullpath)) + if self.open_bool: + print("INFO: Opening file {}".format(self.fullpath)) + open_file(self.fullpath) + + def setup_svg(self): + self.surface = cairo.SVGSurface(self.fullpath, self.width, self.height) + return cairo.Context(self.surface) + + def export_svg(self): + self.surface.finish() + print("INFO: Saving file to {}".format(self.fullpath)) + if self.open_bool: + print("INFO: Opening file {}".format(self.fullpath)) + open_file(self.fullpath) + + def export(self): + if self.file_format == "PNG": + self.export_png() + elif self.file_format == "SVG": + self.export_svg() + + @property + def context(self): + return self.cairo_context + + @context.setter + def context(self, context): + self.context = context + + def get_file_name(self): + return self.filename + + def get_fullpath(self): + return self.fullpath + + def generate_filename(self): + now = datetime.now() + timestamp = now.strftime("%Y%d%m-%H%M%S") + unique_id = uuid4().hex[:8] + return str(timestamp + "-" + unique_id) + + def set_cwd(self): + if self.file_format == "PNG": + return path.dirname(path.realpath(__file__))[:-9] + self.filepath + self.project_folder + elif self.file_format == "SVG": + return path.dirname(path.realpath(__file__))[:-9] + self.filepath + self.project_folder + "/0-svg" + else: + print("ERROR: Choose a valid file format: PNG|SVG") + exit(0) diff --git a/graphics/Generators.py b/graphics/Generators.py new file mode 100644 index 0000000..5fee713 --- /dev/null +++ b/graphics/Generators.py @@ -0,0 +1,55 @@ +from .Vector import Vector as vec2 +from noise import pnoise2 +from random import randint, choices +from math import cos +from math import sin +from math import radians + + +# Noise loop generator +class NoiseLoop: + def __init__(self, diameter, _min, _max): + self.diameter = diameter + self.min = _min + self.max = _max + self.noise_seed = randint(0, 10000) + + def get_value(self, a): + x = self.map(cos(radians(a)), -1, 1, 0, self.diameter) + y = self.map(sin(radians(a)), -1, 1, 0, self.diameter) + r = pnoise2(x+self.noise_seed, y+self.noise_seed) + return self.map(r, -1, 1, self.min, self.max) + + def map(self, value, left_min, left_max, right_min, right_max): + left_span = left_max - left_min + right_span = right_max - right_min + value_scaled = float(value - left_min) / float(left_span) + return right_min + (value_scaled * right_span) + + def set_noise_seed(self, _offset): + self.noise_seed = _offset + + +# Random walker +class RandomWalker: + def __init__(self, x, y, size): + self.pos = vec2([x, y]) + self.prev_pos = vec2([x, y]) + self.size = size + self.direction = vec2([0, 0]) + + def walk(self, d, w): + self.set_direction(d, w) + self.pos.x += self.direction.x * self.size + self.pos.y += self.direction.y * self.size + + def set_direction(self, d, w): + directions = d + weights = w + self.direction = choices(directions, weights=weights, k=1)[0] + + def step_size(self, s, r=None): + if r is None: + self.size = s + else: + self.size = randint(s, r) diff --git a/graphics/Geometry.py b/graphics/Geometry.py new file mode 100644 index 0000000..5ae4a99 --- /dev/null +++ b/graphics/Geometry.py @@ -0,0 +1,133 @@ +import graphics.Config as config +from numpy import clip +from math import hypot, sqrt +from .Helpers import TWO_PI +from .Vector import Vector as vec2 + + +# Circle class +class Circle: + def __init__(self, x, y, r): + self.context = config.Context + self.pos = vec2([x, y]) + self.radius = r + self.draw() + + def draw(self): + self.context.arc(self.pos.x, self.pos.y, self.radius, 0, TWO_PI) + + def get_radius(self): + return self.radius + + def get_position(self): + return self.pos + + def set_position(self, _position): + self.pos = _position + + +# Line class +class Line: + def __init__(self, x1, y1, x2, y2, draw=True): + self.context = config.Context + self.p0 = vec2([x1, y1]) + self.p1 = vec2([x2, y2]) + self.id = None + if draw: + self.draw() + + def draw(self): + self.context.move_to(self.p0.x, self.p0.y) + self.context.line_to(self.p1.x, self.p1.y) + + def get_length(self): + return sqrt((self.p1.x - self.p0.x)**2 + (self.p1.y - self.p0.y)**2) + + def set_id(self, id): + self.id = id + + def get_lerp(self, _t): + x = self.p0.x+(self.p1.x-self.p0.x)*_t + y = self.p0.y+(self.p1.y-self.p0.y)*_t + p = vec2([x, y]) + return p + + def get_direction(self): + d = (self.p0 - self.p1) / self.get_length() + return d * -1.0 + + +# Particle class built on the Circle class +class Particle(Circle): + def __init__(self, _x, _y, _r): + Circle.__init__(self, _x, _y, _r) + self.vel = vec2([0.0, 0.0]) + self.frc = vec2([0.0, 0.0]) + self.width = 1000 + self.height = 1000 + + def update(self): + self.vel += self.frc + self.pos += self.vel + + def edges(self): + if self.pos.x <= 0 or self.pos.x >= self.width: + self.vel.x = self.vel.x * -1 + + if self.pos.y <= 0 or self.pos.y >= self.height: + self.vel.y = self.vel.y * -1 + + def add_force(self, _x, _y, _amt, _limit): + fx, fy = _x * _amt, _y * _amt + fx, fy = clip(fx - self.vel.x, -_limit, _limit), clip(fy - self.vel.y, -_limit, _limit) + self.frc.x += fx + self.frc.y += fy + + +def circle_three_points(A, B, C): + + y_delta_a = B.y - A.y + y_delta_a = B.x - A.x + y_delta_b = C.y - B.y + x_delta_b = C.x - B.x + center = vec2([0.0, 0.0]) + + a_slope = float(y_delta_a / y_delta_a) + b_slope = float(y_delta_b / x_delta_b) + 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)) + center.y = -1 * (center.x - (A.x + B.x) / 2.0) / a_slope + (A.y + B.y) / 2.0 + + radius = hypot(center.x - A.x, center.y - A.y) + return {'center': center, 'radius': radius} + + +def background(r, g, b, a): + config.Context.set_source_rgba(r, g, b, a) + config.Context.paint() + + +def color(r, g, b, a): + config.Context.set_source_rgba(r, g, b, a) + + +def stroke(): + config.Context.stroke() + + +def fill(): + config.Context.fill() + + +def line_width(value): + config.Context.set_line_width(value) + + +def set_line_cap(value): + if value == "LINE_CAP_BUTT": + config.Context.set_line_cap(0) + elif value == "LINE_CAP_ROUND": + config.Context.set_line_cap(1) + elif value == "LINE_CAP_SQUARE": + config.Context.set_line_cap(2) + else: + config.Context.set_line_cap(2) diff --git a/graphics/Graphics.py b/graphics/Graphics.py new file mode 100644 index 0000000..505545b --- /dev/null +++ b/graphics/Graphics.py @@ -0,0 +1,43 @@ +from os import environ as env +import graphics.Config as config +from graphics.Context import DrawContext +from graphics.Twitter import twitter_post + + +def setup(width, height, **kwargs): + if config.twitter_post: + check_env_vars() + + config.DrawContext = DrawContext( + width, + height, + kwargs.get('file_format', config.file_format), + kwargs.get('image_folder', config.image_folder), + kwargs.get('script_name', config.script_name), + kwargs.get('open_file', config.open_file) + ) + config.Context = config.DrawContext.context + log_info() + + +def export(): + config.DrawContext.export() + + if config.twitter_post: + filepath = config.DrawContext.get_fullpath() + twitter_post(filepath) + print("INFO: Posted image to twitter") + + +def log_info(): + print("INFO: Generating image for {}".format(config.script_name)) + print("INFO: Images being saved to directory {}".format(config.image_folder)) + + +def check_env_vars(): + if (not env.get('consumer_key') and + not env.get('consumer_secret') and + not env.get('access_token') and + not env.get('access_token_secret')): + print("ERROR: You must export consumer_key, consumer_secret, access_token and access_token_secret environment variables") + exit() diff --git a/graphics/Helpers.py b/graphics/Helpers.py new file mode 100644 index 0000000..c12d5dd --- /dev/null +++ b/graphics/Helpers.py @@ -0,0 +1,85 @@ +from os import path, makedirs +from math import pi, hypot +from numpy import where, cross +from subprocess import call +from sys import platform +from .Vector import Vector as vec2 +from .Vector import sub_vec, add_vec + +# Useful constants +PI = pi +HALF_PI = PI/2 +TWO_PI = pi*2 + + +# Useful functions +# Map a value to a new range +def map(value, left_min, left_max, right_min, right_max): + left_span = left_max - left_min + right_span = right_max - right_min + value_scaled = float(value - left_min) / float(left_span) + return right_min + (value_scaled * right_span) + + +# Lerp between two points +def lerp(x1, y1, x2, y2, _amt): + x = x1+(x2-x1)*_amt + y = y1+(y2-y1)*_amt + return vec2([x, y]) + + +# Create a folder if it does not exist +def does_path_exist(folder_path): + if not path.exists(folder_path): + makedirs(folder_path) + + +# Same as range(), but uses floats, requires all arguments! +def frange(start, stop, step): + i = start + while i < stop: + yield i + i += step + + +# Open a file or image +def open_file(f): + opener = "open" if platform == "darwin" else "xdg-open" + call([opener, f]) + + +# Get distance between two points +def dist(x1, y1, x2, y2): + d = hypot(x1 - x2, y1 - y2) + return d + + +# Get length of a vector +def length(p): + return hypot(p[0], p[1]) + + +# Scalar projection on a point +def scalar_projection(p, a, b): + ap = sub_vec(p, a) + ab = sub_vec(b, a) + ab.normalize() + ab *= ap.dot(ab) + point = add_vec(a, ab) + return point + + +# Find the closest point, points should be an array of vec2 +def find_closest_point(a, points): + current = None + shortest = None + for p in points: + d = dist(a.x, a.y, p.x, p.y) + if current is None: + current = d + shortest = d + + if d < current: + current = d + shortest = p + return shortest diff --git a/graphics/Intersection.py b/graphics/Intersection.py new file mode 100644 index 0000000..4976209 --- /dev/null +++ b/graphics/Intersection.py @@ -0,0 +1,58 @@ +from numpy import cross + + +# Is a point on a line segment +def point_on_segment(p, a, b, EPSILON=10.0): + ap = a - p + ab = a - b + c = cross(ap, ab) + if c < -EPSILON and c > EPSILON: + return False + + KAP = ap.dot(ab) + if KAP < 0: + return False + if KAP == 0: + return True + + KAB = ab.dot(ab) + if KAP > KAB: + return False + if KAP == KAB: + return True + + return True + + +# Intersection method stolen from: +# https://algorithmtutor.com/Computational-Geometry/Check-if-two-line-segment-intersect/ +def direction(p1, p2, p3): + return cross(p3 - p1, p2 - p1) + + +def on_segment(p1, p2, p): + return min(p1.x, p2.x) <= p.x <= max(p1.x, p2.x) and \ + min(p1.y, p2.y) <= p.y <= max(p1.y, p2.y) + + +def intersect(a, b): + p1, p2, p3, p4 = a.p0, a.p1, b.p0, b.p1 + d1 = direction(p3, p4, p1) + d2 = direction(p3, p4, p2) + d3 = direction(p1, p2, p3) + d4 = direction(p1, p2, p4) + + if ((d1 > 0 and d2 < 0) or (d1 < 0 and d2 > 0)) and \ + ((d3 > 0 and d4 < 0) or (d3 < 0 and d4 > 0)): + return True + + elif d1 == 0 and on_segment(p3, p4, p1): + return True + elif d2 == 0 and on_segment(p3, p4, p2): + return True + elif d3 == 0 and on_segment(p1, p2, p3): + return True + elif d4 == 0 and on_segment(p1, p2, p4): + return True + else: + return False diff --git a/graphics/Twitter.py b/graphics/Twitter.py new file mode 100644 index 0000000..19c25f0 --- /dev/null +++ b/graphics/Twitter.py @@ -0,0 +1,12 @@ +import tweepy +import graphics.Config as config + + +def twitter_post(image_location): + auth = tweepy.OAuthHandler(config.consumer_key, config.consumer_secret) + auth.set_access_token(config.access_token, config.access_token_secret) + api = tweepy.API(auth) + + # Post to twitter + img = image_location + api.update_with_media(img, status=config.tweet_status) diff --git a/graphics/Vector.py b/graphics/Vector.py new file mode 100644 index 0000000..db50080 --- /dev/null +++ b/graphics/Vector.py @@ -0,0 +1,50 @@ +from numpy import ndarray, asarray, multiply, add, divide, subtract +from math import sqrt + + +class Vector(ndarray): + def __new__(cls, input_array, info=None): + obj = asarray(input_array).view(cls) + return obj + + @property + def x(self): + return self[0] + + @property + def y(self): + return self[1] + + @x.setter + def x(self, value): + self[0] = value + + @y.setter + def y(self, value): + self[1] = value + + def mag(self): + return sqrt(self.x*self.x + self.y*self.y) + + def normalize(self): + m = self.mag() + if m > 0: + self.x /= m + self.y /= m + + +# Basic vector transformations, they return numpy arrays +def mult_vec(a, b): + return multiply(a, b) + + +def div_vec(a, b): + return divide(where(a != 0, a, 1), where(b != 0, b, 1)) + + +def add_vec(a, b): + return add(a, b) + + +def sub_vec(a, b): + return subtract(a, b) diff --git a/graphics/__init__.py b/graphics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4c328a0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +noise==1.2.2 +numpy==1.17.3 +pycairo==1.19.1 +tweepy==3.8.0 +docopt==0.6.2 diff --git a/templates/basic.py b/templates/basic.py new file mode 100644 index 0000000..c86bd59 --- /dev/null +++ b/templates/basic.py @@ -0,0 +1,23 @@ +import graphics.Config as config +from graphics.Graphics import setup, export +from graphics.Geometry import Circle, background, color, stroke + +width, height = 1000, 1000 + + +def draw(): + # Draw stuff here + background(0.95, 0.95, 0.95, 1.0) + color(0, 0, 0, 1.0) + Circle(width*0.5, height*0.5, 250) + stroke() + + +def main(): + setup(width, height) + draw() + export() + + +if __name__ == '__main__': + main()