Golang, pictures, and fun
Today, I would like to write with you a small project, which draws images by other images, creating a mosaic. Of course, we will implement this using Golang (because it is stylish, trendy, and fast, of course).
Our task sound like this:
You need to get one image and draw each pixel from that image using a small image, so to get a “mosaic” an image of hundreds of little ones.
I will use my avatar as the source image (more than me, lol). And as parts of the mosaic, icons from WoW (since there are quite a lot of them to complete the palette). As a result, we should get something like this:
So, let’s go!
As usual, first we create
main.go with a template for our builder.
Next, we need our icons, with which we will lay out the mosaic. As I said above, here we will focus on the icons from the World of Warcraft game. You can use absolutely any images, as long as they are square and there are a lot of them.
Okay, we have downloaded our icons, put them somewhere, but we need to know all the pictures that we will use to build our image. To do this, we will write a function that will traverse the directory tree with pictures and return a flat list of paths to them.
Let’s add the
getPartsPaths function and also fix the path to the icons and their size in our collector.
Cool, we can collect elements of our mosaic!
To start picking out images, we need to have quick access to these pictures of their base color, that is, when we find blue in our source image, we need to quickly find the picture that is most similar to blue and do it quickly. To do this, we need to make a map of our icons, where the key will be the color, and the image itself will be the value.
Let’s write some functions:
- loadImage — will load our image from disk into memory for quick access
- calculateModalAverageColour — will get the base color of the image
- getPartsMap — will return our map with images
Since our icons are in the TGA format, and the built-in Golang libraries do not know how to work with this format, we need to install the github.com/ftrvxmtrx/tga package.
go get github.com/ftrvxmtrx/tga
Also, it would be good for us to be sure that our images will have 1 and the same size, for this we will use the github.com/nfnt/resize package.
go get github.com/nfnt/resize
Next, we implement
calculateModalAverageColour, which will calculate the average color of the image and return the color value of the pixel in the RGB model.
In fact, we just calculated the average of each RGB parameter for each pixel, this is not an exact model, but it is enough for us.
Next, we combine what we wrote above and implement the getPartsMap function, which will return the finished image map.
Let’s say that something went wrong while uploading images, so let’s skip the erroneous ones.
Let’s go back to our main Build function and add the getting of the map.
It seems that everything is ready, and we can start laying out our image with flowers, but wait, cowboy, it’s still early 😉
Our map will not be enough, as it does not cover the ENTIRE color palette of the image. We need to understand which color (which icon) suits us best.
Next, we need to implement a search function in our map that will calculate the Euclidean distance between RGB points and return the closest part.
We implement the
getClosestPart function, which will take our map and the pixel that we read from the source image as input.
Be sure to make sure earlier that the map size > 0, otherwise you will get a panic on the nil pointer.
Very well, we have the opportunity to get the closest images to our pixels, it remains only to create a new image, go through the original one and lay out our mosaic, go ahead!
Let’s add the final part, our “Build” function, which opens the source image img.jpg (you can use other built-in decoders, for example PNG) and creates res.png.
In this function, we intentionally reduced our image to 300×300 px, otherwise it will be terribly large, since its size in our case will increase by 60×60 times!
That’s all, the full listing of our program looks like this.
This program can be optimized, but it can be your homework!
As an optimization, you can do:
- Do not recalculate the map at the start of the program and make it lazy load. That is, write the colors and file paths in JSON, load the image along the path in JSON as it is required.
- Make image map loading parallel.
- Make a pass through all pixels parallel.
We have covered in this article:
- How to work with files in Golang
- Worked with built-in types in Golang: slices, maps, and creating custom types.
- How to work with images in Golang
- SCIENCE touched! Specifically, the algorithm for finding the Euclidean distance.