Hey I used Python and Pillow to make grids for my drawing. Read on to watch my brain while I figured it out. Apologies for the minimal editing and the ridiculous number of images.
I enjoy Drawing . Many of my sketches have repeated elements, like Zentangle or Celtic inspired patterns. Okay, I don’t have many examples on the site. Sure there’s plenty of repetition based on symmetry tools in the drawing apps I use, and a little bit taking advantage of perspective grids. Not much in the way of simple grid-based repetition though.
Templates exist, but I want custom templates to fit the size of my workspace. I started exploring the Pillow library recently, so let’s use that to make custom grids for my drawings.
from PIL import Image
if __name__ == '__main__' :
height = 600
width = 600
image = Image. new(mode= 'L' , size= (height, width), color= 255 )
image. show()
I use a modest 600 by 600 pixel grayscale image while working out the details. No point saving anything until I know what’s going on, so just show()
the image.
Most of what I want is in the ImageDraw module.
Simple Grid
from PIL import Image, ImageDraw
if __name__ == '__main__' :
height = 600
width = 600
image = Image. new(mode= 'L' , size= (height, width), color= 255 )
# Draw a line
draw = ImageDraw. Draw(image)
x = image. width / 2
y_start = 0
y_end = image. height
line = ((x, y_start), (x, y_end))
draw. line(line, fill= 128 )
del draw
image. show()
Nice. Okay, how about repeating some lines across?
from PIL import Image, ImageDraw
if __name__ == '__main__' :
height = 600
width = 600
image = Image. new(mode= 'L' , size= (height, width), color= 255 )
# Draw some lines
draw = ImageDraw. Draw(image)
y_start = 0
y_end = image. height
step_size = int(image. width / 10 )
for x in range(0 , image. width, step_size):
line = ((x, y_start), (x, y_end))
draw. line(line, fill= 128 )
del draw
image. show()
Lovely. How about an actual grid?
from PIL import Image, ImageDraw
if __name__ == '__main__' :
height = 600
width = 600
image = Image. new(mode= 'L' , size= (height, width), color= 255 )
# Draw some lines
draw = ImageDraw. Draw(image)
y_start = 0
y_end = image. height
step_size = int(image. width / 10 )
for x in range(0 , image. width, step_size):
line = ((x, y_start), (x, y_end))
draw. line(line, fill= 128 )
x_start = 0
x_end = image. width
for y in range(0 , image. height, step_size):
line = ((x_start, y), (x_end, y))
draw. line(line, fill= 128 )
del draw
image. show()
Okay cool but I often need a specific number of squares in my grid.
from PIL import Image, ImageDraw
if __name__ == '__main__' :
step_count = 25
height = 600
width = 600
image = Image. new(mode= 'L' , size= (height, width), color= 255 )
# Draw some lines
draw = ImageDraw. Draw(image)
y_start = 0
y_end = image. height
step_size = int(image. width / step_count)
for x in range(0 , image. width, step_size):
line = ((x, y_start), (x, y_end))
draw. line(line, fill= 128 )
x_start = 0
x_end = image. width
for y in range(0 , image. height, step_size):
line = ((x_start, y), (x_end, y))
draw. line(line, fill= 128 )
del draw
image. show()
Right but I don’t want to edit the code every time.
import sys
from PIL import Image, ImageDraw
if __name__ == '__main__' :
step_count = 10
if len(sys. argv) == 2 :
step_count = int(sys. argv[1 ])
height = 600
width = 600
image = Image. new(mode= 'L' , size= (height, width), color= 255 )
# Draw some lines
draw = ImageDraw. Draw(image)
y_start = 0
y_end = image. height
step_size = int(image. width / step_count)
for x in range(0 , image. width, step_size):
line = ((x, y_start), (x, y_end))
draw. line(line, fill= 128 )
x_start = 0
x_end = image. width
for y in range(0 , image. height, step_size):
line = ((x_start, y), (x_end, y))
draw. line(line, fill= 128 )
del draw
image. show()
Run it.
$ python grid.py 12
I can specify step count from the command line. Cool. Uh hey about height and width?
import sys
from PIL import Image, ImageDraw
if __name__ == '__main__' :
step_count = 10
height = 600
width = 600
if len(sys. argv) == 2 :
step_count = int(sys. argv[1 ])
elif len(sys. argv) == 3 :
width = int(sys. argv[1 ])
height = int(sys. argv[2 ])
elif len(sys. argv) == 4 :
width = int(sys. argv[1 ])
height = int(sys. argv[2 ])
step_count = int(sys. argv[3 ])
image = Image. new(mode= 'L' , size= (height, width), color= 255 )
# Draw some lines
draw = ImageDraw. Draw(image)
y_start = 0
y_end = image. height
step_size = int(image. width / step_count)
for x in range(0 , image. width, step_size):
line = ((x, y_start), (x, y_end))
draw. line(line, fill= 128 )
x_start = 0
x_end = image. width
for y in range(0 , image. height, step_size):
line = ((x_start, y), (x_end, y))
draw. line(line, fill= 128 )
del draw
image. show()
Oh come on. Stop it with sys.argv
. Get some real command line handling in there.
import argparse
from PIL import Image, ImageDraw
if __name__ == '__main__' :
parser = argparse. ArgumentParser()
parser. add_argument("width" , help= "width of image in pixels" ,
type= int)
parser. add_argument("height" , help= "height of image in pixels" ,
type= int)
parser. add_argument("step_count" , help= "how many steps across the grid" ,
type= int)
args = parser. parse_args()
step_count = args. step_count
height = args. height
width = args. width
image = Image. new(mode= 'L' , size= (height, width), color= 255 )
# Draw some lines
draw = ImageDraw. Draw(image)
y_start = 0
y_end = image. height
step_size = int(image. width / step_count)
for x in range(0 , image. width, step_size):
line = ((x, y_start), (x, y_end))
draw. line(line, fill= 128 )
x_start = 0
x_end = image. width
for y in range(0 , image. height, step_size):
line = ((x_start, y), (x_end, y))
draw. line(line, fill= 128 )
del draw
image. show()
Much better. Run it.
$ python grid.py
usage: grid.py [-h] width height step_count
positional arguments:
width width of image in pixels
height height of image in pixels
step_count how many steps across the grid
optional arguments:
-h, --help show this help message and exit
$ python grid.py 500 500 20
I like Argparse .
Anyways - what if I ask for a rectangle instead of a square?
$ python grid.py 400 600 24 \{\{< /console >}}
Rectangular grid
Hold on. I was handing height
and width
to Image in the wrong order this whole time.
if __name__ == '__main__' :
# ...
image = Image. new(mode= 'L' , size= (width, height), color= 255 )
# ...
Run it.
$ python grid.py 400 600 24
This works. I have half a dozen ideas left, but I want to use it for a sketch now .
grid.py
import argparse
from PIL import Image, ImageDraw
if __name__ == '__main__' :
parser = argparse. ArgumentParser()
parser. add_argument("width" , help= "width of image in pixels" ,
type= int)
parser. add_argument("height" , help= "height of image in pixels" ,
type= int)
parser. add_argument("step_count" , help= "how many steps across the grid" ,
type= int)
args = parser. parse_args()
step_count = args. step_count
height = args. height
width = args. width
image = Image. new(mode= 'L' , size= (width, height), color= 255 )
# Draw a grid
draw = ImageDraw. Draw(image)
y_start = 0
y_end = image. height
step_size = int(image. width / step_count)
for x in range(0 , image. width, step_size):
line = ((x, y_start), (x, y_end))
draw. line(line, fill= 128 )
x_start = 0
x_end = image. width
for y in range(0 , image. height, step_size):
line = ((x_start, y), (x_end, y))
draw. line(line, fill= 128 )
del draw
filename = "grid- {} - {} - {} .png" . format(width, height, step_count)
print("Saving {} " . format(filename))
image. save(filename)
$ python grid.py 1800 2400 50
Saving grid-1800-2400-50.png
$ ls
grid-1800-2400-50.png grid.py*
Let’s skim over the part where I get the grid onto the iPad and import it as a new layer in my current sketch. That part includes no code — for now.
Anyways, back to the sketch.
Added to vault 2024-01-15.
Updated on 2024-02-01