Dice grid from a photo with Python (II) – Drawing the dice

This post is part of the Project: Create a grid of dice from a photo with Python and PIL
  1. The logic behind the program
  2. Drawing the dice
  3. Create a grid of dice from a photo
  4. Design of the GUI
  5. Document and creation of the stand-alone executable

Introduction

In the previous post we analyzed all the steps to create a dicing grid from a picture in theory, but no code was written.
In this post, I will show you how to draw dice with Pillow and its methods.

In this post, we will see:
  1. How to install Pillow as a Python module
  2. How to import and use the methods in PIL.Image and PIL.ImageDraw in our code
  3. How to divide our code into functions
  4. Use of arguments and keyword arguments

Installation of Pillow

Pillow is a library that adds support for manipulation of images in Python. Pillow is a fork of PIL, appearing after the later stopped being updated in 2009.
You can install Pillow through PIP on Windows, Mac, and Linux with the command:
pip install Pillow
Installing Pillow on Windows
Installing Pillow is an easy process if you already have PIP
After the installation process ends you can open a Python Console and write
import PIL

If no error message appears, then now you count with Pillow in your Python installation.

Drawing dice in Python with Pillow

Now that Pillow has been added to Python we can draw images and use them in our programs.
Assume that all the code in this post will be saved in a file named dice.py.
We will start by drawing the face of a die with only one dot, but before we define our function we must import the necessary modules from PIL: Image which contains the methods to open and handle image files and ImageDraw that allows us to draw in the images. After that, we can write the body of our function.

The first step is to create a new Image using PIL so we will call the method Image.new() from the Image module with the following specifications:
  • 1 Color Mode (1 bit per pixel, allows only black and white, saving memory compared to other color modes like RGB. You can read more about color modes in the Pillow documentation). This will be enough to draw the die.
  • 7x7 px, that will be the size of our die.
  • White color
We create the new Image and store it in a variable im writing:
im = Image.new("1", (7,7), "white")

Note how the color mode is a string, the size is a tuple (width, height) and the color mode is a string. Although PIL supports getting colors by value (and it's necessary to pass them that way to get specific colors) it also supports strings, making the code more readable as the value of some colors varies according to the color mode.

Before we can draw in the new image we need to create an ImageDraw object that will provide us with the methods we need to do it, so we write:
draw = ImageDraw.Draw(im)

To draw a single pixel we are going to use the ZZZpoint() method, passing the coordinates and the color as arguments. To get the coordinates you can look at the template from the previous post:

With the coordinates we summon the method:
draw.point( (3,3), "black")

The code so far looks like this:

However, if we tried to implement this in a grid, it would look like this:
Without a border, it will be hard to know where a die ends and the other begins, so we will give the user the option to decide if they want a border.  As that is code we could reuse for other functions and it is not related at all with the creation of the die, I recommend writing another function to add a border to the die.
But before writing the function we must consider what do we really need and the more suitable solution. If we add a border at every die our 7x7 die will become a 9x9 die and the grid will be severely affected:
As you can see, we've passed from 7 columns without borders to 5 with borders. A way to lessen this loss of space is to use a semi-border instead of a full one. That way our die will pass from this:

To this:

And the grid will look like this:
Gaining one column compared to the full border and looking more similar to the borderless picture.
To add that semi border we only need to create an Image slightly larger than the die and then paste the die in it a little "moved".
For this function we will get three parameters:
  • The image on which we will add the border
  • The size of the border (default=1px)
  • The color of the border (default= RGB(245, 245, 245) )

In this function, we are using the method paste() for the first time in our program. This method allows us to add an image to another at a specific coordinate.

Now we can go back to our one_dot() function and add the bool border as a parameter and include a call to the function add_border() if border == True.

As we defined default parameters or keyworded arguments in the add_border() function it's unnecessary to pass a border_size or a color to the function unless we want to change them, so we only pass the die image.
But before finishing with the one_dot() function there's still one thing that we might want to do: Create dice of different sizes. To do that we just need to add a parameter size and call the method resize() if the size is not 7x7.
The method resize() takes a tuple as an argument. With that, the function now looks like this:

Note that I resize the image before adding the border; otherwise, the border would be resized too and it would be larger than 1px after the resizing.

The functions to draw the other faces of the die share most of the steps, only changing the coordinates of the black points.

To make the construction of multiple dice easier I created a helper function that takes one extra argument: Number of dots in the die, returning one of the functions that we already coded after some comparisons:

Note how it raises an error if the number of dots is lesser than 0, greater than 6 or a number with a decimal (I used the remainder operand instead of checking for the number type because this way both 6 and 6.0 are valid numbers).

And that's all we need to draw the dice. In the next post I'll show you how to code the functions needed to create a dice portrait from a picture, but before that, I would like to take a moment to talk about docstrings.

Documenting the code with docstrings

A docstring is a string literal that appears at the beginning of each class and function in Python, describing what it does, its arguments and the value it returns.
PEP 257 shows the docstring conventions that we can follow to document our code. It's a useful read to understand why docstring matters and their purpose, although as conventions, we are not obliged to follow them and personally, I prefer the style of Sphinx.

Comments

Popular posts from this blog

How to install Spyder 3 on Windows without Anaconda

How to install PyQt5 and Qt Designer on Ubuntu

Hello World in Python 3