Last active
August 2, 2025 19:42
-
-
Save bharadwaj-raju/694c340fb44799eb501782de402e080c to your computer and use it in GitHub Desktop.
Generative art involving lines being disturbed by sums of sines.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# inspired by https://www.youtube.com/shorts/iX7HJxrrpGA | |
# check it out | |
from __future__ import annotations | |
import random | |
import math | |
from dataclasses import dataclass | |
from enum import Enum | |
from PIL import Image, ImageDraw | |
MIN_PERTURB_WIDTH = 10 | |
MAX_PERTURB_WIDTH = 20 | |
MIN_PERTURB_AMPLI = 5 | |
MAX_PERTURB_AMPLI = 15 | |
AMPLITUDE_DAMP = 0.5 | |
MIN_PERTURB_PER_LINE = 1 | |
MAX_PERTURB_PER_LINE = 3 | |
IMAGE_SIZE = 600 | |
VERT_PADDING = 50 | |
LINES_GAP = 8 | |
FG = (255, 255, 255, 255) | |
BG = (0, 0, 0, 255) | |
class Direction(Enum): | |
Up = 1 | |
Down = 2 | |
@dataclass | |
class Perturbation: | |
start: int | |
width: float | |
amplitude: float | |
direction: Direction | |
@classmethod | |
def generate(cls: type[Perturbation], width: int) -> Perturbation: | |
return cls( | |
start=random.randint(0, width), | |
width=random.randint(MIN_PERTURB_WIDTH, MAX_PERTURB_WIDTH), | |
amplitude=random.randint(MIN_PERTURB_AMPLI, MAX_PERTURB_AMPLI), | |
direction=random.choice((Direction.Up, Direction.Down)), | |
) | |
def dampen(self, stages: int = 1) -> Perturbation: | |
if self.amplitude == 0: | |
return self | |
return Perturbation( | |
start=self.start - (2 * stages), | |
width=self.width + (2 * stages), | |
amplitude=self.amplitude - (AMPLITUDE_DAMP * stages), | |
direction=self.direction, | |
) | |
def dy(self, x: int) -> float: | |
if x < self.start: | |
return 0.0 | |
if (x - self.start) / self.width >= math.pi: | |
return 0.0 | |
return self.amplitude * math.sin( | |
(0.0 if self.direction == Direction.Up else math.pi) | |
+ (x - self.start) / self.width | |
) | |
with Image.new("RGBA", (IMAGE_SIZE, IMAGE_SIZE), color=BG) as im: | |
draw = ImageDraw.Draw(im) | |
perturbations: list[Perturbation] = [] | |
perturbations_per_line: list[list[Perturbation]] = [] | |
for _ in range(VERT_PADDING, IMAGE_SIZE - VERT_PADDING, LINES_GAP): | |
perturbations_per_line.append( | |
[ | |
Perturbation.generate(IMAGE_SIZE) | |
for _ in range( | |
random.randint(MIN_PERTURB_PER_LINE, MAX_PERTURB_PER_LINE) | |
) | |
] | |
) | |
for line_no, y_start in enumerate( | |
range(VERT_PADDING, IMAGE_SIZE - VERT_PADDING, LINES_GAP) | |
): | |
perturbations = [] | |
for i, ps in enumerate(perturbations_per_line): | |
for p in ps: | |
perturbations.append(p.dampen(abs(i - line_no))) | |
for x in range(IMAGE_SIZE): | |
dy = sum(p.dy(x) for p in perturbations) | |
draw.point((x, y_start + dy), fill=FG) | |
im.show() | |
# im.save(sys.stdout.buffer, "PNG") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment