# Circular Grids With Python and Pillow

Added by to Programming on and tagged · python ·

I want a circular grid for drawing. Let's make one with Python!

A while back, I wrote about drawing grids with Python and Pillow. I no longer use that code so much, since Procreate now includes square grids in its drawing aid tools.

One idea sitting in my Taskwarrior queue for a full year now would still be useful, though. A circle template could help me break out of the square grid with my Celtic and Tangle drawings.

I already create circular drawings using symmetry tools in my drawing apps. Those are doodles, though: unplanned and improvised. I sketch and see what the automated symmetry produces from my linework. Circle templates simplify planning a complex image which I then produce, probably without using symmetry tools.

So, let's write a little code!

I'll keep using Python, since that worked for me last time. Lately I have been using the Anaconda Distribution for my Python programming needs. It includes a number of Python packages, including Pillow!

My template includes three characteristics:

• an origin in the center of my square image
• some concentric circles increasing in radius by a fixed amount
• some line segments slicing the image from the origin point to the outermost circle

## Write some code

I will save myself effort by grabbing some of the work used for drawing grids and putting into a new class.

``````#!/usr/bin/env python3
"""
Utility script to draw concentric circle templates for drawing
"""

import argparse

from PIL import Image, ImageDraw

DEFAULT_SIZE = 600
DEFAULT_CIRCLES = 10
DEFAULT_SLICES = 12

class CircleTemplate:
"""
Draws a circle template
"""
def __init__(self, size, circle_count, slice_count):
self.size = size
self.circle_count = circle_count
self.slice_count = slice_count
self.image = Image.new(mode='L', size=(size, size), color=255)

def save(self):
"""Write my circle template image to file"""
filename = "circle-{}-{}-{}.png".format(self.size, self.circle_count, self.slice_count)
print("Saving {}".format(filename))
self.image.save(filename)

def show(self):
"""Display my circle template image on screen"""
self.image.show()

def main():
"""Create a circle template from command line options"""
# Get details from command line or use defaults
parser = argparse.ArgumentParser()
parser.add_argument("--size", help="length of image side in pixels",
type=int, default=DEFAULT_SIZE)
type=int, default=DEFAULT_CIRCLES)
type=int, default=DEFAULT_SLICES)
args = parser.parse_args()
size = args.size
circle_count = args.circles
slice_count = args.slices
circle_template = CircleTemplate(size, circle_count, slice_count)
circle_template.show()

if __name__ == '__main__':
main()``````

My `CircleTemplate` class knows how to construct, save, and show a blank image. argparse processes the command line arguments for image size, number of circles, and number of slices. I added defaults so I don't have to type in a value every time I tested the script for this post.

I can build on this framework. Time to fill in the blanks.

## Draw some circles

``````from PIL import Image, ImageDraw

class CircleTemplate:
"""
Draws a circle template
"""
def __init__(self, size, circle_count, slice_count):
# ...
self.midpoint = int(size / 2)
self._draw()

# ...

def _draw(self):
"""Create circles and slices in-memory"""
draw = ImageDraw.Draw(self.image)
largest_circle = self._draw_circles(draw)
self._draw_slices(draw, largest_circle)
del draw

def _draw_circles(self, draw):
if self.circle_count <= 0:
return 0

bounding_box = [
draw.arc(bounding_box, 0, 360)``````

I need to figure out my origin, the center for my circles and slices. Since the image is a square, it will be the same along both the X and Y axes. This means I only need to calculate a single `midpoint`.

Each time we move on to a new radius, `ImageDraw.arc` creates a circle by drawing a 360 degree arc within `bounding_box`, a square that extends `radius` pixels from a midpoint along the `x` and `y` axes.

Right. I could do some moderately clever math to calculate angles and draw lines from the midpoint, or I could use the existing `ImageDraw.pieslice` method to accomplish pretty much the same thing. If you read the section title, you can probably guess what I chose.

``````class CircleTemplate:
# ...
def _draw(self):
"""Create circles and slices in-memory"""
draw = ImageDraw.Draw(self.image)
largest_circle = self._draw_circles(draw)
self._draw_slices(draw, largest_circle)
del draw

def _draw_circles(self, draw):
if self.circle_count <= 0:
return 0

# To remember the largest circle we drew.

bounding_box = [
draw.arc(bounding_box, 0, 360)

if self.slice_count <= 0:
return

pie_box = [
angle = 360 / self.slice_count
start_angle = 0

for pieslice in range(1, self.slice_count):
end_angle = angle * pieslice
draw.pieslice(pie_box, start_angle, end_angle)``````

I'm dividing the 360 degrees of a circle into `slice_count` pieces. `ImageDraw.pieslice` draws a tidy wedge at the angles we give it fitting the bounding box defined by my largest circle.

How does that look?

It looks pretty cool.

I need more circles and slices for the drawings I'm thinking of, though. Many more.

``````\$ python3 circle_template.py --circles=30 --slices=36
Saving circle-600-30-36.png``````

Yes, that's more like it.

This is all I need for a drawing template. Using transformation tools and the right blending modes, I can manuever and manipulate my grid however I need for a drawing template!

I'll stop here so I can get to my drawing.