In this tutorial, I will show you another use case of Python for graphic design. I’ll walk you through a Python script that creates smooth, random shapes using spline interpolation. The result? Beautiful, fluid shapes perfect for modern design elements.
The Basic Concept
My approach follows these key steps:
- Generate random points in a circular pattern
- Connect these points using a smooth curve (spline interpolation)
- Fill the resulting shape with a random color
Let’s dive into how it all works!
Required Libraries
from PIL import Image, ImageDraw # For creating and drawing on images
import numpy as np # For mathematical operations
import random # For generating random numbers
from scipy.interpolate import splprep, splev # For spline interpolation
The star of the show here is scipy.interpolate
, which provides the tools we need for creating smooth curves between our points.
Understanding the Main Function
def generate_smooth_blob(width=800, height=600, num_points=8, noise_factor=0.8):
# Create a new image with white background
image = Image.new('RGB', (width, height), 'white')
draw = ImageDraw.Draw(image)
# Generate initial points in a circle
center_x, center_y = width // 2, height // 2
base_radius = min(width, height) // 4
# Generate random points around a circle
angles = np.linspace(0, 2*np.pi, num_points, endpoint=False)
points = []
for angle in angles:
# Add random variation to the radius
radius = base_radius * (1 + noise_factor * (random.random() - 0.5))
x = center_x + radius * np.cos(angle)
y = center_y + radius * np.sin(angle)
points.append([x, y])
# Close the shape by repeating the first point
points.append(points[0])
points = np.array(points)
# Fit a B-spline to the points
tck, u = splprep([points[:, 0], points[:, 1]], s=0, k=3, per=1)
# Generate more points along the spline for smoothness
u_new = np.linspace(0, 1, 100)
smooth_points = splev(u_new, tck)
# Convert to list of tuples for PIL
smooth_points = list(zip(smooth_points[0], smooth_points[1]))
# Draw the shape
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
draw.polygon(smooth_points, fill=color)
return image
Our function takes four parameters:
width
andheight
: The dimensions of our output imagenum_points
: How many control points to use (more points = more complex shapes)noise_factor
: Controls how “wild” our shape gets (0 = perfect circle, 1 = very random)
Step 1: Setting Up the Canvas
image = Image.new('RGB', (width, height), 'white')
draw = ImageDraw.Draw(image)
We create a white canvas using PIL (Python Imaging Library). Think of this as our digital piece of paper.
Step 2: Generating Base Points
center_x, center_y = width // 2, height // 2
base_radius = min(width, height) // 4
angles = np.linspace(0, 2*np.pi, num_points, endpoint=False)
points = []
for angle in angles:
radius = base_radius * (1 + noise_factor * (random.random() - 0.5))
x = center_x + radius * np.cos(angle)
y = center_y + radius * np.sin(angle)
points.append([x, y])
Here’s where the magic begins:
- We find the center of our image
- Calculate a base radius (1/4 of the smaller dimension)
- Generate evenly spaced angles around a circle
- For each angle, we:
- Add random variation to the radius using
noise_factor
- Calculate x, y coordinates using trigonometry
- Add random variation to the radius using
The noise_factor
is crucial – it determines how much the radius can vary from our base circle. A value of 0.8 means the radius can vary by ±40% of the base radius.
Step 3: Smoothing with Spline Interpolation
points.append(points[0]) # Close the shape
points = np.array(points)
tck, u = splprep([points[:, 0], points[:, 1]], s=0, k=3, per=1)
u_new = np.linspace(0, 1, 100)
smooth_points = splev(u_new, tck)
This is where we transform our angular collection of points into a smooth shape:
- We close the shape by adding the first point to the end
splprep
creates a B-spline representation of our pointssplev
evaluates the spline at 100 evenly spaced points
The parameters:
s=0
: No smoothing, pass exactly through our pointsk=3
: Use cubic splines for smooth curvesper=1
: Make it periodic (connect end to start smoothly)
Step 4: Drawing the Final Shape
smooth_points = list(zip(smooth_points[0], smooth_points[1]))
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
draw.polygon(smooth_points, fill=color)
Finally, we:
- Convert our points into the format PIL expects
- Generate a random RGB color
- Draw and fill our shape
Using the Code
width, height = 600, 400
image = generate_smooth_blob(width, height)
image.save("random_shape.png")
Below are some shapes generated by this code:
Each time you run this code, you’ll get a unique, smooth, organic shape. Try experimenting with different parameters:
- Increase
num_points
for more complex shapes - Adjust
noise_factor
for different levels of randomness - Change dimensions for different aspect ratios