# Circular Grids With Python and Pillow

Added by to Programming on and tagged · python ·

About 5 minutes to read (899 words) Circular Grids With Python and Pillow (see original image in new window)

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)
parser.add_argument("--circles", help="number of circles",
type=int, default=DEFAULT_CIRCLES)
parser.add_argument("--slices", help="number of slices",
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

radius_step = int(self.midpoint / self.circle_count)

for radius in range(0, self.midpoint, radius_step):
bounding_box = [
(self.midpoint - radius, self.midpoint - radius),
(self.midpoint + radius, self.midpoint + radius)]
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. concentric circles

## Add some pie slices

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

radius_step = int(self.midpoint / self.circle_count)
# To remember the largest circle we drew.
last_radius = 0

for radius in range(0, self.midpoint, radius_step):
bounding_box = [
(self.midpoint - radius, self.midpoint - radius),
(self.midpoint + radius, self.midpoint + radius)]
draw.arc(bounding_box, 0, 360)
last_radius = radius

return last_radius

def _draw_slices(self, draw, radius):
if self.slice_count <= 0:
return

pie_box = [
(self.midpoint - radius, self.midpoint - radius),
(self.midpoint + radius, self.midpoint + radius)]
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? concentric circles divided by pie slices

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. generated circle template

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! template prepared for drawing (see original image in new window)

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