Skip to content

Instantly share code, notes, and snippets.

@a5kin
Last active March 7, 2020 00:21
Show Gist options
  • Save a5kin/d06e97dd9afb715c0e8a7dc444b189ca to your computer and use it in GitHub Desktop.
Save a5kin/d06e97dd9afb715c0e8a7dc444b189ca to your computer and use it in GitHub Desktop.
Procedural generation of circular patterns
#!/usr/bin/python3
__author__ = "Andrey Zamaraev (a5kin)"
import math
import random
import cairo
import numpy as np
CANVAS_WIDTH, CANVAS_HEIGHT = 1000, 1000
LINE_WIDTH = 2
NUM_CLONES = 23
BACKGROUND_COLOR = (0, 0, 0)
FULL_COLOR1 = (1, 0.7, 0.1, 0.7)
FULL_COLOR2 = (1, 1, 1, 0.05)
EMPTY_COLOR1 = (0, 0, 0, 0.1)
EMPTY_COLOR2 = (0, 0, 0, 0.1)
RADIUS1 = 0.3
RADIUS2 = RADIUS1 * 0.8
MU1 = 0.75 # random.random()
MU2 = 0.6 # random.random()
SIGMA1 = 0.21 # random.random() / 4 + 0.2
SIGMA2 = 0.2 # random.random() / 4 + 0.2
NUM_POINTS1 = 666
NUM_POINTS2 = 666
class EntropyCanvas:
def __init__(self, width, height, color):
"""Create canvas."""
self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
self.ctx = cairo.Context(self.surface)
self.ctx.scale(width, height)
self.background(color)
def background(self, color):
"""Create canvas background."""
pat = cairo.SolidPattern(*color)
self.ctx.rectangle(0, 0, 1, 1)
self.ctx.set_source(pat)
self.ctx.fill()
def shift(self, x, deviation):
"""Return deviated value."""
return x + random.gauss(0.5, 0.2) * deviation * 2 - deviation / 2
def distort(self, path, deviation):
"""Distort given path."""
interpolated_path = []
for p1, p2 in zip(path[:-1], path[1:]):
segment_len = math.hypot(p2[0] - p1[0], p2[1] - p1[1])
num_subsegments = max(1, segment_len // 0.01)
segment = np.linspace(p1, p2, num_subsegments)
interpolated_path += segment.tolist()[:-1]
interpolated_path.append(path[-1])
res_path = []
for x, y in interpolated_path:
x, y = self.shift(x, deviation / 10), self.shift(y, deviation / 10)
res_path.append((x, y))
return res_path
def line(self, path, color, width):
"""Draw humanized line."""
dev = 0.01
dpath = [(self.shift(x, dev), self.shift(y, dev)) for x, y in path]
for p1, p2 in zip(dpath[:-1], dpath[1:]):
subpath = self.distort((p1, p2), deviation=dev)
self.ctx.move_to(*subpath[0])
for x, y in subpath[1:]:
self.ctx.line_to(x, y)
self.ctx.set_source_rgba(*color)
self.ctx.set_line_width(width)
self.ctx.stroke()
def multiline(self, path, color1, color2, num_clones, width):
"""Draw line several times with variations."""
colors = np.linspace(color1, color2, num_clones).tolist()
for i in range(num_clones):
color = colors[i]
self.line(path, color, width)
def save_to(self, filename):
"""Save result to a file."""
self.surface.write_to_png(filename)
def generate_path(r, mu, sigma, num_points):
"""Generate circular path."""
angles = [0] + [random.gauss(mu, sigma) * math.pi for _ in range(num_points)]
angles += [2 * math.pi - a for a in angles[::-1]]
points = [(r * math.sin(fi) + 0.5, r * math.cos(fi) + 0.5) for fi in angles]
# points = [(0.5 * r * math.sin(fi) ** 3 + 0.5, r * math.cos(fi) + 0.5) for fi in angles]
path = points + [points[0]]
return path
def main():
canvas = EntropyCanvas(CANVAS_WIDTH, CANVAS_HEIGHT, BACKGROUND_COLOR)
line_width = LINE_WIDTH / CANVAS_WIDTH
path = generate_path(RADIUS1, MU1, SIGMA1, NUM_POINTS1)
canvas.multiline(path, FULL_COLOR1, FULL_COLOR2, NUM_CLONES, line_width)
path = generate_path(RADIUS2, MU2, SIGMA2, NUM_POINTS2)
canvas.multiline(path, EMPTY_COLOR1, EMPTY_COLOR2, NUM_CLONES, line_width)
canvas.save_to("frame.png")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment