Who didn’t play with LEGO in his/her childhood? The Danish toy company just overtook Mattel as the number one toy maker in the world. I recently came across some classical paintings remodeled with LEGO bricks on the internet . And with bricks I mean small 1x1 plates giving a pixelated view onto the old masterpieces. Which got me thinking about two things: Is it possible to remodel any old classic into a LEGO version of itself? And: Can you do it only with colors already available for purchase? The last thing is important if you want to actually build the image after remodeling it. The examples I have found used a lot of colors not in the standard LEGO color palette.
But first, let’s have a look at the original I have chosen. It is The Girl with the Pearl Earring by Johannes Vermeer.
The internet is made for … arbitrary table layouts
First things first. One of the first problems to tackle is the range of colors available. This was the first time I stumbled across Bricklink, a shop site offering single bricks for purchase. Trust me: They are great! And this is not the last time you will hear about them in this post (Trust me!). Bricklink offers a full color table. The good news: The table offers a timeline for each color and therefore the possibility to filter colors which are not available any more. The bad news: HTML crawling is necessary. Yay! So let’s set up a color table in a pandas DataFrame.
We import all the basic stuff for HTML crawling. Requests for downloading, BeautifulSoup for CSS selection and pandas for storing the table.
Download the color table website…
…and parse the information.
You might have noticed that I removed some colors by hand. Those are the colors which are so rare, they score up to 4.00€ per piece on Bricklink. If you still want them in your picture just remove the last instruction. So, current_colors should look like this. It contains circa 30 colors.
1950 - 2017
Light Bluish Gray
2003 - 2017
Dark Bluish Gray
2003 - 2017
50 Shades of LEGO
The following progress is called color quantisation. It replaces all colors with a limited choice of other colors. In our case the image pixels will be transmuted (+1 on the fancy word scale) to the LEGO color scale. This is done by finding the nearest neighbours in the RGB space. Each pixel is represented as a vector of its R, G and B component. The distance between two pixels is the euclidean distance between their respective vectors.
Now we replace all the pixels in the original (girl.jpg) with colors from current_colors.
After all this, the image (rescaled to the original’s size) looks like this. Isn’t she a looker?
Notes: If you use the fleshy colors, you will get a more even skin tone. You can see that the reduced color palette is far from perfect for classical images.
…one can see that filling a image with only 1x1 plates (part ID: 3024) costs a rather large amount of money. For me, it was between 65€ and 103€ depending on the shop. This should make it clear to the reader, why I have chosen to shrink the image to 32 pixels. Any size larger would only cost more. But is there a way, not to Amarillo, but to enlarge the portions of the image with the same color? LEGO bricks and plates are rectangular most of the time. Can we fit rectangles larger than 1x1 into the image? Spoiler!
I’ll make my own algorithm. With blackjack and graphs!
Fitting larger rectangles into the image should be easy. Just find all areas with the same color. Then, try to fill the areas with large rectangles. Let’s transfer the image to a graph. Every pixel is translated to a node with the x and y coordinates as positions in the graph and the color as a node property.
The final graph looks a little bit like the original image, if you really squint your eyes.
You might have noticed the connections between the areas with the same color. Every pixel (or node) is connected to its neighbour if their color is the same. So, every enclosed color builds a connected component in the graph. The biggest one, as shown below, is of course the black background.
Now the tricky part begins. We fill all connected components of the graph with rectangles as large as possible. Therefore, the algorithm starts with the minimum node (left upper corner of an area) and expands southwards until the edge of the area is found or the maximum size is reached. Afterwards, it tries to expand the area eastwards with the same conditions. If an edge is found or the maximum size is reached the last complete rectangle is returned. The following video details the steps for the first black rectangle.
The function find_rectangles returns the complete set of rectangles over all connected components for the image.
max_size is used to control the maximum dimension of LEGO plates used. You can change it according to your likings. The full traversal looks like this:
Calling Billund (DK)
After collecting another HTML page with the mappings from brick sizes to part IDs,…
…I’m able to collect a material list…
..and build the final Bricklink XML.
The XML can now be uploaded to Bricklink and you can order all the parts there.
We built this city on LEGO
Let me summarise what I have accomplished.
quantised and pixelated an image to make it look like it is made out of LEGO,
intelligently reduced the number of bricks used via graph connectivity,
having so much fun, I actually ordered the bricks (~30€) and built the image.
Yes, you read right: I built the image with actual LEGO bricks. So, the last thing for me to say is: Back to my hands, desperately trying to find that one piece, I saw earlier. Arrgh! Where is it?