Creating Smooth Random Shapes in Python: A Step-by-Step Guide

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:

  1. Generate random points in a circular pattern
  2. Connect these points using a smooth curve (spline interpolation)
  3. 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 and height: The dimensions of our output image
  • num_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:

  1. We find the center of our image
  2. Calculate a base radius (1/4 of the smaller dimension)
  3. Generate evenly spaced angles around a circle
  4. For each angle, we:
    • Add random variation to the radius using noise_factor
    • Calculate x, y coordinates using trigonometry

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:

  1. We close the shape by adding the first point to the end
  2. splprep creates a B-spline representation of our points
  3. splev evaluates the spline at 100 evenly spaced points

The parameters:

  • s=0: No smoothing, pass exactly through our points
  • k=3: Use cubic splines for smooth curves
  • per=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:

  1. Convert our points into the format PIL expects
  2. Generate a random RGB color
  3. 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:

python random shape generation
generate 2d shapes with Python
python for shape generation

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

Leave a Reply

Your email address will not be published. Required fields are marked *