Dice grid from a photo with Python (III) – Creating the resulting picture

This post is part of the Project: Create a grid of dice from a photo with Python and PIL

Introduction

In the first post of this project I mentioned the steps to create a dicing grid parting from a picture:
  1. Divide the picture into subsets
  2. Analyze each subset and replace it with the correspondent die based on its brightness
I already defined the functions to draw the dice, so in this post I'm going to continue with the functions needed to transform the picture into a grid of dice, basically finishing the main purpose of this project.

The steps to code the dicing grid are a bit different than the mentioned before, being like this:
  1. Get the constant values using the formula mentioned in the first post
  2. Validate or resize the size of the picture
  3. Divide the picture into subsets
  4. Analyze each subset and replace it with the correspondent die based on its brightness

Coding

Importing modules

In this program, we are only going to need to import PIL.Image and dice.py, the file that was written in the previous post.
For this to work correctly it is needed to save this file in the same folder than dice.py.


Coding the formula and storing the values

In the first post I mentioned the following function to get the range of values every die would cover:
With the following results:
We can "translate" that function into Python very easily, and store its values in a constant list that is going to be named DICE_VALUES, all in caps to indicate that its values are constants that are never going to change during the program.


You can see that DICE_VALUES is written using a list comprehension. If you're not familiarized with the syntax, the "explicit" equivalent would be:DICE_VALUES = []
for i in range(1, 7):
    DICE_VALUES.append(formula(i))


The range(start, end) function generates an iterable object that goes from start to end-1, that's why I wrote range(1, 7) instead of range(1, 6).

Validating the size of the picture

In the first post of this project, I said that it's possible that the picture dimensions can not be divided by the size of the dice and that it would be needed to resize them as a way to solve this problem because we can't put half-dices in the picture.
We will define a function that will do this for us. My particular solution to this problem is to divide the width and height of the image by the size of the die, then multiply the integer part with the size of the die to get an approximate but similar value to the original one.
For example:
However, this procedure can deform the image a bit too much, like in the example, on which we are losing 2 pixels for the width and 5 pixels for the height. While the width is not being too affected, the lacking pixels on the height are over half the size of the die. To correct this, I decided to sum the size of the die if the difference between old width and new width or old height and new height is over half the size of the die, by applying this on the example the result would become 798x602, closer to the original size.
The code:


Custom number of dice

The next step is to define a function to limit the number of dice we are putting in the picture.
We can know how many dice are going to be needed to display a picture with the equation:
Parting from it, we can find another one that will return a ratio.

With this ratio, we can either divide the size of the picture by it, if we are adjusting the number of dice by resizing the picture again or multiply the size of the dice to adjust the size of the dice. As I think that resizing the picture is simpler than adjusting the size of the dice, that will be the approach I'll take. Later, if the user decides to limit the number of dice, we'll resize the picture to its original size.
The code:


Analyze a subset of pixels

The next step in the project is to analyze the average value in a subset of the picture with the size of the dice.
To do this we are going to take advantage of a method that PIL.Image provides, Image.getpixel(x, y), which lets us grab a pixel given some coordinates and get its value. Now we only have to write a function that traverses through a certain area of the picture and gets the values of all the pixels in that certain area, after that, we only need to get the average value and we'll know what die put in the area.

My solution is to write a loop to move around the y-axis of the subset and a nested loop to move around the x-axis of it. That way we'll be dividing the area into rows and we'll get all the values in a row before moving to the next one.
As I want this function to work at any place in the picture I'll be taking two parameters, start_x and start_y, that will work as an offset.
The values are stored in a list and the average is returned by the function.


Convert the picture into a grid of dice

At this point we've done most of the hard work needed to create the grid of dice, now its only needed to define one last function that taking an image as input combines all the previous functions we've coded in the correct way to form a new picture resembling the original but with dice.

For this function I'm taking four parameters: Image to be converted to dice, size of the dice, border (True or False) and max number of dice.
First of all, I'll call the dice_dots functions in the dice module passing the border and size of the dice as parameters and I'll store the six dice in a list, after that I convert the picture to grayscale.


Now I check if there's a max number of dice to put in the picture. If there is, then we call the function limit_num_dices(). Either way, I check the dimensions of the image to see if the size of the dice fits it correctly with check_image_size().


In the last part of the function I get the size of the Image and write two loops: One for the y-axis using a range going from 0 to the value of the height of the image, each step is the size of the die and another nested loop for the x-axis, with a range going from 0 to the value of the width of the image, each step is the size of the die.
As in the get_pixel_area() function, using the loops this way allows us to separate the image in rows and move through the whole x-axis before moving in the y-axis covering an area equal to the defined size of the dice. I.e. if we begin in the (0, 0) the next step will be at (7, 0), then (14, 0) and so on. And when the row is over it will advance the same amount of pixels in the way axis, so we'll be then at (0, 7), then (7, 7), (14, 7) and so on until covering the whole picture.
Inside the nested loop I get the average value of the area that it's being covered and compare it to the DICE_VALUES, based on the formula defined before and the next picture:

After the comparing, the correspondent die is pasted in the picture at the given coordinates.

And that's all! You can now call the function and create a grid of dice from any picture.

Extra: Getting stats

If you want to know how many dice were used to create the new picture and the number of dots in each die you can implement a dictionary with the number of dots as keys and update its value every time a die is pasted in the picture.
When the process is over you can print the values in the dictionary.

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