# Drawing an Archimedean spiral using Pillow

From Rosetta Code:

The Archimedean spiral is a spiral named after the Greek mathematician Archimedes. It can be described by the equation: $$r=a+b\theta$$ with real numbers $a$ and $b$.

Here is my attempt to draw it in Python (using Pillow):

"""This module creates an Archimdean Spiral."""

from math import cos, sin, pi
from PIL import Image, ImageDraw

def translate(point, screen_size):
"""
Takes a point and converts it to the appropriate coordinate system.
Note that PIL uses upper left as 0, we want the center.
Args:
point (real, real): A point in space.
screen_size (int): Size of an N x N screen.
Returns:
(real, real): Translated point for Pillow coordinate system.
"""
return point[0] + screen_size / 2, point[1] + screen_size / 2

def draw_spiral(a, b, img, step=0.5, loops=5):
"""
Draw the Archimdean spiral defined by:
r = a + b*theta
Args:
a (real): First parameter
b (real): Second parameter
img (Image): Image to write spiral to.
step (real): How much theta should increment by. (default: 0.5)
loops (int): How many times theta should loop around. (default: 5)
"""
draw = ImageDraw.Draw(img)
theta = 0.0
r = a
prev_x = int(r*cos(theta))
prev_y = int(r*sin(theta))
while theta < 2 * loops * pi:
theta += step
r = a + b*theta
# Draw pixels, but remember to convert to Cartesian:
x = int(r*cos(theta))
y = int(r*sin(theta))
draw.line(translate((prev_x, prev_y), img.size[0]) +
translate((x, y), img.size[0]), fill=1)
prev_x = x
prev_y = y

if __name__ == '__main__':
IMAGE_SIZE = 300, 300
img = Image.new('1', IMAGE_SIZE)
draw_spiral(1, 2, img)
img.save('spiral.png')


The program outputs this image:

With the default values, the spiral does not look very smooth: it's easy to see that it's been drawn using a series of straight-line segments. That's because the step in $\theta$ is quite large — 0.5 radians is more than 28°. Setting step=0.1 and loops=10 is an improvement:

But you can still see deviations from smoothness in various places. The problem here is that the step in $\theta$ is constant, but as $r$ gets bigger this becomes a bigger step in actual distance. What we would like is a (roughly) constant step in terms of distance along the curve, which we get by replacing

theta += step


with

theta += step / r


This makes step into an approximate distance along the curve in pixels (rather than a change in angle, as in the original code). Now, with step=1 and loops=10, we get this spiral:

While a transformation to cartesian coordinates is not too complicated, I would still consider encapsulating it in a function:

def polar_to_cartesian(r, theta):
return int(r*cos(theta)), int(r*sin(theta))


which you can then use in draw_spiral (you can even just use pos instead of (x, y)):

def draw_spiral(a, b, img, step=0.5, loops=5):
"""
Draw the Archimedean spiral defined by:
r = a + b*theta
Args:
a (real): First parameter
b (real): Second parameter
img (Image): Image to write spiral to.
step (real): How much theta should increment by. (default: 0.5)
loops (int): How many times theta should loop around. (default: 5)
"""
draw = ImageDraw.Draw(img)
theta = 0.0
r = a
prev_pos = polar_to_cartesian(r, theta)
while theta < 2 * loops * pi:
theta += step
r = a + b*theta
# Draw pixels, but remember to convert to Cartesian:
pos = polar_to_cartesian(r, theta)
draw.line(translate(prev_pos, img.size[0]) +
translate(pos, img.size[0]), fill=1)
prev_pos = pos