Title: Weaving Threads of Code: My Journey into Algorithmic Art

Welcome to the inaugural post in my Substack series on algorithmic art! Over the past few months, I’ve been exploring creative ways to combine geometry, randomness, and color into generative artworks. My Instagram page @new_pathway has been filling up with images from various experiments, but I’ve never really shown how the code behind those pieces works—until now.

Today, I want to take you through my “yarn” algorithm: a playful system that draws entangled strands across a canvas. Along the way, you’ll see the project structure, a couple of code snippets, and a comedic rounding “gotcha” that almost drove me nuts.


1. The Concept: Yarn, Threads, and Weaving

The initial idea for this artwork was to create lines that look like yarn—each line made up of smaller threads, each thread further split into segments that weave under and over one another. Visually, it suggests layers of fiber crossing a page, fraying at the ends, and gently shifting in color.

Key components:

  1. Yarn: The overall piece of string, described by a curve (often a cubic Bézier) that runs from left to right (or top to bottom) on the canvas.
  2. Threads: Each yarn is subdivided into multiple threads, each thread following a sub-section of that main curve.
  3. Segments: We then slice each thread into smaller “over/under” pieces to simulate weaving or twisting. Each segment gets a z-index so we know which bit is on top.

I decided to store all these segments in a single list, sorted by z_index. That made it easy when the time came to draw them: I just told my program, “Here are all the segments. Sort them and draw from bottom to top.”


2. Structure of the Project

I like to separate my logic into distinct files for clarity:

  • yarn.py: Holds the Yarn and Thread classes, plus a few helper functions for slicing Bézier curves, offsetting control points, and so on.
  • main.py: The entry point where I set up a canvas, create a bunch of Yarn objects, and then ask each Yarn to generate its threads and segments. Finally, it draws them (or saves to an image).
  • utils.py (Optional): Some of my random color generation or geometric helper methods end up here to keep them out of the main classes.

Snippet: Yarn and Thread Setup

python

Copy code

class Yarn:     
	def __init__(self, yarn_path, **kwargs):         
		self.yarn_path = yarn_path  # A single CubicBezier
		self.number_of_threads = kwargs.get("number_of_threads", 10)
		self.number_of_twists = kwargs.get("number_of_twists", 20)
		self.thickness_of_thread = kwargs.get("thickness_of_thread", 1.5)
		self.base_colour = kwargs.get("base_colour", "#0000FF")
		self.colour_variation = kwargs.get("colour_variation", 0.2)
		self.threads = []      
	def generate_threads(self):    
		# picks random start/end sub-paths and creates Thread objects         
		# ...         
		return self.threads  
class Thread:     
	def __init__(self, colour, thickness):         
		self.colour = colour         
		self.thickness = thickness         
		self.segments = []          
	def build_segments(self):         
		# splits the Thread's path into smaller weaving segments         
		# assigns each segment a z_index         
		pass      
	def add_frays(self):         
		# branches out small 'fray' curves at the ends         
		pass

You can see the full file in my GitHub repo @galiquis if you want all the details.


3. Colour Variations and the Hue-Saturation Trick

Because each Yarn can contain multiple threads, I wanted gentle colour variety—like different shades of teal or burnt umber. One technique I used was to define three “base” colours:

  • Dark Teal (#004B49)
  • Sea Blue (#0082C9)
  • Burnt Umber (#8A3324)

Then I do a tiny shift in the HSL (Hue, Saturation, Lightness) for each new Yarn or Thread, so the final piece has subtle variations. It’s a lovely way to keep to a palette but still get plenty of variety.

Short snippet showing that idea:

python

Copy code

import colorsys, random  
 
def vary_color_hsl(hex_color, max_hue_shift=0.05, max_sat_shift=0.1):     
	# parse rgb from hex     
	# convert to hls     
	# shift hue & saturation     
	# clamp & convert back to hex     
	return new_hex_color

4. The Rounding “Gotcha”

Midway through generating 300+ yarns, I hit a problem: everything jammed into the top 10% of the canvas, looking nothing like the sprawling fiber tapestry I’d imagined. After re-checking my random offsets and control points, I realized the culprit was a tiny integer division bug:

spacing = canvas_size[1] // (number_of_yarns - 1)

Because this used //, Python gave me integer spacing—so for large numbers of yarns, spacing was rounding down to 1 or even 0! That meant each successive yarn was practically on top of the last. Swapping to:

spacing = canvas_size[1] / (number_of_yarns - 1)

… solved it. My yarn lines finally spread gracefully across the height of the canvas. It’s a perfect example of how a seemingly tiny math slip can produce large-scale artistic chaos.


5. Drawing the Final Artwork

When everything is said and done, I gather all the segments from each Yarn into one master list, sort by z_index, and then feed them into my drawing routine. In my case, I’m using aggdraw or sometimes svgwrite:

all_segments.sort(key=lambda seg: seg["z_index"]) 
draw_shapes_dict(canvas, all_segments, (0,0))

Each segment dictionary includes:

  • svg_path: The curve geometry (a CubicBezier)
  • stroke_colour: The thread color
  • stroke_width: The thickness
  • fill_opacity: Usually zero (so it’s just a line)

The real fun, of course, is playing with these threads: letting them weave “over” or “under,” offsetting control points to add gentle loops, or sprinkling in random frays at each end.


Closing Thoughts & What’s Next

That’s the story of how my “yarn” algorithm came to life: from the simple idea of entwined threads to a code structure that slices paths, weaves segments, and carefully manages color. I learned:

  1. Diligence with division (no more // for me!).
  2. It’s often the subtle geometry—like offset curves and small frays—that bring an otherwise simple line to life.
  3. Working in consistent, small classes (Yarn, Thread, Segment) makes it easy to expand and experiment further.

I’ll be sharing more about my creative coding journeys in future posts, so stay tuned if you like reading about color algorithms, fractal curves, or random glitchy art. Don’t forget to pop over to my Instagram @new_pathway for more generative visuals. And if you’d like to see the complete code, head over to my GitHub @galiquis.

Feel free to shoot me questions, or suggestions for the next experiment. Thanks for reading, and happy weaving!