Last Updated on February 9, 2022 by Jay
In this tutorial, I’ll show you how to create Excel pixel art with Python. With a little technical skill, you can become a professional Excel artist.
Take a look at the below short video to get a sense of what we are about to achieve.
Required Libraries
For this project, we’ll use two libraries: xlwings
and opencv
.
xlwings
– one of the best Python libraries to automate Excel filesopencv
– a library for computer vision
Opencv
is not required as the library is really intended for machine learning applications. We can use something like Pillow
to replace it. However, I already had it that’s why I’m sticking with opencv
.
What Is A Picture?
Basically, an image as we know consists of thousands of pixels with different colors. Each pixel has one color. However, once we zoom out far enough and the size of a pixel becomes very small, we don’t see the pixels and all we see is a picture.
How Does The Excel Art Program Work?
At a high level, we use an Excel sheet as our “canvas” with each cell being a single “pixel” on the canvas. Then we create the artwork by filling each cell (pixel) with a different color. For instance, the picture I showed in the video has a size of 1,257 * 700 which translates into 879,900 pixels. Once we fill colors for all the 879.9k cells, then zoom out far enough, an image will appear.
How Do Computers Understand Color?
The color of each pixel has a meaning to humans, like red, green, blue, etc.
However, to a computer, those colors are just a bunch of integer numbers.
Black And White Picture
Usually, we can use just a single integer to represent a color. We use 0 to represent the black color, and 255 to represent white. Any integers in between are some kind of grey color (between black and white).
Take the below B&W picture as an example. It’s a handwritten number 3 with writing in white color on a black background. Once read into Python, what computer “sees” is a matrix of integer numbers from 0 to 255.
Color Picture & RGB Model
Color pictures are usually represented using three separate color channels. One of the most common color models is the RGB model which contains Red, Green, and Blue channels. Similar to the B&W color, each of the RGB color channels also can take on integer values between 0 and 255. So in theory, we can use the RGB model to create a total of 256 ^ 3 = 16.777 million different colors! Unfortunately, our eyes can’t tell the subtle differences between many colors…
Using Excel as our color palette to illustrate how to use RGB to create colors. As shown below, we can create the “cyan” color by mixing green and blue colors together while leaving out the red (0, 255, 255).
The intersection of Red, Green, and Blue is White, with an RGB value of (255,255,255).
Now we have a good understanding of pictures and colors. It’s time to practice our art skills!
Working With Pictures In OpenCV
Let’s now load a picture into Python with opencv
. The matplotlib
library is only for displaying the image for demonstration purposes, our actual program doesn’t require it.
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread(r'one-punch-man.jpg', cv2.IMREAD_COLOR)
With the above 4 lines of simple code, we have loaded an image file into Python. In the cv2.imread()
method:
- The 1st argument is the path of the image file
- The 2nd argument specifies that our image file is a color image.
Let’s see what it looks like:
As expected, just a bunch of numbers. Note that these are groups of 3 integers, with each group representing an RGB value.
Further checking the img
object, we realize it’s a numpy.ndarray
object, with a size of (700, 1257, 3).
- 700 means the number of pixels vertically (i.e. # of rows)
- 1257 means the number of pixels horizontally (i.e. # of columns)
- 3 means each element in the ndarray is again a list, with 3 items inside
Let’s use the matplotlib
to display this image. Hmm, Saitama seems cold… what’s happening?
It turned out that opencv
actually expects BGR instead of RGB, so the R and B are swapped. Whereas matplotlib
uses the conventional RGB format. What we need is to swap the R and B channels back, which is easy to do using a list reversal.
Transfer Pixel Colors From Python To Excel
I named this program “Picasso bot” since it’s quite talented in painting. Let’s create a function for it.
Prepare Image
def picasso_bot(img_path, direction= None, x=1, y=1):
img = cv2.imread(img_path,cv2.IMREAD_COLOR)
if x != 1 and y !=1:
img_resize = cv2.resize(img, None, fx=x, fy=y)
else:
img_resize = img
row, col, _ = img_resize.shape
We’ll also give it a nice optional feature to resize the image. This is because for large pictures it will be difficult to show inside Excel (Excel zoom out max is at 10%), and it will take a long time to paint.
We can use the cv2.resize
to change the picture size, remember to keep the argument x
and y
to be the same values to maintain the aspect ratio of the image.
Next, we’ll find out the height (rows) and length (columns) of the image and store them inside two variables called row, and col. Note there’s a third value for shape once we unpack it, and that is the 3 (for RGB). We don’t care about this value from the .shape attribute, so we can just ignore it and set it to “underscore”.
Prepare Excel Sheet For Art
Now the picture data (i.e. each information for each pixel) is inside a matrix. We can start painting cells. To make this Excel art “live painting”, we’ll use the xlwings
library.
First, we’ll open up a new workbook. Then, we’ll set the column width and cell heights to be appropriate values such that each cell appears to be a square. I found the 0.3 and 3 to be good values for width and height, feel free to try other value combinations.
We’ll also add a sleep timer to pause the program for 3 seconds so that we have time to maximize the Excel window and zoom out on the cells.
book = xw.Book()
sheet = book.sheets[0]
xw.Range((1,1),(1,col+1)).column_width = 0.3
xw.Range((1,1),(row+1,1)).row_height = 3
time.sleep(3)
Paint Direction
Notice we have a direction
argument in the function? Our “Picasso” Excel art bot is capable of painting in different order/styles. Here, I’m only showing how to paint horizontally line by line. Feel free to come up with your own creative painting styles. You can paint vertically, or even diagonally!
if direction == 'horizontal':
r = 1
for row in img_resize:
c = 1
for i in row:
if not (i == 255).all():
sheet.cells[r,c].color = (i[2],i[1],i[0])
c += 1
r += 1
Here we use a nested loop. We’ll:
- Start painting cells on the top-left corner of the picture, paint each cells for the first row from left to right
- Then move on to the second row, paint all cells from left to right
- And so on
The (i == 255).all()
checks if a pixel is a white color, then it will skip painting it. If not a white color, i.e. not all of the RGB values are 255, then we’ll paint that pixel.
Each i
in the loop represents the RGB value for a single pixel, and it should be a list with three integers. Also, remember we have to swap the R and B so that BGR becomes RGB?
Putting It All Together
The below provides a shell program for creating Excel art pieces, feel free to copy it and come up with your own creative versions!
import cv2
import xlwings as xw
import time
def picasso_bot(img_path, direction= None, x=1, y=1):
img = cv2.imread(img_path,cv2.IMREAD_COLOR)
if x != 1 and y !=1:
img_resize = cv2.resize(img, None, fx=x, fy=y)
else:
img_resize = img
row, col, _ = img_resize.shape
book = xw.Book()
sheet = book.sheets[0]
xw.Range((1,1),(1,col+1)).column_width = 0.3
xw.Range((1,1),(row+1,1)).row_height = 3
time.sleep(3)
if direction == 'horizontal':
r = 1
for row in img_resize:
c = 1
for i in row:
if not (i == 255).all():
sheet.cells[r,c].color = (i[2],i[1],i[0])
c += 1
r += 1