Skip to content

Instantly share code, notes, and snippets.

@bharadwaj-raju
Last active August 2, 2025 19:42
Show Gist options
  • Save bharadwaj-raju/694c340fb44799eb501782de402e080c to your computer and use it in GitHub Desktop.
Save bharadwaj-raju/694c340fb44799eb501782de402e080c to your computer and use it in GitHub Desktop.
Generative art involving lines being disturbed by sums of sines.
# 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