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 remodelled 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 LEGOfied version of itself? And: Can you do it only with colours already available for purchase? The last thing is important if you want to actually build the image after LEGOfying it. The examples I have found used a lot of colours not in the standard LEGO colour 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.
First things first. One of the first problems to tackle is the range of colours 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 colour table. The good news: The table offers a timeline for each colour and therefore the possibility to filter colours which are not available any more. The bad news: HTML crawling is necessary. Yay! So let’s set up a colour 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.
You might have noticed that I removed some colours by hand. Those are the colours 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_colours should look like this. It contains circa 30 colours.
The following progress is called colour quantisation. It replaces all colours with a limited choice of other colours. In our case the image pixels will be transmuted (+1 on the fancy word scale) to the LEGO colour 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 colours from current_colours.
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 colours, you will get a more even skin tone. You can see that the reduced colour 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 colour? 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 colour. 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 colour 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 colour. Every pixel (or node) is connected to its neighbour if their colour is the same. So, every enclosed colour 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.
assert max_size % 2 == 0 or max_size < 4, "Even numbers for max_size only!"
..and build the final Bricklink XML.
The XML can now be uploaded to Bricklink and you can order all the parts there.
Let me summarise what I have accomplished.
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?
Other sources and libraries include pillow, networkx and scikit-learn.